mirror of
				https://github.com/kunkundi/crossdesk.git
				synced 2025-10-28 20:06:14 +08:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			v1.0.1
			...
			v1.0.1-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0bd27d0b17 | ||
|  | ee5612da8b | ||
|  | c7a2023c88 | ||
|  | c031a8c145 | ||
|  | 0bf83f07ad | ||
|  | 3638b712bd | ||
|  | 17f9536476 | ||
|  | 0ef51e3faf | ||
|  | cccf5dadb2 | ||
|  | 2f0b0ffc22 | ||
|  | c7411b59f1 | ||
|  | 8222782522 | ||
|  | 5fed09c1aa | ||
|  | 8e499772f9 | ||
|  | 0da812e7e9 | ||
|  | 3d67b6e9d6 | ||
|  | 506b2893c6 | ||
|  | 56abe9e690 | 
| @@ -1,4 +1,4 @@ | ||||
| name: Build and Release CrossDesk | ||||
| name: Build and Release | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
| @@ -34,10 +34,11 @@ jobs: | ||||
|         shell: bash | ||||
|         id: set_deb_version | ||||
|         run: | | ||||
|           SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | ||||
|           if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then | ||||
|             LEGAL_VERSION="0.0.0-${VERSION_NUM}" | ||||
|             LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}" | ||||
|           else | ||||
|             LEGAL_VERSION="${VERSION_NUM}" | ||||
|             LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}" | ||||
|           fi | ||||
|           echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV | ||||
| 
 | ||||
| @@ -52,6 +53,7 @@ jobs: | ||||
|           XMAKE_GLOBALDIR: /data | ||||
|         run: | | ||||
|           ls -la $XMAKE_GLOBALDIR | ||||
|           xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y | ||||
|           xmake b -vy --root crossdesk | ||||
| 
 | ||||
|       - name: Decode and save certificate | ||||
| @@ -96,10 +98,11 @@ jobs: | ||||
|         shell: bash | ||||
|         id: set_deb_version | ||||
|         run: | | ||||
|           SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | ||||
|           if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then | ||||
|             LEGAL_VERSION="0.0.0-${VERSION_NUM}" | ||||
|             LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}" | ||||
|           else | ||||
|             LEGAL_VERSION="${VERSION_NUM}" | ||||
|             LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}" | ||||
|           fi | ||||
|           echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV | ||||
| 
 | ||||
| @@ -113,6 +116,7 @@ jobs: | ||||
|           CUDA_PATH: /usr/local/cuda | ||||
|           XMAKE_GLOBALDIR: /data | ||||
|         run: | | ||||
|           xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y | ||||
|           xmake b -vy --root crossdesk | ||||
| 
 | ||||
|       - name: Decode and save certificate | ||||
| @@ -155,7 +159,8 @@ jobs: | ||||
|         id: version | ||||
|         run: | | ||||
|           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}" | ||||
| 
 | ||||
| @@ -177,7 +182,9 @@ jobs: | ||||
|         run: git submodule update --init --recursive | ||||
| 
 | ||||
|       - 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 | ||||
|         shell: bash | ||||
| @@ -215,7 +222,8 @@ jobs: | ||||
|           $version = $ref -replace '^refs/(tags|heads)/', '' | ||||
|           $version = $version -replace '^v', '' | ||||
|           $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 | ||||
|         uses: actions/cache@v4 | ||||
| @@ -281,8 +289,10 @@ jobs: | ||||
|           copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir | ||||
| 
 | ||||
|       - 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 | ||||
|         shell: powershell | ||||
|         run: | | ||||
| @@ -321,7 +331,8 @@ jobs: | ||||
|         id: version | ||||
|         run: | | ||||
|           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 | ||||
| 
 | ||||
|       - name: Rename artifacts | ||||
							
								
								
									
										65
									
								
								.github/workflows/close-issue.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								.github/workflows/close-issue.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| name: Close Inactive Issues | ||||
