mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-29 12:32:58 +08:00
Compare commits
12 Commits
win-virtua
...
v1.0.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bd27d0b17 | ||
|
|
ee5612da8b | ||
|
|
c7a2023c88 | ||
|
|
c031a8c145 | ||
|
|
0bf83f07ad | ||
|
|
3638b712bd | ||
|
|
17f9536476 | ||
|
|
0ef51e3faf | ||
|
|
cccf5dadb2 | ||
|
|
2f0b0ffc22 | ||
|
|
c7411b59f1 | ||
|
|
8222782522 |
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@@ -34,10 +34,11 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
id: set_deb_version
|
id: set_deb_version
|
||||||
run: |
|
run: |
|
||||||
|
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||||
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
||||||
LEGAL_VERSION="0.0.0-${VERSION_NUM}"
|
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
|
||||||
else
|
else
|
||||||
LEGAL_VERSION="${VERSION_NUM}"
|
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
|
||||||
fi
|
fi
|
||||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ jobs:
|
|||||||
XMAKE_GLOBALDIR: /data
|
XMAKE_GLOBALDIR: /data
|
||||||
run: |
|
run: |
|
||||||
ls -la $XMAKE_GLOBALDIR
|
ls -la $XMAKE_GLOBALDIR
|
||||||
|
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
|
||||||
xmake b -vy --root crossdesk
|
xmake b -vy --root crossdesk
|
||||||
|
|
||||||
- name: Decode and save certificate
|
- name: Decode and save certificate
|
||||||
@@ -96,10 +98,11 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
id: set_deb_version
|
id: set_deb_version
|
||||||
run: |
|
run: |
|
||||||
|
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||||
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
||||||
LEGAL_VERSION="0.0.0-${VERSION_NUM}"
|
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
|
||||||
else
|
else
|
||||||
LEGAL_VERSION="${VERSION_NUM}"
|
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
|
||||||
fi
|
fi
|
||||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||||
|
|
||||||
@@ -113,6 +116,7 @@ jobs:
|
|||||||
CUDA_PATH: /usr/local/cuda
|
CUDA_PATH: /usr/local/cuda
|
||||||
XMAKE_GLOBALDIR: /data
|
XMAKE_GLOBALDIR: /data
|
||||||
run: |
|
run: |
|
||||||
|
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
|
||||||
xmake b -vy --root crossdesk
|
xmake b -vy --root crossdesk
|
||||||
|
|
||||||
- name: Decode and save certificate
|
- name: Decode and save certificate
|
||||||
@@ -155,7 +159,8 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF##*/}"
|
VERSION="${GITHUB_REF##*/}"
|
||||||
VERSION_NUM="${VERSION#v}"
|
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||||
|
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
|
||||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||||
echo "VERSION_NUM=${VERSION_NUM}"
|
echo "VERSION_NUM=${VERSION_NUM}"
|
||||||
|
|
||||||
@@ -177,7 +182,9 @@ jobs:
|
|||||||
run: git submodule update --init --recursive
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
- name: Build CrossDesk
|
- name: Build CrossDesk
|
||||||
run: xmake b -vy crossdesk
|
run: |
|
||||||
|
xmake f --CROSSDESK_VERSION=${VERSION_NUM} -y
|
||||||
|
xmake b -vy crossdesk
|
||||||
|
|
||||||
- name: Decode and save certificate
|
- name: Decode and save certificate
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -215,7 +222,8 @@ jobs:
|
|||||||
$version = $ref -replace '^refs/(tags|heads)/', ''
|
$version = $ref -replace '^refs/(tags|heads)/', ''
|
||||||
$version = $version -replace '^v', ''
|
$version = $version -replace '^v', ''
|
||||||
$version = $version -replace '/', '-'
|
$version = $version -replace '/', '-'
|
||||||
echo "VERSION_NUM=$version" >> $env:GITHUB_ENV
|
$SHORT_SHA = $env:GITHUB_SHA.Substring(0,7)
|
||||||
|
echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV
|
||||||
|
|
||||||
- name: Cache xmake dependencies
|
- name: Cache xmake dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -281,8 +289,10 @@ jobs:
|
|||||||
copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir
|
copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir
|
||||||
|
|
||||||
- name: Build CrossDesk
|
- name: Build CrossDesk
|
||||||
run: xmake b -vy crossdesk
|
run: |
|
||||||
|
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y
|
||||||
|
xmake b -vy crossdesk
|
||||||
|
|
||||||
- name: Decode and save certificate
|
- name: Decode and save certificate
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
@@ -321,7 +331,8 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF##*/}"
|
VERSION="${GITHUB_REF##*/}"
|
||||||
VERSION_NUM="${VERSION#v}"
|
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||||
|
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
|
||||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Rename artifacts
|
- name: Rename artifacts
|
||||||
|
|||||||
39
.github/workflows/close-issue.yml
vendored
39
.github/workflows/close-issue.yml
vendored
@@ -17,26 +17,49 @@ jobs:
|
|||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
state: 'open',
|
state: 'open',
|
||||||
|
per_page: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
|
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
|
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
const lastUpdated = new Date(issue.updated_at).getTime();
|
// skip pull requests (they are also returned by listForRepo)
|
||||||
|
if (issue.pull_request) continue;
|
||||||
// if the issue hasn't been updated in the past week, close it
|
|
||||||
if (now - lastUpdated > inactivePeriod && issue.labels.length === 0) {
|
// skip labeled issues
|
||||||
console.log(`Closing inactive issue: ${issue.number} (No labels)`);
|
if (issue.labels.length > 0) {
|
||||||
|
console.log(`Skipping issue #${issue.number} (Has labels).`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch comments for this issue
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
per_page: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// determine the "last activity" time
|
||||||
|
let lastActivityTime;
|
||||||
|
if (comments.length > 0) {
|
||||||
|
const lastComment = comments[comments.length - 1];
|
||||||
|
lastActivityTime = new Date(lastComment.updated_at).getTime();
|
||||||
|
} else {
|
||||||
|
lastActivityTime = new Date(issue.created_at).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check inactivity
|
||||||
|
if (now - lastActivityTime > inactivePeriod) {
|
||||||
|
console.log(`Closing inactive issue: #${issue.number} (No recent replies for 7 days)`);
|
||||||
await github.rest.issues.update({
|
await github.rest.issues.update({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
issue_number: issue.number,
|
issue_number: issue.number,
|
||||||
state: 'closed',
|
state: 'closed',
|
||||||
});
|
});
|
||||||
} else if (issue.labels.length === 0) {
|
|
||||||
console.log(`Skipping issue ${issue.number} (No labels) as it has been recently updated.`);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`Skipping issue ${issue.number} (Has labels).`);
|
console.log(`Skipping issue #${issue.number} (Active within 7 days).`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
.github/workflows/update-pages.yml
vendored
3
.github/workflows/update-pages.yml
vendored
@@ -15,8 +15,9 @@ jobs:
|
|||||||
- name: Set version number
|
- name: Set version number
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
|
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||||
VERSION_NUM="${GITHUB_REF##*/}"
|
VERSION_NUM="${GITHUB_REF##*/}"
|
||||||
VERSION_NUM="${VERSION_NUM#v}"
|
VERSION_NUM="${VERSION_NUM#v}-${SHORT_SHA}"
|
||||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[]()
|
[]()
|
||||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||||
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
||||||
[](https://github.com/kunkundi/crossdesk/actions)
|
[](https://github.com/kunkundi/crossdesk/actions)
|
||||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
@@ -65,7 +65,7 @@ xmake r crossdesk
|
|||||||
|
|
||||||
### 无 CUDA 环境下的开发支持
|
### 无 CUDA 环境下的开发支持
|
||||||
|
|
||||||
对于未安装 **CUDA 环境** 的 Linux 开发者,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
|
对于**未安装 CUDA 环境的 Linux 开发者**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
|
||||||
|
|
||||||
进入容器,下载工程后执行:
|
进入容器,下载工程后执行:
|
||||||
```
|
```
|
||||||
@@ -75,7 +75,7 @@ export XMAKE_GLOBALDIR=/data
|
|||||||
xmake b --root -vy crossdesk
|
xmake b --root -vy crossdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
对于未安装 **CUDA 环境** 的 Windows 开发者,执行下面的命令安装 CUDA 编译环境:
|
对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 CUDA 编译环境:
|
||||||
```
|
```
|
||||||
xmake require -vy "cuda 12.6.3"
|
xmake require -vy "cuda 12.6.3"
|
||||||
```
|
```
|
||||||
@@ -295,3 +295,6 @@ Generation complete. Deployment files::
|
|||||||
|
|
||||||
7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br>
|
7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br>
|
||||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||||
|
|
||||||
|
# 常见问题
|
||||||
|
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
[]()
|
[]()
|
||||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||||
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
||||||
[](https://github.com/kunkundi/crossdesk/actions)
|
[](https://github.com/kunkundi/crossdesk/actions)
|
||||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
@@ -65,7 +65,7 @@ xmake r crossdesk
|
|||||||
|
|
||||||
#### Development Without CUDA Environment
|
#### Development Without CUDA Environment
|
||||||
|
|
||||||
For **Linux** developers who do not have a **CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
|
For **Linux developers who do not have a CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
|
||||||
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
|
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
|
||||||
|
|
||||||
After entering the container, download the project and run:
|
After entering the container, download the project and run:
|
||||||
@@ -76,7 +76,7 @@ export XMAKE_GLOBALDIR=/data
|
|||||||
xmake b --root -vy crossdesk
|
xmake b --root -vy crossdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
For **Windows** developers without a **CUDA environment** installed, run the following command to install the CUDA build environment:
|
For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment:
|
||||||
```
|
```
|
||||||
xmake require -vy "cuda 12.6.3"
|
xmake require -vy "cuda 12.6.3"
|
||||||
```
|
```
|
||||||
@@ -300,3 +300,6 @@ Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/y
|
|||||||
|
|
||||||
4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
|
4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
|
||||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
|
||||||
33
docs/FAQ.md
Normal file
33
docs/FAQ.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 常见问题(FAQ)
|
||||||
|
|
||||||
|
欢迎来到 **CrossDesk 常见问题** 页面!
|
||||||
|
这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q1. 对等连接失败
|
||||||
|
**A:**
|
||||||
|
打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。
|
||||||
|
|
||||||
|
<img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" />
|
||||||
|
|
||||||
|
由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8)
|
||||||
|
|
||||||
|
### Q2. Windows 无 CUDA 环境下编译
|
||||||
|
**A:**
|
||||||
|
运行下面的命令安装 CUDA 编译环境。
|
||||||
|
```
|
||||||
|
xmake require -vy "cuda 12.6.3"
|
||||||
|
```
|
||||||
|
安装完成后执行
|
||||||
|
```
|
||||||
|
xmake require --info "cuda 12.6.3"
|
||||||
|
```
|
||||||
|
输出如下
|
||||||
|
|
||||||
|
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||||
|
|
||||||
|
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir,重新执行 xmake b -vy crossdesk 即可。
|
||||||
|
[Issue #6](https://github.com/kunkundi/crossdesk/issues/6)
|
||||||
|
|
||||||
|
---
|
||||||
@@ -44,6 +44,9 @@ int ConfigCenter::Load() {
|
|||||||
enable_self_hosted_ =
|
enable_self_hosted_ =
|
||||||
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||||
|
|
||||||
|
enable_minimize_to_tray_ = ini_.GetBoolValue(
|
||||||
|
section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +65,8 @@ int ConfigCenter::Save() {
|
|||||||
ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_));
|
ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_));
|
||||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||||
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||||
|
ini_.SetBoolValue(section_, "enable_minimize_to_tray",
|
||||||
|
enable_minimize_to_tray_);
|
||||||
|
|
||||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
@@ -186,6 +191,11 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) {
|
||||||
|
enable_minimize_to_tray_ = enable_minimize_to_tray;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// getters
|
// getters
|
||||||
|
|
||||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
|
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
|
||||||
@@ -226,4 +236,6 @@ std::string ConfigCenter::GetDefaultCertFilePath() const {
|
|||||||
return cert_file_path_default_;
|
return cert_file_path_default_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
|
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
|
||||||
|
|
||||||
|
bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; }
|
||||||
@@ -36,6 +36,7 @@ class ConfigCenter {
|
|||||||
int SetServerPort(int server_port);
|
int SetServerPort(int server_port);
|
||||||
int SetCertFilePath(const std::string& cert_file_path);
|
int SetCertFilePath(const std::string& cert_file_path);
|
||||||
int SetSelfHosted(bool enable_self_hosted);
|
int SetSelfHosted(bool enable_self_hosted);
|
||||||
|
int SetMinimizeToTray(bool enable_minimize_to_tray);
|
||||||
|
|
||||||
// read config
|
// read config
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ class ConfigCenter {
|
|||||||
int GetDefaultServerPort() const;
|
int GetDefaultServerPort() const;
|
||||||
std::string GetDefaultCertFilePath() const;
|
std::string GetDefaultCertFilePath() const;
|
||||||
bool IsSelfHosted() const;
|
bool IsSelfHosted() const;
|
||||||
|
bool IsMinimizeToTray() const;
|
||||||
|
|
||||||
int Load();
|
int Load();
|
||||||
int Save();
|
int Save();
|
||||||
@@ -76,6 +78,7 @@ class ConfigCenter {
|
|||||||
int server_port_default_ = 9099;
|
int server_port_default_ = 9099;
|
||||||
std::string cert_file_path_default_ = "";
|
std::string cert_file_path_default_ = "";
|
||||||
bool enable_self_hosted_ = false;
|
bool enable_self_hosted_ = false;
|
||||||
|
bool enable_minimize_to_tray_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,13 @@
|
|||||||
#define INPUT_WINDOW_PADDING_EN 96
|
#define INPUT_WINDOW_PADDING_EN 96
|
||||||
#define SETTINGS_WINDOW_WIDTH_CN 202
|
#define SETTINGS_WINDOW_WIDTH_CN 202
|
||||||
#define SETTINGS_WINDOW_WIDTH_EN 248
|
#define SETTINGS_WINDOW_WIDTH_EN 248
|
||||||
|
#if _WIN32
|
||||||
|
#define SETTINGS_WINDOW_HEIGHT_CN 345
|
||||||
|
#define SETTINGS_WINDOW_HEIGHT_EN 345
|
||||||
|
#else
|
||||||
#define SETTINGS_WINDOW_HEIGHT_CN 315
|
#define SETTINGS_WINDOW_HEIGHT_CN 315
|
||||||
#define SETTINGS_WINDOW_HEIGHT_EN 315
|
#define SETTINGS_WINDOW_HEIGHT_EN 315
|
||||||
|
#endif
|
||||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
|
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
|
||||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
|
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
|
||||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 165
|
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 165
|
||||||
@@ -42,6 +47,8 @@
|
|||||||
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
|
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
|
||||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
|
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
|
||||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
|
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
|
||||||
|
#define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171
|
||||||
|
#define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218
|
||||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
|
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
|
||||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN 137
|
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN 137
|
||||||
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90
|
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
namespace localization {
|
namespace localization {
|
||||||
|
|
||||||
static std::vector<std::string> local_desktop = {
|
static std::vector<std::string> local_desktop = {
|
||||||
@@ -155,6 +159,12 @@ static std::vector<std::string> version = {
|
|||||||
static std::vector<std::string> confirm_delete_connection = {
|
static std::vector<std::string> confirm_delete_connection = {
|
||||||
reinterpret_cast<const char*>(u8"确认删除此连接"),
|
reinterpret_cast<const char*>(u8"确认删除此连接"),
|
||||||
"Confirm to delete this connection"};
|
"Confirm to delete this connection"};
|
||||||
} // namespace localization
|
#if _WIN32
|
||||||
|
|
||||||
|
static std::vector<std::string> minimize_to_tray = {
|
||||||
|
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
|
||||||
|
"Minimize to system tray when exit:"};
|
||||||
|
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
|
||||||
|
#endif
|
||||||
|
} // namespace localization
|
||||||
#endif
|
#endif
|
||||||
@@ -588,6 +588,17 @@ int Render::CreateMainWindow() {
|
|||||||
// for window region action
|
// for window region action
|
||||||
SDL_SetWindowHitTest(main_window_, HitTestCallback, this);
|
SDL_SetWindowHitTest(main_window_, HitTestCallback, this);
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
SDL_PropertiesID props = SDL_GetWindowProperties(main_window_);
|
||||||
|
HWND main_hwnd = (HWND)SDL_GetPointerProperty(
|
||||||
|
props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
||||||
|
|
||||||
|
HICON tray_icon = (HICON)LoadImageW(NULL, L"crossdesk.ico", IMAGE_ICON, 0, 0,
|
||||||
|
LR_LOADFROMFILE | LR_DEFAULTSIZE);
|
||||||
|
tray_ = std::make_unique<WinTray>(main_hwnd, tray_icon, L"CrossDesk",
|
||||||
|
localization_language_index_);
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,6 +979,14 @@ void Render::MainLoop() {
|
|||||||
ProcessSdlEvent(event);
|
ProcessSdlEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
MSG msg;
|
||||||
|
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
UpdateLabels();
|
UpdateLabels();
|
||||||
HandleRecentConnections();
|
HandleRecentConnections();
|
||||||
HandleStreamWindow();
|
HandleStreamWindow();
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
#include "screen_capturer_factory.h"
|
#include "screen_capturer_factory.h"
|
||||||
#include "speaker_capturer_factory.h"
|
#include "speaker_capturer_factory.h"
|
||||||
#include "thumbnail.h"
|
#include "thumbnail.h"
|
||||||
|
#if _WIN32
|
||||||
|
#include "win_tray.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class Render {
|
class Render {
|
||||||
public:
|
public:
|
||||||
@@ -298,6 +301,9 @@ class Render {
|
|||||||
ImGuiContext* main_ctx_ = nullptr;
|
ImGuiContext* main_ctx_ = nullptr;
|
||||||
bool exit_ = false;
|
bool exit_ = false;
|
||||||
const int sdl_refresh_ms_ = 16; // ~60 FPS
|
const int sdl_refresh_ms_ = 16; // ~60 FPS
|
||||||
|
#if _WIN32
|
||||||
|
std::unique_ptr<WinTray> tray_;
|
||||||
|
#endif
|
||||||
|
|
||||||
// main window properties
|
// main window properties
|
||||||
bool start_mouse_controller_ = false;
|
bool start_mouse_controller_ = false;
|
||||||
@@ -335,8 +341,8 @@ class Render {
|
|||||||
float connection_status_window_height_ = 150;
|
float connection_status_window_height_ = 150;
|
||||||
float notification_window_width_ = 200;
|
float notification_window_width_ = 200;
|
||||||
float notification_window_height_ = 80;
|
float notification_window_height_ = 80;
|
||||||
float about_window_width_ = 200;
|
float about_window_width_ = 300;
|
||||||
float about_window_height_ = 150;
|
float about_window_height_ = 170;
|
||||||
int screen_width_ = 1280;
|
int screen_width_ = 1280;
|
||||||
int screen_height_ = 720;
|
int screen_height_ = 720;
|
||||||
int selected_display_ = 0;
|
int selected_display_ = 0;
|
||||||
@@ -444,6 +450,8 @@ class Render {
|
|||||||
bool enable_hardware_video_codec_last_ = false;
|
bool enable_hardware_video_codec_last_ = false;
|
||||||
bool enable_turn_last_ = false;
|
bool enable_turn_last_ = false;
|
||||||
bool enable_srtp_last_ = false;
|
bool enable_srtp_last_ = false;
|
||||||
|
bool enable_minimize_to_tray_ = false;
|
||||||
|
bool enable_minimize_to_tray_last_ = false;
|
||||||
char signal_server_ip_tmp_[256] = "api.crossdesk.cn";
|
char signal_server_ip_tmp_[256] = "api.crossdesk.cn";
|
||||||
char signal_server_port_tmp_[6] = "9099";
|
char signal_server_port_tmp_[6] = "9099";
|
||||||
bool settings_window_pos_reset_ = true;
|
bool settings_window_pos_reset_ = true;
|
||||||
|
|||||||
@@ -139,9 +139,17 @@ int Render::TitleBar(bool main_window) {
|
|||||||
float xmark_size = 12.0f;
|
float xmark_size = 12.0f;
|
||||||
std::string close_button = "##xmark"; // ICON_FA_XMARK;
|
std::string close_button = "##xmark"; // ICON_FA_XMARK;
|
||||||
if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) {
|
if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) {
|
||||||
SDL_Event event;
|
#if _WIN32
|
||||||
event.type = SDL_EVENT_QUIT;
|
if (enable_minimize_to_tray_) {
|
||||||
SDL_PushEvent(&event);
|
tray_->MinimizeToTray();
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
|
SDL_Event event;
|
||||||
|
event.type = SDL_EVENT_QUIT;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
#if _WIN32
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
|
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
|
||||||
xmark_pos_y - xmark_size / 2 + 0.75f),
|
xmark_pos_y - xmark_size / 2 + 0.75f),
|
||||||
|
|||||||
112
src/gui/tray/win_tray.cpp
Normal file
112
src/gui/tray/win_tray.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include "win_tray.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include "localization.h"
|
||||||
|
|
||||||
|
// callback for the message-only window that handles tray icon messages
|
||||||
|
static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
WinTray* tray =
|
||||||
|
reinterpret_cast<WinTray*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||||
|
if (!tray) {
|
||||||
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg == WM_TRAY_CALLBACK) {
|
||||||
|
MSG tmpMsg = {};
|
||||||
|
tmpMsg.message = msg;
|
||||||
|
tmpMsg.wParam = wParam;
|
||||||
|
tmpMsg.lParam = lParam;
|
||||||
|
tray->HandleTrayMessage(&tmpMsg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
WinTray::WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
|
||||||
|
int language_index)
|
||||||
|
: app_hwnd_(app_hwnd),
|
||||||
|
icon_(icon),
|
||||||
|
tip_(tooltip),
|
||||||
|
hwnd_message_only_(nullptr),
|
||||||
|
language_index_(language_index) {
|
||||||
|
WNDCLASS wc = {};
|
||||||
|
wc.lpfnWndProc = MsgWndProc;
|
||||||
|
wc.hInstance = GetModuleHandle(nullptr);
|
||||||
|
wc.lpszClassName = L"TrayMessageWindow";
|
||||||
|
RegisterClass(&wc);
|
||||||
|
|
||||||
|
// create a message-only window to receive tray messages
|
||||||
|
hwnd_message_only_ =
|
||||||
|
CreateWindowEx(0, wc.lpszClassName, L"TrayMsg", 0, 0, 0, 0, 0,
|
||||||
|
HWND_MESSAGE, nullptr, wc.hInstance, nullptr);
|
||||||
|
|
||||||
|
// store pointer to this WinTray instance in window data
|
||||||
|
SetWindowLongPtr(hwnd_message_only_, GWLP_USERDATA,
|
||||||
|
reinterpret_cast<LONG_PTR>(this));
|
||||||
|
|
||||||
|
// initialize NOTIFYICONDATA structure
|
||||||
|
ZeroMemory(&nid_, sizeof(nid_));
|
||||||
|
nid_.cbSize = sizeof(nid_);
|
||||||
|
nid_.hWnd = hwnd_message_only_;
|
||||||
|
nid_.uID = 1;
|
||||||
|
nid_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||||
|
nid_.uCallbackMessage = WM_TRAY_CALLBACK;
|
||||||
|
nid_.hIcon = icon_;
|
||||||
|
wcsncpy_s(nid_.szTip, tip_.c_str(), _TRUNCATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
WinTray::~WinTray() {
|
||||||
|
RemoveTrayIcon();
|
||||||
|
if (hwnd_message_only_) DestroyWindow(hwnd_message_only_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinTray::MinimizeToTray() {
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &nid_);
|
||||||
|
// hide application window
|
||||||
|
ShowWindow(app_hwnd_, SW_HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinTray::RemoveTrayIcon() { Shell_NotifyIcon(NIM_DELETE, &nid_); }
|
||||||
|
|
||||||
|
bool WinTray::HandleTrayMessage(MSG* msg) {
|
||||||
|
if (!msg || msg->message != WM_TRAY_CALLBACK) return false;
|
||||||
|
|
||||||
|
switch (LOWORD(msg->lParam)) {
|
||||||
|
case WM_LBUTTONDBLCLK:
|
||||||
|
case WM_LBUTTONUP: {
|
||||||
|
ShowWindow(app_hwnd_, SW_SHOW);
|
||||||
|
SetForegroundWindow(app_hwnd_);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_RBUTTONUP: {
|
||||||
|
POINT pt;
|
||||||
|
GetCursorPos(&pt);
|
||||||
|
HMENU menu = CreatePopupMenu();
|
||||||
|
AppendMenuW(menu, MF_STRING, 1001,
|
||||||
|
localization::exit_program[language_index_]);
|
||||||
|
|
||||||
|
SetForegroundWindow(hwnd_message_only_);
|
||||||
|
int cmd =
|
||||||
|
TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_LEFTALIGN,
|
||||||
|
pt.x, pt.y, 0, hwnd_message_only_, nullptr);
|
||||||
|
DestroyMenu(menu);
|
||||||
|
|
||||||
|
// handle menu command
|
||||||
|
if (cmd == 1001) {
|
||||||
|
// exit application
|
||||||
|
SDL_Event event;
|
||||||
|
event.type = SDL_EVENT_QUIT;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
} else if (cmd == 1002) {
|
||||||
|
ShowWindow(app_hwnd_, SW_SHOW); // show main window
|
||||||
|
SetForegroundWindow(app_hwnd_);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
36
src/gui/tray/win_tray.h
Normal file
36
src/gui/tray/win_tray.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-10-22
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _WIN_TRAY_H_
|
||||||
|
#define _WIN_TRAY_H_
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define WM_TRAY_CALLBACK (WM_USER + 1)
|
||||||
|
|
||||||
|
class WinTray {
|
||||||
|
public:
|
||||||
|
WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
|
||||||
|
int language_index);
|
||||||
|
~WinTray();
|
||||||
|
|
||||||
|
void MinimizeToTray();
|
||||||
|
void RemoveTrayIcon();
|
||||||
|
bool HandleTrayMessage(MSG* msg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
HWND app_hwnd_;
|
||||||
|
HWND hwnd_message_only_;
|
||||||
|
HICON icon_;
|
||||||
|
std::wstring tip_;
|
||||||
|
int language_index_;
|
||||||
|
NOTIFYICONDATA nid_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -27,15 +27,21 @@ int Render::AboutWindow() {
|
|||||||
ImGui::SetWindowFontScale(0.5f);
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
|
|
||||||
std::string version;
|
std::string version;
|
||||||
#ifdef RD_VERSION
|
#ifdef CROSSDESK_VERSION
|
||||||
version = RD_VERSION;
|
version = CROSSDESK_VERSION;
|
||||||
#else
|
#else
|
||||||
version = "Unknown";
|
version = "Unknown";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string text =
|
std::string text = localization::version[localization_language_index_] +
|
||||||
localization::version[localization_language_index_] + ": " + version;
|
": CrossDesk v" + version;
|
||||||
ImGui::Text("%s", text.c_str());
|
ImGui::Text("%s", text.c_str());
|
||||||
|
ImGui::Text("");
|
||||||
|
|
||||||
|
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
|
||||||
|
std::string license_text = "Licensed under GNU LGPL v3.";
|
||||||
|
ImGui::Text("%s", copyright_text.c_str());
|
||||||
|
ImGui::Text("%s", license_text.c_str());
|
||||||
|
|
||||||
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
|
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
|
||||||
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
|
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ int Render::SettingWindow() {
|
|||||||
localization::language_en[localization_language_index_].c_str()};
|
localization::language_en[localization_language_index_].c_str()};
|
||||||
|
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
"%s", localization::language[localization_language_index_].c_str());
|
"%s", localization::language[localization_language_index_].c_str());
|
||||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||||
@@ -88,7 +88,7 @@ int Render::SettingWindow() {
|
|||||||
.c_str()};
|
.c_str()};
|
||||||
|
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
"%s",
|
"%s",
|
||||||
localization::video_quality[localization_language_index_].c_str());
|
localization::video_quality[localization_language_index_].c_str());
|
||||||
@@ -111,7 +111,7 @@ int Render::SettingWindow() {
|
|||||||
const char* video_frame_rate_items[] = {"30 fps", "60 fps"};
|
const char* video_frame_rate_items[] = {"30 fps", "60 fps"};
|
||||||
|
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text("%s",
|
ImGui::Text("%s",
|
||||||
localization::video_frame_rate[localization_language_index_]
|
localization::video_frame_rate[localization_language_index_]
|
||||||
.c_str());
|
.c_str());
|
||||||
@@ -137,7 +137,7 @@ int Render::SettingWindow() {
|
|||||||
localization::av1[localization_language_index_].c_str()};
|
localization::av1[localization_language_index_].c_str()};
|
||||||
|
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
"%s",
|
"%s",
|
||||||
localization::video_encode_format[localization_language_index_]
|
localization::video_encode_format[localization_language_index_]
|
||||||
@@ -160,7 +160,7 @@ int Render::SettingWindow() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text("%s", localization::enable_hardware_video_codec
|
ImGui::Text("%s", localization::enable_hardware_video_codec
|
||||||
[localization_language_index_]
|
[localization_language_index_]
|
||||||
.c_str());
|
.c_str());
|
||||||
@@ -179,7 +179,7 @@ int Render::SettingWindow() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
"%s",
|
"%s",
|
||||||
localization::enable_turn[localization_language_index_].c_str());
|
localization::enable_turn[localization_language_index_].c_str());
|
||||||
@@ -197,7 +197,7 @@ int Render::SettingWindow() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
ImGui::Text(
|
ImGui::Text(
|
||||||
"%s",
|
"%s",
|
||||||
localization::enable_srtp[localization_language_index_].c_str());
|
localization::enable_srtp[localization_language_index_].c_str());
|
||||||
@@ -215,7 +215,7 @@ int Render::SettingWindow() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
settings_items_offset += settings_items_padding;
|
settings_items_offset += settings_items_padding;
|
||||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
ImGui::SetCursorPosY(settings_items_offset + 1);
|
||||||
|
|
||||||
if (ImGui::Button(localization::self_hosted_server_config
|
if (ImGui::Button(localization::self_hosted_server_config
|
||||||
[localization_language_index_]
|
[localization_language_index_]
|
||||||
@@ -232,7 +232,27 @@ int Render::SettingWindow() {
|
|||||||
ImGui::Checkbox("##enable_self_hosted_server",
|
ImGui::Checkbox("##enable_self_hosted_server",
|
||||||
&enable_self_hosted_server_);
|
&enable_self_hosted_server_);
|
||||||
}
|
}
|
||||||
|
#if _WIN32
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
{
|
||||||
|
settings_items_offset += settings_items_padding;
|
||||||
|
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||||
|
|
||||||
|
ImGui::Text("%s",
|
||||||
|
localization::minimize_to_tray[localization_language_index_]
|
||||||
|
.c_str());
|
||||||
|
|
||||||
|
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||||
|
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_CN);
|
||||||
|
} else {
|
||||||
|
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_EN);
|
||||||
|
}
|
||||||
|
ImGui::SetCursorPosY(settings_items_offset);
|
||||||
|
ImGui::Checkbox("##enable_minimize_to_tray_",
|
||||||
|
&enable_minimize_to_tray_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (stream_window_inited_) {
|
if (stream_window_inited_) {
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
static std::vector<DisplayInfo> gs_display_list;
|
static std::vector<DisplayInfo> gs_display_list;
|
||||||
|
|
||||||
std::string WideToUtf8(const wchar_t* wideStr) {
|
std::string WideToUtf8(const wchar_t *wideStr) {
|
||||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
|
||||||
nullptr, nullptr);
|
nullptr, nullptr);
|
||||||
std::string result(size_needed, 0);
|
std::string result(size_needed, 0);
|
||||||
@@ -31,14 +31,14 @@ BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
|
|||||||
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
|
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
|
||||||
gs_display_list.insert(
|
gs_display_list.insert(
|
||||||
gs_display_list.begin(),
|
gs_display_list.begin(),
|
||||||
{(void*)hmonitor, WideToUtf8(monitor_info_.szDevice),
|
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
|
||||||
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
||||||
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
||||||
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
|
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
|
||||||
*(HMONITOR*)data = hmonitor;
|
*(HMONITOR *)data = hmonitor;
|
||||||
} else {
|
} else {
|
||||||
gs_display_list.push_back(DisplayInfo(
|
gs_display_list.push_back(DisplayInfo(
|
||||||
(void*)hmonitor, WideToUtf8(monitor_info_.szDevice),
|
(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
|
||||||
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
||||||
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
||||||
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
|
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
|
||||||
@@ -81,7 +81,7 @@ bool ScreenCapturerWgc::IsWgcSupported() {
|
|||||||
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
|
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
|
||||||
return winrt::Windows::Foundation::Metadata::ApiInformation::
|
return winrt::Windows::Foundation::Metadata::ApiInformation::
|
||||||
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
|
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
|
||||||
} catch (const winrt::hresult_error&) {
|
} catch (const winrt::hresult_error &) {
|
||||||
return false;
|
return false;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return false;
|
return false;
|
||||||
@@ -115,7 +115,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < display_info_list_.size(); i++) {
|
for (int i = 0; i < display_info_list_.size(); i++) {
|
||||||
const auto& display = display_info_list_[i];
|
const auto &display = display_info_list_[i];
|
||||||
LOG_INFO(
|
LOG_INFO(
|
||||||
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
|
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
|
||||||
"({}, {})",
|
"({}, {})",
|
||||||
@@ -243,28 +243,26 @@ int ScreenCapturerWgc::SwitchTo(int monitor_index) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame,
|
||||||
int id) {
|
int id) {
|
||||||
if (!on_data_) {
|
if (on_data_) {
|
||||||
return;
|
if (!nv12_frame_) {
|
||||||
|
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4,
|
||||||
|
(uint8_t *)nv12_frame_, frame.width,
|
||||||
|
(uint8_t *)(nv12_frame_ + frame.width * frame.height),
|
||||||
|
frame.width, frame.width, frame.height);
|
||||||
|
|
||||||
|
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
|
||||||
|
frame.height, display_info_list_[id].name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nv12_frame_) {
|
|
||||||
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
libyuv::ARGBToNV12((const uint8_t*)frame.data, frame.width * 4,
|
|
||||||
(uint8_t*)nv12_frame_, frame.width,
|
|
||||||
(uint8_t*)(nv12_frame_ + frame.width * frame.height),
|
|
||||||
frame.width, frame.width, frame.height);
|
|
||||||
|
|
||||||
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
|
|
||||||
frame.height, display_info_list_[id].name.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenCapturerWgc::CleanUp() {
|
void ScreenCapturerWgc::CleanUp() {
|
||||||
if (inited_) {
|
if (inited_) {
|
||||||
for (auto& session : sessions_) {
|
for (auto &session : sessions_) {
|
||||||
if (session.session_) {
|
if (session.session_) {
|
||||||
session.session_->Stop();
|
session.session_->Stop();
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,6 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
|||||||
std::vector<DisplayInfo> display_info_list_;
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
int monitor_index_ = 0;
|
int monitor_index_ = 0;
|
||||||
|
|
||||||
HWND hwnd_ = nullptr;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class WgcSessionInfo {
|
class WgcSessionInfo {
|
||||||
public:
|
public:
|
||||||
@@ -65,9 +63,6 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
|||||||
|
|
||||||
unsigned char* nv12_frame_ = nullptr;
|
unsigned char* nv12_frame_ = nullptr;
|
||||||
unsigned char* nv12_frame_scaled_ = nullptr;
|
unsigned char* nv12_frame_scaled_ = nullptr;
|
||||||
|
|
||||||
private:
|
|
||||||
bool CreateHiddenWindow();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#ifndef _SCREEN_CAPTURER_WGC_H_
|
|
||||||
#define _SCREEN_CAPTURER_WGC_H_
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "screen_capturer.h"
|
|
||||||
#include "wgc_session.h"
|
|
||||||
#include "wgc_session_impl.h"
|
|
||||||
|
|
||||||
class ScreenCapturerWgc : public ScreenCapturer,
|
|
||||||
public WgcSession::wgc_session_observer {
|
|
||||||
public:
|
|
||||||
ScreenCapturerWgc();
|
|
||||||
~ScreenCapturerWgc();
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool IsWgcSupported();
|
|
||||||
|
|
||||||
int Init(const int fps, cb_desktop_data cb) override;
|
|
||||||
int Destroy() override;
|
|
||||||
int Start() override;
|
|
||||||
int Stop() override;
|
|
||||||
|
|
||||||
int Pause(int monitor_index) override;
|
|
||||||
int Resume(int monitor_index) override;
|
|
||||||
|
|
||||||
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
|
|
||||||
|
|
||||||
int SwitchTo(int monitor_index);
|
|
||||||
|
|
||||||
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void CleanUp();
|
|
||||||
|
|
||||||
private:
|
|
||||||
HMONITOR monitor_;
|
|
||||||
MONITORINFOEX monitor_info_;
|
|
||||||
std::vector<DisplayInfo> display_info_list_;
|
|
||||||
int monitor_index_ = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
class WgcSessionInfo {
|
|
||||||
public:
|
|
||||||
std::unique_ptr<WgcSession> session_;
|
|
||||||
bool inited_ = false;
|
|
||||||
bool running_ = false;
|
|
||||||
bool paused_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<WgcSessionInfo> sessions_;
|
|
||||||
|
|
||||||
std::atomic_bool running_;
|
|
||||||
std::atomic_bool inited_;
|
|
||||||
|
|
||||||
int fps_ = 60;
|
|
||||||
|
|
||||||
cb_desktop_data on_data_ = nullptr;
|
|
||||||
|
|
||||||
unsigned char* nv12_frame_ = nullptr;
|
|
||||||
unsigned char* nv12_frame_scaled_ = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
#include <Windows.h>
|
|
||||||
#include <d3d11_4.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "libyuv.h"
|
|
||||||
#include "rd_log.h"
|
|
||||||
#include "screen_capturer_wgc.h"
|
|
||||||
|
|
||||||
// Dummy window proc for hidden window
|
|
||||||
static LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
|
||||||
LPARAM lParam) {
|
|
||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 构造函数 / 析构函数 =======================
|
|
||||||
ScreenCapturerWgc::ScreenCapturerWgc()
|
|
||||||
: monitor_(nullptr),
|
|
||||||
hwnd_(nullptr),
|
|
||||||
monitor_index_(0),
|
|
||||||
running_(false),
|
|
||||||
inited_(false),
|
|
||||||
fps_(60),
|
|
||||||
on_data_(nullptr),
|
|
||||||
nv12_frame_(nullptr),
|
|
||||||
nv12_frame_scaled_(nullptr) {}
|
|
||||||
|
|
||||||
ScreenCapturerWgc::~ScreenCapturerWgc() {
|
|
||||||
Stop();
|
|
||||||
CleanUp();
|
|
||||||
|
|
||||||
if (nv12_frame_) {
|
|
||||||
delete[] nv12_frame_;
|
|
||||||
nv12_frame_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nv12_frame_scaled_) {
|
|
||||||
delete[] nv12_frame_scaled_;
|
|
||||||
nv12_frame_scaled_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 隐藏窗口创建 =======================
|
|
||||||
bool ScreenCapturerWgc::CreateHiddenWindow() {
|
|
||||||
const wchar_t kClassName[] = L"ScreenCapturerHiddenWindow";
|
|
||||||
|
|
||||||
WNDCLASSW wc = {};
|
|
||||||
wc.lpfnWndProc = DummyWndProc;
|
|
||||||
wc.hInstance = GetModuleHandle(nullptr);
|
|
||||||
wc.lpszClassName = kClassName;
|
|
||||||
|
|
||||||
if (!RegisterClassW(&wc)) {
|
|
||||||
std::cerr << "Failed to register dummy window class\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hwnd_ = CreateWindowW(kClassName, L"", WS_OVERLAPPEDWINDOW, 0, 0, 1, 1,
|
|
||||||
nullptr, nullptr, wc.hInstance, nullptr);
|
|
||||||
if (!hwnd_) {
|
|
||||||
std::cerr << "Failed to create dummy window\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowWindow(hwnd_, SW_HIDE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 初始化 =======================
|
|
||||||
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
|
|
||||||
if (inited_) return 0;
|
|
||||||
|
|
||||||
fps_ = fps;
|
|
||||||
on_data_ = cb;
|
|
||||||
|
|
||||||
// 创建隐藏窗口
|
|
||||||
if (!CreateHiddenWindow()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 WGC Session
|
|
||||||
sessions_.push_back(
|
|
||||||
{std::make_unique<WgcSessionImpl>(0), false, false, false});
|
|
||||||
sessions_.back().session_->RegisterObserver(this);
|
|
||||||
int error = sessions_.back().session_->Initialize(hwnd_);
|
|
||||||
if (error != 0) {
|
|
||||||
std::cerr << "WGC session init failed\n";
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
sessions_[0].inited_ = true;
|
|
||||||
|
|
||||||
inited_ = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ScreenCapturerWgc::Destroy() {
|
|
||||||
Stop();
|
|
||||||
CleanUp();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ScreenCapturerWgc::Pause(int monitor_index) {
|
|
||||||
// 目前只支持隐藏窗口,所以忽略 monitor_index
|
|
||||||
if (!running_) return -1;
|
|
||||||
sessions_[0].session_->Pause();
|
|
||||||
sessions_[0].paused_ = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ScreenCapturerWgc::Resume(int monitor_index) {
|
|
||||||
if (!running_) return -1;
|
|
||||||
sessions_[0].session_->Resume();
|
|
||||||
sessions_[0].paused_ = false;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ScreenCapturerWgc::SwitchTo(int monitor_index) {
|
|
||||||
// 单隐藏窗口模式,不支持切换
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 开始 =======================
|
|
||||||
int ScreenCapturerWgc::Start() {
|
|
||||||
if (!inited_) return -1;
|
|
||||||
if (running_) return 0;
|
|
||||||
if (sessions_.empty()) return -1;
|
|
||||||
|
|
||||||
sessions_[0].session_->Start();
|
|
||||||
sessions_[0].running_ = true;
|
|
||||||
running_ = true;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 停止 =======================
|
|
||||||
int ScreenCapturerWgc::Stop() {
|
|
||||||
if (!running_) return 0;
|
|
||||||
|
|
||||||
if (!sessions_.empty()) {
|
|
||||||
sessions_[0].session_->Stop();
|
|
||||||
sessions_[0].running_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
running_ = false;
|
|
||||||
|
|
||||||
if (hwnd_) {
|
|
||||||
DestroyWindow(hwnd_);
|
|
||||||
hwnd_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
inited_ = false;
|
|
||||||
sessions_.clear();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 帧回调 =======================
|
|
||||||
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
|
||||||
int id) {
|
|
||||||
if (!on_data_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nv12_frame_) {
|
|
||||||
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
libyuv::ARGBToNV12((const uint8_t*)frame.data, frame.width * 4,
|
|
||||||
(uint8_t*)nv12_frame_, frame.width,
|
|
||||||
(uint8_t*)(nv12_frame_ + frame.width * frame.height),
|
|
||||||
frame.width, frame.width, frame.height);
|
|
||||||
|
|
||||||
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
|
|
||||||
frame.height, "hidden_window");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================= 清理 =======================
|
|
||||||
void ScreenCapturerWgc::CleanUp() {
|
|
||||||
if (inited_) {
|
|
||||||
for (auto& session : sessions_) {
|
|
||||||
if (session.session_) {
|
|
||||||
session.session_->Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sessions_.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,6 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "rd_log.h"
|
|
||||||
|
|
||||||
#define CHECK_INIT \
|
#define CHECK_INIT \
|
||||||
if (!is_initialized_) { \
|
if (!is_initialized_) { \
|
||||||
std::cout << "AE_NEED_INIT" << std::endl; \
|
std::cout << "AE_NEED_INIT" << std::endl; \
|
||||||
@@ -22,118 +20,122 @@
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
|
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
|
||||||
::IDXGIDevice* dxgiDevice, ::IInspectable** graphicsDevice);
|
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
|
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
|
||||||
|
|
||||||
WgcSessionImpl::~WgcSessionImpl() {
|
WgcSessionImpl::~WgcSessionImpl() {
|
||||||
try {
|
Stop();
|
||||||
Stop();
|
CleanUp();
|
||||||
CleanUp();
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WgcSessionImpl::Release() { delete this; }
|
void WgcSessionImpl::Release() { delete this; }
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize(HWND hwnd) {
|
int WgcSessionImpl::Initialize(HWND hwnd) {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
|
|
||||||
target_.hwnd = hwnd;
|
target_.hwnd = hwnd;
|
||||||
target_.is_window = true;
|
target_.is_window = true;
|
||||||
return Initialize();
|
return Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
|
int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
|
|
||||||
target_.hmonitor = hmonitor;
|
target_.hmonitor = hmonitor;
|
||||||
target_.is_window = false;
|
target_.is_window = false;
|
||||||
return Initialize();
|
return Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
|
void WgcSessionImpl::RegisterObserver(wgc_session_observer *observer) {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
observer_ = observer;
|
observer_ = observer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Start() {
|
int WgcSessionImpl::Start() {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
CHECK_INIT;
|
|
||||||
|
|
||||||
if (is_running_) return 0;
|
if (is_running_) return 0;
|
||||||
|
|
||||||
|
int error = 1;
|
||||||
|
|
||||||
|
CHECK_INIT;
|
||||||
try {
|
try {
|
||||||
if (!capture_item_) {
|
if (!capture_session_) {
|
||||||
std::cout << "AE_NO_CAPTURE_ITEM" << std::endl;
|
auto current_size = capture_item_.Size();
|
||||||
LOG_ERROR("No capture item");
|
capture_framepool_ =
|
||||||
return 2;
|
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
|
||||||
|
CreateFreeThreaded(d3d11_direct_device_,
|
||||||
|
winrt::Windows::Graphics::DirectX::
|
||||||
|
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||||
|
2, current_size);
|
||||||
|
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
|
||||||
|
capture_frame_size_ = current_size;
|
||||||
|
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
|
||||||
|
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
|
||||||
|
capture_close_trigger_ = capture_item_.Closed(
|
||||||
|
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto current_size = capture_item_.Size();
|
if (!capture_framepool_) throw std::exception();
|
||||||
capture_framepool_ =
|
|
||||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
|
|
||||||
CreateFreeThreaded(d3d11_direct_device_,
|
|
||||||
winrt::Windows::Graphics::DirectX::
|
|
||||||
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
|
||||||
2, current_size);
|
|
||||||
|
|
||||||
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
|
|
||||||
capture_frame_size_ = current_size;
|
|
||||||
|
|
||||||
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
|
|
||||||
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
|
|
||||||
capture_close_trigger_ = capture_item_.Closed(
|
|
||||||
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
|
|
||||||
|
|
||||||
capture_session_.IsCursorCaptureEnabled(false);
|
|
||||||
capture_session_.StartCapture();
|
|
||||||
|
|
||||||
is_running_ = true;
|
is_running_ = true;
|
||||||
is_paused_ = false;
|
|
||||||
return 0;
|
// we do not need to crate a thread to enter a message loop coz we use
|
||||||
} catch (winrt::hresult_error const& e) {
|
// CreateFreeThreaded instead of Create to create a capture frame pool,
|
||||||
LOG_ERROR("Create WGC Capture Failed: {}", winrt::to_string(e.message()));
|
// we need to test the performance later
|
||||||
|
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
|
||||||
|
|
||||||
|
capture_session_.StartCapture();
|
||||||
|
|
||||||
|
capture_session_.IsCursorCaptureEnabled(false);
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
} catch (winrt::hresult_error) {
|
||||||
|
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
|
||||||
return 86;
|
return 86;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return 86;
|
return 86;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Stop() {
|
int WgcSessionImpl::Stop() {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
|
|
||||||
CHECK_INIT;
|
CHECK_INIT;
|
||||||
|
|
||||||
if (!is_running_) return 0;
|
|
||||||
is_running_ = false;
|
is_running_ = false;
|
||||||
|
|
||||||
try {
|
// if (loop_.joinable()) loop_.join();
|
||||||
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
|
|
||||||
if (capture_close_trigger_) capture_close_trigger_.revoke();
|
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
|
||||||
if (capture_session_) {
|
|
||||||
capture_session_.Close();
|
if (capture_session_) {
|
||||||
capture_session_ = nullptr;
|
capture_session_.Close();
|
||||||
}
|
capture_session_ = nullptr;
|
||||||
if (capture_framepool_) {
|
|
||||||
capture_framepool_.Close();
|
|
||||||
capture_framepool_ = nullptr;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Pause() {
|
int WgcSessionImpl::Pause() {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
CHECK_INIT;
|
|
||||||
is_paused_ = true;
|
is_paused_ = true;
|
||||||
|
|
||||||
|
CHECK_INIT;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Resume() {
|
int WgcSessionImpl::Resume() {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
CHECK_INIT;
|
|
||||||
is_paused_ = false;
|
is_paused_ = false;
|
||||||
|
|
||||||
|
CHECK_INIT;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,15 +143,19 @@ auto WgcSessionImpl::CreateD3D11Device() {
|
|||||||
winrt::com_ptr<ID3D11Device> d3d_device;
|
winrt::com_ptr<ID3D11Device> d3d_device;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
WINRT_ASSERT(!d3d_device);
|
||||||
|
|
||||||
|
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_HARDWARE;
|
||||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||||
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags,
|
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
||||||
nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(),
|
D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);
|
||||||
nullptr, nullptr);
|
|
||||||
|
|
||||||
if (DXGI_ERROR_UNSUPPORTED == hr) {
|
if (DXGI_ERROR_UNSUPPORTED == hr) {
|
||||||
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, flags,
|
// change D3D_DRIVER_TYPE
|
||||||
nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(),
|
type = D3D_DRIVER_TYPE_WARP;
|
||||||
nullptr, nullptr);
|
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
||||||
|
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
|
||||||
|
nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
winrt::check_hresult(hr);
|
winrt::check_hresult(hr);
|
||||||
@@ -162,28 +168,26 @@ auto WgcSessionImpl::CreateD3D11Device() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
|
auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
|
||||||
auto interop_factory =
|
auto activation_factory = winrt::get_activation_factory<
|
||||||
winrt::get_activation_factory<
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>()
|
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
|
||||||
.as<IGraphicsCaptureItemInterop>();
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
|
|
||||||
interop_factory->CreateForWindow(
|
interop_factory->CreateForWindow(
|
||||||
hwnd,
|
hwnd,
|
||||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
||||||
reinterpret_cast<void**>(winrt::put_abi(item)));
|
reinterpret_cast<void **>(winrt::put_abi(item)));
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
|
auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
|
||||||
auto interop_factory =
|
auto activation_factory = winrt::get_activation_factory<
|
||||||
winrt::get_activation_factory<
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>()
|
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
|
||||||
.as<IGraphicsCaptureItemInterop>();
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
|
|
||||||
interop_factory->CreateForMonitor(
|
interop_factory->CreateForMonitor(
|
||||||
hmonitor,
|
hmonitor,
|
||||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
||||||
reinterpret_cast<void**>(winrt::put_abi(item)));
|
reinterpret_cast<void **>(winrt::put_abi(item)));
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,10 +196,13 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
|
|||||||
unsigned int height) {
|
unsigned int height) {
|
||||||
D3D11_TEXTURE2D_DESC src_desc;
|
D3D11_TEXTURE2D_DESC src_desc;
|
||||||
src_texture->GetDesc(&src_desc);
|
src_texture->GetDesc(&src_desc);
|
||||||
|
D3D11_TEXTURE2D_DESC map_desc;
|
||||||
D3D11_TEXTURE2D_DESC map_desc = src_desc;
|
map_desc.Width = width == 0 ? src_desc.Width : width;
|
||||||
map_desc.Width = width ? width : src_desc.Width;
|
map_desc.Height = height == 0 ? src_desc.Height : height;
|
||||||
map_desc.Height = height ? height : src_desc.Height;
|
map_desc.MipLevels = src_desc.MipLevels;
|
||||||
|
map_desc.ArraySize = src_desc.ArraySize;
|
||||||
|
map_desc.Format = src_desc.Format;
|
||||||
|
map_desc.SampleDesc = src_desc.SampleDesc;
|
||||||
map_desc.Usage = D3D11_USAGE_STAGING;
|
map_desc.Usage = D3D11_USAGE_STAGING;
|
||||||
map_desc.BindFlags = 0;
|
map_desc.BindFlags = 0;
|
||||||
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||||
@@ -209,101 +216,163 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WgcSessionImpl::OnFrame(
|
void WgcSessionImpl::OnFrame(
|
||||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
|
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender,
|
||||||
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
|
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const &args) {
|
||||||
if (!is_running_ || is_paused_) return;
|
std::lock_guard locker(lock_);
|
||||||
std::scoped_lock locker(lock_);
|
|
||||||
|
|
||||||
if (!observer_) return;
|
auto is_new_size = false;
|
||||||
|
|
||||||
auto frame = sender.TryGetNextFrame();
|
{
|
||||||
if (!frame) return;
|
auto frame = sender.TryGetNextFrame();
|
||||||
|
auto frame_size = frame.ContentSize();
|
||||||
|
|
||||||
auto frame_size = frame.ContentSize();
|
if (frame_size.Width != capture_frame_size_.Width ||
|
||||||
bool size_changed = (frame_size.Width != capture_frame_size_.Width ||
|
frame_size.Height != capture_frame_size_.Height) {
|
||||||
frame_size.Height != capture_frame_size_.Height);
|
// The thing we have been capturing has changed size.
|
||||||
if (size_changed) {
|
// We need to resize our swap chain first, then blit the pixels.
|
||||||
capture_frame_size_ = frame_size;
|
// After we do that, retire the frame and then recreate our frame pool.
|
||||||
|
is_new_size = true;
|
||||||
|
capture_frame_size_ = frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy to mapped texture
|
||||||
|
{
|
||||||
|
if (is_paused_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto frame_captured =
|
||||||
|
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||||
|
|
||||||
|
if (!d3d11_texture_mapped_ || is_new_size)
|
||||||
|
CreateMappedTexture(frame_captured);
|
||||||
|
|
||||||
|
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
|
||||||
|
frame_captured.get());
|
||||||
|
|
||||||
|
D3D11_MAPPED_SUBRESOURCE map_result;
|
||||||
|
HRESULT hr = d3d11_device_context_->Map(
|
||||||
|
d3d11_texture_mapped_.get(), 0, D3D11_MAP_READ,
|
||||||
|
0 /*coz we use CreateFreeThreaded, so we cant use flags
|
||||||
|
D3D11_MAP_FLAG_DO_NOT_WAIT*/
|
||||||
|
,
|
||||||
|
&map_result);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
OutputDebugStringW(
|
||||||
|
(L"map resource failed: " + std::to_wstring(hr)).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy data from map_result.pData
|
||||||
|
if (map_result.pData && observer_) {
|
||||||
|
observer_->OnFrame(
|
||||||
|
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
|
||||||
|
static_cast<unsigned int>(frame_size.Height),
|
||||||
|
map_result.RowPitch,
|
||||||
|
const_cast<const unsigned char *>(
|
||||||
|
(unsigned char *)map_result.pData)},
|
||||||
|
id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_new_size) {
|
||||||
capture_framepool_.Recreate(d3d11_direct_device_,
|
capture_framepool_.Recreate(d3d11_direct_device_,
|
||||||
winrt::Windows::Graphics::DirectX::
|
winrt::Windows::Graphics::DirectX::
|
||||||
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||||
2, capture_frame_size_);
|
2, capture_frame_size_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto frame_surface =
|
|
||||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
|
||||||
|
|
||||||
if (!d3d11_texture_mapped_ || size_changed)
|
|
||||||
CreateMappedTexture(frame_surface, frame_size.Width, frame_size.Height);
|
|
||||||
|
|
||||||
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
|
|
||||||
frame_surface.get());
|
|
||||||
|
|
||||||
D3D11_MAPPED_SUBRESOURCE map_result;
|
|
||||||
HRESULT hr = d3d11_device_context_->Map(d3d11_texture_mapped_.get(), 0,
|
|
||||||
D3D11_MAP_READ, 0, &map_result);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
wgc_session_frame frame_info{
|
|
||||||
static_cast<unsigned int>(frame_size.Width),
|
|
||||||
static_cast<unsigned int>(frame_size.Height), map_result.RowPitch,
|
|
||||||
reinterpret_cast<const unsigned char*>(map_result.pData)};
|
|
||||||
observer_->OnFrame(frame_info, id_);
|
|
||||||
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WgcSessionImpl::OnClosed(
|
void WgcSessionImpl::OnClosed(
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &,
|
||||||
winrt::Windows::Foundation::IInspectable const&) {
|
winrt::Windows::Foundation::IInspectable const &) {
|
||||||
OutputDebugStringW(L"WgcSessionImpl::OnClosed");
|
OutputDebugStringW(L"WgcSessionImpl::OnClosed");
|
||||||
Stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize() {
|
int WgcSessionImpl::Initialize() {
|
||||||
if (is_initialized_) return 0;
|
if (is_initialized_) return 0;
|
||||||
|
|
||||||
d3d11_direct_device_ = CreateD3D11Device();
|
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
|
||||||
if (!d3d11_direct_device_) {
|
|
||||||
std::cout << "AE_D3D_CREATE_DEVICE_FAILED" << std::endl;
|
std::cout << "AE_D3D_CREATE_DEVICE_FAILED" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
capture_item_ = target_.is_window
|
if (target_.is_window)
|
||||||
? CreateCaptureItemForWindow(target_.hwnd)
|
capture_item_ = CreateCaptureItemForWindow(target_.hwnd);
|
||||||
: CreateCaptureItemForMonitor(target_.hmonitor);
|
else
|
||||||
|
capture_item_ = CreateCaptureItemForMonitor(target_.hmonitor);
|
||||||
|
|
||||||
auto d3d_device =
|
// Set up
|
||||||
|
auto d3d11_device =
|
||||||
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
|
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
|
||||||
d3d_device->GetImmediateContext(d3d11_device_context_.put());
|
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
|
||||||
|
|
||||||
|
} catch (winrt::hresult_error) {
|
||||||
|
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
|
||||||
|
return 86;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
|
||||||
return 86;
|
return 86;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_initialized_ = true;
|
is_initialized_ = true;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WgcSessionImpl::CleanUp() {
|
void WgcSessionImpl::CleanUp() {
|
||||||
std::scoped_lock locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
if (cleaned_.exchange(true)) return;
|
|
||||||
|
auto expected = false;
|
||||||
|
if (cleaned_.compare_exchange_strong(expected, true)) {
|
||||||
|
capture_close_trigger_.revoke();
|
||||||
|
capture_framepool_trigger_.revoke();
|
||||||
|
|
||||||
try {
|
|
||||||
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
|
|
||||||
if (capture_close_trigger_) capture_close_trigger_.revoke();
|
|
||||||
if (capture_framepool_) capture_framepool_.Close();
|
if (capture_framepool_) capture_framepool_.Close();
|
||||||
|
|
||||||
if (capture_session_) capture_session_.Close();
|
if (capture_session_) capture_session_.Close();
|
||||||
} catch (...) {
|
|
||||||
|
capture_framepool_ = nullptr;
|
||||||
|
capture_session_ = nullptr;
|
||||||
|
capture_item_ = nullptr;
|
||||||
|
|
||||||
|
is_initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
capture_framepool_ = nullptr;
|
|
||||||
capture_session_ = nullptr;
|
|
||||||
capture_item_ = nullptr;
|
|
||||||
d3d11_texture_mapped_ = nullptr;
|
|
||||||
d3d11_device_context_ = nullptr;
|
|
||||||
d3d11_direct_device_ = nullptr;
|
|
||||||
|
|
||||||
is_initialized_ = false;
|
|
||||||
is_running_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
|
||||||
|
LPARAM l_param) {
|
||||||
|
return DefWindowProc(window, message, w_param, l_param);
|
||||||
|
}
|
||||||
|
|
||||||
|
// void WgcSessionImpl::message_func() {
|
||||||
|
// const std::wstring kClassName = L"am_fake_window";
|
||||||
|
|
||||||
|
// WNDCLASS wc = {};
|
||||||
|
|
||||||
|
// wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||||
|
// wc.lpfnWndProc = DefWindowProc;
|
||||||
|
// wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||||
|
// wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW);
|
||||||
|
// wc.lpszClassName = kClassName.c_str();
|
||||||
|
|
||||||
|
// if (!::RegisterClassW(&wc)) return;
|
||||||
|
|
||||||
|
// hwnd_ = ::CreateWindowW(kClassName.c_str(), nullptr, WS_OVERLAPPEDWINDOW,
|
||||||
|
// 0,
|
||||||
|
// 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
|
||||||
|
// MSG msg;
|
||||||
|
// while (is_running_) {
|
||||||
|
// while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||||
|
// if (!is_running_) break;
|
||||||
|
// TranslateMessage(&msg);
|
||||||
|
// DispatchMessage(&msg);
|
||||||
|
// }
|
||||||
|
// Sleep(10);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ::CloseWindow(hwnd_);
|
||||||
|
// ::DestroyWindow(hwnd_);
|
||||||
|
// }
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
#include "wgc_session_impl.h"
|
|
||||||
|
|
||||||
#include <Windows.Graphics.Capture.Interop.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "rd_log.h"
|
|
||||||
|
|
||||||
#define CHECK_INIT \
|
|
||||||
if (!is_initialized_) { \
|
|
||||||
std::cout << "AE_NEED_INIT" << std::endl; \
|
|
||||||
return 4; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CHECK_CLOSED \
|
|
||||||
if (cleaned_.load() == true) { \
|
|
||||||
throw winrt::hresult_error(RO_E_CLOSED); \
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
|
|
||||||
::IDXGIDevice* dxgiDevice, ::IInspectable** graphicsDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
|
|
||||||
|
|
||||||
WgcSessionImpl::~WgcSessionImpl() {
|
|
||||||
Stop();
|
|
||||||
CleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WgcSessionImpl::Release() { delete this; }
|
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize(HWND hwnd) {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
target_.hwnd = hwnd;
|
|
||||||
target_.is_window = true;
|
|
||||||
return Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
target_.hmonitor = hmonitor;
|
|
||||||
target_.is_window = false;
|
|
||||||
return Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
observer_ = observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WgcSessionImpl::Start() {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
if (is_running_) return 0;
|
|
||||||
|
|
||||||
int error = 1;
|
|
||||||
|
|
||||||
CHECK_INIT;
|
|
||||||
try {
|
|
||||||
if (!capture_session_) {
|
|
||||||
auto current_size = capture_item_.Size();
|
|
||||||
capture_framepool_ =
|
|
||||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
|
|
||||||
CreateFreeThreaded(d3d11_direct_device_,
|
|
||||||
winrt::Windows::Graphics::DirectX::
|
|
||||||
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
|
||||||
2, current_size);
|
|
||||||
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
|
|
||||||
capture_frame_size_ = current_size;
|
|
||||||
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
|
|
||||||
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
|
|
||||||
capture_close_trigger_ = capture_item_.Closed(
|
|
||||||
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!capture_framepool_) throw std::exception();
|
|
||||||
|
|
||||||
is_running_ = true;
|
|
||||||
|
|
||||||
// we do not need to crate a thread to enter a message loop coz we use
|
|
||||||
// CreateFreeThreaded instead of Create to create a capture frame pool,
|
|
||||||
// we need to test the performance later
|
|
||||||
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
|
|
||||||
|
|
||||||
capture_session_.StartCapture();
|
|
||||||
|
|
||||||
capture_session_.IsCursorCaptureEnabled(false);
|
|
||||||
|
|
||||||
error = 0;
|
|
||||||
} catch (winrt::hresult_error) {
|
|
||||||
LOG_ERROR("Create WGC Capture Failed");
|
|
||||||
return 86;
|
|
||||||
} catch (...) {
|
|
||||||
return 86;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WgcSessionImpl::Stop() {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
CHECK_INIT;
|
|
||||||
|
|
||||||
is_running_ = false;
|
|
||||||
|
|
||||||
// if (loop_.joinable()) loop_.join();
|
|
||||||
|
|
||||||
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
|
|
||||||
|
|
||||||
if (capture_session_) {
|
|
||||||
capture_session_.Close();
|
|
||||||
capture_session_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WgcSessionImpl::Pause() {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
is_paused_ = true;
|
|
||||||
|
|
||||||
CHECK_INIT;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WgcSessionImpl::Resume() {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
is_paused_ = false;
|
|
||||||
|
|
||||||
CHECK_INIT;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto WgcSessionImpl::CreateD3D11Device() {
|
|
||||||
winrt::com_ptr<ID3D11Device> d3d_device;
|
|
||||||
HRESULT hr;
|
|
||||||
|
|
||||||
WINRT_ASSERT(!d3d_device);
|
|
||||||
|
|
||||||
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_HARDWARE;
|
|
||||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
|
||||||
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
|
||||||
D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);
|
|
||||||
|
|
||||||
if (DXGI_ERROR_UNSUPPORTED == hr) {
|
|
||||||
// change D3D_DRIVER_TYPE
|
|
||||||
type = D3D_DRIVER_TYPE_WARP;
|
|
||||||
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
|
||||||
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
|
|
||||||
nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
winrt::check_hresult(hr);
|
|
||||||
|
|
||||||
winrt::com_ptr<::IInspectable> d3d11_device;
|
|
||||||
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(
|
|
||||||
d3d_device.as<IDXGIDevice>().get(), d3d11_device.put()));
|
|
||||||
return d3d11_device
|
|
||||||
.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
|
|
||||||
auto activation_factory = winrt::get_activation_factory<
|
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
|
|
||||||
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
|
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
|
|
||||||
interop_factory->CreateForWindow(
|
|
||||||
hwnd,
|
|
||||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
|
||||||
reinterpret_cast<void**>(winrt::put_abi(item)));
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
|
|
||||||
auto activation_factory = winrt::get_activation_factory<
|
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
|
|
||||||
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
|
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
|
|
||||||
interop_factory->CreateForMonitor(
|
|
||||||
hmonitor,
|
|
||||||
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
|
|
||||||
reinterpret_cast<void**>(winrt::put_abi(item)));
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
HRESULT WgcSessionImpl::CreateMappedTexture(
|
|
||||||
winrt::com_ptr<ID3D11Texture2D> src_texture, unsigned int width,
|
|
||||||
unsigned int height) {
|
|
||||||
D3D11_TEXTURE2D_DESC src_desc;
|
|
||||||
src_texture->GetDesc(&src_desc);
|
|
||||||
D3D11_TEXTURE2D_DESC map_desc;
|
|
||||||
map_desc.Width = width == 0 ? src_desc.Width : width;
|
|
||||||
map_desc.Height = height == 0 ? src_desc.Height : height;
|
|
||||||
map_desc.MipLevels = src_desc.MipLevels;
|
|
||||||
map_desc.ArraySize = src_desc.ArraySize;
|
|
||||||
map_desc.Format = src_desc.Format;
|
|
||||||
map_desc.SampleDesc = src_desc.SampleDesc;
|
|
||||||
map_desc.Usage = D3D11_USAGE_STAGING;
|
|
||||||
map_desc.BindFlags = 0;
|
|
||||||
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
|
||||||
map_desc.MiscFlags = 0;
|
|
||||||
|
|
||||||
auto d3dDevice =
|
|
||||||
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
|
|
||||||
|
|
||||||
return d3dDevice->CreateTexture2D(&map_desc, nullptr,
|
|
||||||
d3d11_texture_mapped_.put());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WgcSessionImpl::OnFrame(
|
|
||||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
|
|
||||||
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
auto is_new_size = false;
|
|
||||||
|
|
||||||
{
|
|
||||||
auto frame = sender.TryGetNextFrame();
|
|
||||||
auto frame_size = frame.ContentSize();
|
|
||||||
|
|
||||||
if (frame_size.Width != capture_frame_size_.Width ||
|
|
||||||
frame_size.Height != capture_frame_size_.Height) {
|
|
||||||
// The thing we have been capturing has changed size.
|
|
||||||
// We need to resize our swap chain first, then blit the pixels.
|
|
||||||
// After we do that, retire the frame and then recreate our frame pool.
|
|
||||||
is_new_size = true;
|
|
||||||
capture_frame_size_ = frame_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy to mapped texture
|
|
||||||
{
|
|
||||||
if (is_paused_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto frame_captured =
|
|
||||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
|
||||||
|
|
||||||
if (!d3d11_texture_mapped_ || is_new_size)
|
|
||||||
CreateMappedTexture(frame_captured);
|
|
||||||
|
|
||||||
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
|
|
||||||
frame_captured.get());
|
|
||||||
|
|
||||||
D3D11_MAPPED_SUBRESOURCE map_result;
|
|
||||||
HRESULT hr = d3d11_device_context_->Map(
|
|
||||||
d3d11_texture_mapped_.get(), 0, D3D11_MAP_READ,
|
|
||||||
0 /*coz we use CreateFreeThreaded, so we cant use flags
|
|
||||||
D3D11_MAP_FLAG_DO_NOT_WAIT*/
|
|
||||||
,
|
|
||||||
&map_result);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
OutputDebugStringW(
|
|
||||||
(L"map resource failed: " + std::to_wstring(hr)).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy data from map_result.pData
|
|
||||||
if (map_result.pData && observer_) {
|
|
||||||
observer_->OnFrame(
|
|
||||||
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
|
|
||||||
static_cast<unsigned int>(frame_size.Height),
|
|
||||||
map_result.RowPitch,
|
|
||||||
const_cast<const unsigned char*>(
|
|
||||||
(unsigned char*)map_result.pData)},
|
|
||||||
id_);
|
|
||||||
}
|
|
||||||
|
|
||||||
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_new_size) {
|
|
||||||
capture_framepool_.Recreate(d3d11_direct_device_,
|
|
||||||
winrt::Windows::Graphics::DirectX::
|
|
||||||
DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
|
||||||
2, capture_frame_size_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WgcSessionImpl::OnClosed(
|
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
|
|
||||||
winrt::Windows::Foundation::IInspectable const&) {
|
|
||||||
OutputDebugStringW(L"WgcSessionImpl::OnClosed");
|
|
||||||
}
|
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize() {
|
|
||||||
if (is_initialized_) return 0;
|
|
||||||
|
|
||||||
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
|
|
||||||
std::cout << "AE_D3D_CREATE_DEVICE_FAILED" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (target_.is_window)
|
|
||||||
capture_item_ = CreateCaptureItemForWindow(target_.hwnd);
|
|
||||||
else
|
|
||||||
capture_item_ = CreateCaptureItemForMonitor(target_.hmonitor);
|
|
||||||
|
|
||||||
// Set up
|
|
||||||
auto d3d11_device =
|
|
||||||
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
|
|
||||||
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
|
|
||||||
|
|
||||||
} catch (winrt::hresult_error) {
|
|
||||||
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
|
||||||
return 86;
|
|
||||||
} catch (...) {
|
|
||||||
return 86;
|
|
||||||
}
|
|
||||||
|
|
||||||
is_initialized_ = true;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WgcSessionImpl::CleanUp() {
|
|
||||||
std::lock_guard locker(lock_);
|
|
||||||
|
|
||||||
auto expected = false;
|
|
||||||
if (cleaned_.compare_exchange_strong(expected, true)) {
|
|
||||||
capture_close_trigger_.revoke();
|
|
||||||
capture_framepool_trigger_.revoke();
|
|
||||||
|
|
||||||
if (capture_framepool_) capture_framepool_.Close();
|
|
||||||
|
|
||||||
if (capture_session_) capture_session_.Close();
|
|
||||||
|
|
||||||
capture_framepool_ = nullptr;
|
|
||||||
capture_session_ = nullptr;
|
|
||||||
capture_item_ = nullptr;
|
|
||||||
|
|
||||||
is_initialized_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
|
|
||||||
LPARAM l_param) {
|
|
||||||
return DefWindowProc(window, message, w_param, l_param);
|
|
||||||
}
|
|
||||||
|
|
||||||
// void WgcSessionImpl::message_func() {
|
|
||||||
// const std::wstring kClassName = L"am_fake_window";
|
|
||||||
|
|
||||||
// WNDCLASS wc = {};
|
|
||||||
|
|
||||||
// wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
||||||
// wc.lpfnWndProc = DefWindowProc;
|
|
||||||
// wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
||||||
// wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW);
|
|
||||||
// wc.lpszClassName = kClassName.c_str();
|
|
||||||
|
|
||||||
// if (!::RegisterClassW(&wc)) return;
|
|
||||||
|
|
||||||
// hwnd_ = ::CreateWindowW(kClassName.c_str(), nullptr, WS_OVERLAPPEDWINDOW,
|
|
||||||
// 0,
|
|
||||||
// 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
|
|
||||||
// MSG msg;
|
|
||||||
// while (is_running_) {
|
|
||||||
// while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
||||||
// if (!is_running_) break;
|
|
||||||
// TranslateMessage(&msg);
|
|
||||||
// DispatchMessage(&msg);
|
|
||||||
// }
|
|
||||||
// Sleep(10);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ::CloseWindow(hwnd_);
|
|
||||||
// ::DestroyWindow(hwnd_);
|
|
||||||
// }
|
|
||||||
16
xmake.lua
16
xmake.lua
@@ -1,8 +1,11 @@
|
|||||||
set_project("crossdesk")
|
set_project("crossdesk")
|
||||||
set_license("LGPL-3.0")
|
set_license("LGPL-3.0")
|
||||||
|
|
||||||
set_version("0.0.1")
|
option("CROSSDESK_VERSION")
|
||||||
add_defines("RD_VERSION=\"0.0.1\"");
|
set_default("0.0.0")
|
||||||
|
set_showmenu(true)
|
||||||
|
set_description("Set CROSSDESK_VERSION for build")
|
||||||
|
option_end()
|
||||||
|
|
||||||
add_rules("mode.release", "mode.debug")
|
add_rules("mode.release", "mode.debug")
|
||||||
set_languages("c++17")
|
set_languages("c++17")
|
||||||
@@ -143,13 +146,18 @@ target("assets")
|
|||||||
target("gui")
|
target("gui")
|
||||||
set_kind("object")
|
set_kind("object")
|
||||||
add_packages("libyuv")
|
add_packages("libyuv")
|
||||||
|
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
|
||||||
add_deps("rd_log", "common", "assets", "config_center", "minirtc",
|
add_deps("rd_log", "common", "assets", "config_center", "minirtc",
|
||||||
"path_manager", "screen_capturer", "speaker_capturer",
|
"path_manager", "screen_capturer", "speaker_capturer",
|
||||||
"device_controller", "thumbnail")
|
"device_controller", "thumbnail")
|
||||||
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
|
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
|
||||||
"src/gui/windows/*.cpp")
|
"src/gui/windows/*.cpp")
|
||||||
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",
|
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",
|
||||||
"src/gui/windows", {public = true})
|
"src/gui/windows", {public = true})
|
||||||
|
if is_os("windows") then
|
||||||
|
add_files("src/gui/tray/*.cpp")
|
||||||
|
add_includedirs("src/gui/tray", {public = true})
|
||||||
|
end
|
||||||
|
|
||||||
target("crossdesk")
|
target("crossdesk")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
|
|||||||
Reference in New Issue
Block a user