|  | ||||
| on: | ||||
|   schedule: | ||||
|     # run every day at midnight | ||||
|     - cron: "0 0 * * *" | ||||
|  | ||||
| jobs: | ||||
|   close_inactive_issues: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check inactive issues and close them | ||||
|         uses: actions/github-script@v6 | ||||
|         with: | ||||
|           script: | | ||||
|             const { data: issues } = await github.rest.issues.listForRepo({ | ||||
|               owner: context.repo.owner, | ||||
|               repo: context.repo.repo, | ||||
|               state: 'open', | ||||
|               per_page: 100, | ||||
|             }); | ||||
|  | ||||
|             const now = new Date().getTime(); | ||||
|             const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days | ||||
|  | ||||
|             for (const issue of issues) { | ||||
|               // skip pull requests (they are also returned by listForRepo) | ||||
|               if (issue.pull_request) continue; | ||||
|  | ||||
|               // skip labeled issues | ||||
|               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({ | ||||
|                   owner: context.repo.owner, | ||||
|                   repo: context.repo.repo, | ||||
|                   issue_number: issue.number, | ||||
|                   state: 'closed', | ||||
|                 }); | ||||
|               } else { | ||||
|                 console.log(`Skipping issue #${issue.number} (Active within 7 days).`); | ||||
|               } | ||||
|             } | ||||
| @@ -15,8 +15,9 @@ jobs: | ||||
|       - name: Set version number | ||||
|         id: version | ||||
|         run: | | ||||
|           SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | ||||
|           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_OUTPUT | ||||
| 
 | ||||
							
								
								
									
										51
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,6 +1,15 @@ | ||||
| # CrossDesk | ||||
|  | ||||
| [English](README_EN.md) / [中文](README.md) | ||||
| []() | ||||
| [](https://www.gnu.org/licenses/lgpl-3.0) | ||||
| [](https://github.com/kunkundi/crossdesk/commits/self-hosted-server) | ||||
| [](https://github.com/kunkundi/crossdesk/actions)   | ||||
| [](https://hub.docker.com/r/crossdesk/crossdesk-server/tags) | ||||
| []() | ||||
| []() | ||||
| []() | ||||
|  | ||||
| [ [English](README_EN.md) / 中文 ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -46,24 +55,45 @@ git submodule init | ||||
|  | ||||
| git submodule update | ||||
|  | ||||
| xmake b crossdesk | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
| #### 无 CUDA 环境下的开发支持 | ||||
|  | ||||
| 对于未安装 **CUDA 环境** 的Linux开发者,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。   | ||||
| 该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。 | ||||
| 运行 | ||||
| ``` | ||||
| xmake r crossdesk | ||||
| ``` | ||||
|  | ||||
| ### 无 CUDA 环境下的开发支持 | ||||
|  | ||||
| 对于**未安装 CUDA 环境的 Linux 开发者**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。 | ||||
|  | ||||
| 进入容器,下载工程后执行: | ||||
| ``` | ||||
| export CUDA_PATH=/usr/local/cuda | ||||
| export XMAKE_GLOBALDIR=/data | ||||
|  | ||||
| xmake b --root crossdesk | ||||
| xmake b --root -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| 运行 | ||||
| 对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 CUDA 编译环境: | ||||
| ``` | ||||
| xmake r crossdesk | ||||
| 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 | ||||
| ``` | ||||
|  | ||||
| #### 注意 | ||||
| @@ -230,7 +260,7 @@ echo "  Server certificate: $SERVER_CERT" | ||||
| 执行 | ||||
| ``` | ||||
| chmod +x generate_certs.sh | ||||
| ./generate_certs.sh 服务器外网IP | ||||
| ./generate_certs.sh 服务器公网IP | ||||
|  | ||||
| # 例如 ./generate_certs.sh 111.111.111.111 | ||||
| ``` | ||||
| @@ -265,3 +295,6 @@ Generation complete. Deployment files:: | ||||
|  | ||||
| 7. 勾选使用**自托管服务器配置**,点击确认配置生效。<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) 。 | ||||
							
								
								
									
										46
									
								
								README_EN.md
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								README_EN.md
									
									
									
									
									
								
							| @@ -1,6 +1,15 @@ | ||||
| # CrossDesk | ||||
|  | ||||
| [中文](README.md) / [English](README_EN.md) | ||||
| []() | ||||
| [](https://www.gnu.org/licenses/lgpl-3.0) | ||||
| [](https://github.com/kunkundi/crossdesk/commits/self-hosted-server) | ||||
| [](https://github.com/kunkundi/crossdesk/actions)   | ||||
| [](https://hub.docker.com/r/crossdesk/crossdesk-server/tags) | ||||
| []() | ||||
| []() | ||||
| []() | ||||
|  | ||||
| [ [中文](README.md) / English ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -46,12 +55,17 @@ git submodule init | ||||
|  | ||||
| git submodule update | ||||
|  | ||||
| xmake b crossdesk | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| Run: | ||||
| ``` | ||||
| xmake r crossdesk | ||||
| ``` | ||||
|  | ||||
| #### Development Without CUDA Environment | ||||
|  | ||||
| For 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. | ||||
|  | ||||
| After entering the container, download the project and run: | ||||
| @@ -59,12 +73,29 @@ After entering the container, download the project and run: | ||||
| export CUDA_PATH=/usr/local/cuda | ||||
| export XMAKE_GLOBALDIR=/data | ||||
|  | ||||
| xmake b --root crossdesk | ||||
| xmake b --root -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| Run: | ||||
| For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment: | ||||
| ``` | ||||
| xmake r crossdesk | ||||
| xmake require -vy "cuda 12.6.3" | ||||
| ``` | ||||
| After the installation is complete, execute: | ||||
| ``` | ||||
| xmake require --info "cuda 12.6.3" | ||||
| ``` | ||||
| The output will look like this: | ||||
|  | ||||
| <img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" /> | ||||
|  | ||||
| From the output above, locate the CUDA installation directory — this is the path pointed to by installdir. | ||||
| Add this path to your system environment variable CUDA_PATH, or set it in the terminal using: | ||||
| ``` | ||||
| set CUDA_PATH=path_to_cuda_installdir: | ||||
| ``` | ||||
| Then re-run: | ||||
| ``` | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| #### Notice | ||||
| @@ -269,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> | ||||
| <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_ = | ||||
|       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; | ||||
| } | ||||
|  | ||||
| @@ -62,6 +65,8 @@ int ConfigCenter::Save() { | ||||
|   ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_)); | ||||
|   ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); | ||||
|   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()); | ||||
|   if (rc < 0) { | ||||
| @@ -186,6 +191,11 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) { | ||||
|   enable_minimize_to_tray_ = enable_minimize_to_tray; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| // getters | ||||
|  | ||||
| ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; } | ||||
| @@ -226,4 +236,6 @@ std::string ConfigCenter::GetDefaultCertFilePath() const { | ||||
|   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 SetCertFilePath(const std::string& cert_file_path); | ||||
|   int SetSelfHosted(bool enable_self_hosted); | ||||
|   int SetMinimizeToTray(bool enable_minimize_to_tray); | ||||
|  | ||||
|   // read config | ||||
|  | ||||
| @@ -53,6 +54,7 @@ class ConfigCenter { | ||||
|   int GetDefaultServerPort() const; | ||||
|   std::string GetDefaultCertFilePath() const; | ||||
|   bool IsSelfHosted() const; | ||||
|   bool IsMinimizeToTray() const; | ||||
|  | ||||
|   int Load(); | ||||
|   int Save(); | ||||
| @@ -76,6 +78,7 @@ class ConfigCenter { | ||||
|   int server_port_default_ = 9099; | ||||
|   std::string cert_file_path_default_ = ""; | ||||
|   bool enable_self_hosted_ = false; | ||||
|   bool enable_minimize_to_tray_ = false; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -20,8 +20,13 @@ | ||||
| #define INPUT_WINDOW_PADDING_EN 96 | ||||
| #define SETTINGS_WINDOW_WIDTH_CN 202 | ||||
| #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_EN 315 | ||||
| #endif | ||||
| #define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 165 | ||||
| @@ -42,6 +47,8 @@ | ||||
| #define ENABLE_SRTP_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171 | ||||
| #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_EN 137 | ||||
| #define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90 | ||||
|   | ||||
| @@ -8,6 +8,10 @@ | ||||
|  | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #if _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
| namespace localization { | ||||
|  | ||||
| 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 = { | ||||
|     reinterpret_cast<const char*>(u8"确认删除此连接"), | ||||
|     "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 | ||||
| @@ -588,6 +588,17 @@ int Render::CreateMainWindow() { | ||||
|   // for window region action | ||||
|   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; | ||||
| } | ||||
|  | ||||
| @@ -968,6 +979,14 @@ void Render::MainLoop() { | ||||
|       ProcessSdlEvent(event); | ||||
|     } | ||||
|  | ||||
| #if _WIN32 | ||||
|     MSG msg; | ||||
|     while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { | ||||
|       TranslateMessage(&msg); | ||||
|       DispatchMessage(&msg); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     UpdateLabels(); | ||||
|     HandleRecentConnections(); | ||||
|     HandleStreamWindow(); | ||||
|   | ||||
| @@ -29,6 +29,9 @@ | ||||
| #include "screen_capturer_factory.h" | ||||
| #include "speaker_capturer_factory.h" | ||||
| #include "thumbnail.h" | ||||
| #if _WIN32 | ||||
| #include "win_tray.h" | ||||
| #endif | ||||
|  | ||||
| class Render { | ||||
|  public: | ||||
| @@ -298,6 +301,9 @@ class Render { | ||||
|   ImGuiContext* main_ctx_ = nullptr; | ||||
|   bool exit_ = false; | ||||
|   const int sdl_refresh_ms_ = 16;  // ~60 FPS | ||||
| #if _WIN32 | ||||
|   std::unique_ptr<WinTray> tray_; | ||||
| #endif | ||||
|  | ||||
|   // main window properties | ||||
|   bool start_mouse_controller_ = false; | ||||
| @@ -335,8 +341,8 @@ class Render { | ||||
|   float connection_status_window_height_ = 150; | ||||
|   float notification_window_width_ = 200; | ||||
|   float notification_window_height_ = 80; | ||||
|   float about_window_width_ = 200; | ||||
|   float about_window_height_ = 150; | ||||
|   float about_window_width_ = 300; | ||||
|   float about_window_height_ = 170; | ||||
|   int screen_width_ = 1280; | ||||
|   int screen_height_ = 720; | ||||
|   int selected_display_ = 0; | ||||
| @@ -444,6 +450,8 @@ class Render { | ||||
|   bool enable_hardware_video_codec_last_ = false; | ||||
|   bool enable_turn_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_port_tmp_[6] = "9099"; | ||||
|   bool settings_window_pos_reset_ = true; | ||||
|   | ||||
| @@ -139,9 +139,17 @@ int Render::TitleBar(bool main_window) { | ||||
|     float xmark_size = 12.0f; | ||||
|     std::string close_button = "##xmark";  // ICON_FA_XMARK; | ||||
|     if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) { | ||||
|       SDL_Event event; | ||||
|       event.type = SDL_EVENT_QUIT; | ||||
|       SDL_PushEvent(&event); | ||||
| #if _WIN32 | ||||
|       if (enable_minimize_to_tray_) { | ||||
|         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, | ||||
|                               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); | ||||
|  | ||||
|     std::string version; | ||||
| #ifdef RD_VERSION | ||||
|     version = RD_VERSION; | ||||
| #ifdef CROSSDESK_VERSION | ||||
|     version = CROSSDESK_VERSION; | ||||
| #else | ||||
|     version = "Unknown"; | ||||
| #endif | ||||
|  | ||||
|     std::string text = | ||||
|         localization::version[localization_language_index_] + ": " + version; | ||||
|     std::string text = localization::version[localization_language_index_] + | ||||
|                        ": CrossDesk v" + version; | ||||
|     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::SetCursorPosY(about_window_height_ * 0.75f); | ||||
|   | ||||
| @@ -57,7 +57,7 @@ int Render::SettingWindow() { | ||||
|             localization::language_en[localization_language_index_].c_str()}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", localization::language[localization_language_index_].c_str()); | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
| @@ -88,7 +88,7 @@ int Render::SettingWindow() { | ||||
|                 .c_str()}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             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"}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text("%s", | ||||
|                     localization::video_frame_rate[localization_language_index_] | ||||
|                         .c_str()); | ||||
| @@ -137,7 +137,7 @@ int Render::SettingWindow() { | ||||
|             localization::av1[localization_language_index_].c_str()}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::video_encode_format[localization_language_index_] | ||||
| @@ -160,7 +160,7 @@ int Render::SettingWindow() { | ||||
|  | ||||
|       { | ||||
|         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 | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str()); | ||||
| @@ -179,7 +179,7 @@ int Render::SettingWindow() { | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::enable_turn[localization_language_index_].c_str()); | ||||
| @@ -197,7 +197,7 @@ int Render::SettingWindow() { | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::enable_srtp[localization_language_index_].c_str()); | ||||
| @@ -215,7 +215,7 @@ int Render::SettingWindow() { | ||||
|  | ||||
|       { | ||||
|         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 | ||||
|                               [localization_language_index_] | ||||
| @@ -232,7 +232,27 @@ int Render::SettingWindow() { | ||||
|         ImGui::Checkbox("##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_) { | ||||
|         ImGui::EndDisabled(); | ||||
|       } | ||||
|   | ||||
							
								
								
									
										2
									
								
								thirdparty/minirtc
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								thirdparty/minirtc
									
									
									
									
										vendored
									
									
								
							 Submodule thirdparty/minirtc updated: b8b54613fa...40eaf93b42
									
								
							
							
								
								
									
										16
									
								
								xmake.lua
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								xmake.lua
									
									
									
									
									
								
							| @@ -1,8 +1,11 @@ | ||||
| set_project("crossdesk") | ||||
| set_license("LGPL-3.0") | ||||
|  | ||||
| set_version("0.0.1") | ||||
| add_defines("RD_VERSION=\"0.0.1\""); | ||||
| option("CROSSDESK_VERSION") | ||||
|     set_default("0.0.0") | ||||
|     set_showmenu(true) | ||||
|     set_description("Set CROSSDESK_VERSION for build") | ||||
| option_end() | ||||
|  | ||||
| add_rules("mode.release", "mode.debug") | ||||
| set_languages("c++17") | ||||
| @@ -143,13 +146,18 @@ target("assets") | ||||
| target("gui") | ||||
|     set_kind("object") | ||||
|     add_packages("libyuv") | ||||
|     add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"") | ||||
|     add_deps("rd_log", "common", "assets", "config_center", "minirtc",  | ||||
|         "path_manager", "screen_capturer", "speaker_capturer",  | ||||
|         "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") | ||||
|     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}) | ||||
|     if is_os("windows") then | ||||
|         add_files("src/gui/tray/*.cpp") | ||||
|         add_includedirs("src/gui/tray", {public = true}) | ||||
|     end | ||||
|  | ||||
| target("crossdesk") | ||||
|     set_kind("binary") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user