mirror of
				https://github.com/kunkundi/crossdesk.git
				synced 2025-10-28 20:06:14 +08:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
			v1.0.0
			...
			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 | ||
|  | ab13fa582d | ||
|  | b9e8192eee | ||
|  | 5590aaeb1e | ||
|  | adfe14809f | ||
|  | a21dbc8d69 | ||
|  | b9c70f54d3 | ||
|  | 9cd617d078 | 
							
								
								
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| *.h   linguist-language=C++ | ||||
| *.cpp linguist-language=C++ | ||||
| *.lua linguist-language=Xmake | ||||
| @@ -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 | ||||
| @@ -140,7 +144,7 @@ jobs: | ||||
|       matrix: | ||||
|         include: | ||||
|           - arch: x64 | ||||
|             runner: macos-13 | ||||
|             runner: macos-15-intel | ||||
|             cache-key: intel | ||||
|             out-dir: ./build/macosx/x86_64/release/crossdesk | ||||
|             package_script: ./scripts/macosx/pkg_x64.sh | ||||
| @@ -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 | ||||
| 
 | ||||
							
								
								
									
										222
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,9 +1,15 @@ | ||||
| # CrossDesk | ||||
|  | ||||
| #### 跨界连接,高效如一 | ||||
| []() | ||||
| [](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) / [中文](README.md) | ||||
| [ [English](README_EN.md) / 中文 ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -49,19 +55,7 @@ git submodule init | ||||
|  | ||||
| git submodule update | ||||
|  | ||||
| xmake b 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 -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| 运行 | ||||
| @@ -69,10 +63,43 @@ xmake b --root crossdesk | ||||
| 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 -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| 对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 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 | ||||
| ``` | ||||
|  | ||||
| #### 注意 | ||||
| 运行时如果客户端状态栏显示 **未连接服务器**,请先在 [CrossDesk 官方网站](https://www.crossdesk.cn/) 安装客户端,以便在环境中安装所需的证书文件。 | ||||
|  | ||||
| <img width="129" height="60" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" /> | ||||
| <img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" /> | ||||
|  | ||||
| ## 关于 Xmake | ||||
|  | ||||
| @@ -110,3 +137,164 @@ xmake b -vy crossdesk | ||||
| xmake r -d crossdesk | ||||
| ``` | ||||
| 更多使用方法可参考 [Xmake官方文档](https://xmake.io/guide/quick-start.html) 。 | ||||
|  | ||||
| ## 自托管服务器 | ||||
| 推荐使用Docker部署CrossDesk Server。 | ||||
| ``` | ||||
| sudo docker run -d \ | ||||
|   --name crossdesk_server \ | ||||
|   --network host \ | ||||
|   -e EXTERNAL_IP=xxx.xxx.xxx.xxx \ | ||||
|   -e INTERNAL_IP=xxx.xxx.xxx.xxx \ | ||||
|   -e CROSSDESK_SERVER_PORT=9099 \ | ||||
|   -v /path/to/your/certs:/crossdesk-server/certs \ | ||||
|   -v /path/to/your/db:/crossdesk-server/db \ | ||||
|   -v /path/to/your/logs:/crossdesk-server/logs \ | ||||
|   crossdesk/crossdesk-server:latest | ||||
| ``` | ||||
|  | ||||
| 上述命令中,用户需注意的参数如下: | ||||
|  | ||||
| - EXTERNAL_IP:服务器公网 IP , 对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器地址** | ||||
|  | ||||
| - INTERNAL_IP:服务器内网 IP | ||||
|  | ||||
| - CROSSDESK_SERVER_PORT:自托管服务使用的端口,对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口** | ||||
|  | ||||
| - /path/to/your/certs:证书文件目录 | ||||
|  | ||||
| - /path/to/your/db:CrossDesk Server 设备管理数据库 | ||||
|  | ||||
| - /path/to/your/logs:日志目录 | ||||
|  | ||||
| **注意**: | ||||
| - **/path/to/your/ 是示例路径,请替换为你自己的实际路径。挂载的目录必须事先创建好,否则容器会报错。** | ||||
| - **服务器需开放端口:3478/udp,3478/tcp,30000-60000/udp,CROSSDESK_SERVER_PORT/tcp,443/tcp。** | ||||
|  | ||||
| ## 证书文件 | ||||
| 客户端需加载根证书文件,服务端需加载服务器私钥和服务器证书文件。 | ||||
|  | ||||
| 如果已有SSL证书的用户,可以忽略下面的证书生成步骤。 | ||||
|  | ||||
| 对于无证书的用户,可使用下面的脚本自行生成证书文件: | ||||
| ``` | ||||
| # 创建证书生成脚本 | ||||
| vim generate_certs.sh | ||||
| ``` | ||||
| 拷贝到脚本中 | ||||
| ``` | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| # 检查参数 | ||||
| if [ "$#" -ne 1 ]; then | ||||
|     echo "Usage: $0 <SERVER_IP>" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| SERVER_IP="$1" | ||||
|  | ||||
| # 文件名 | ||||
| ROOT_KEY="crossdesk.cn_root.key" | ||||
| ROOT_CERT="crossdesk.cn_root.crt" | ||||
| SERVER_KEY="crossdesk.cn.key" | ||||
| SERVER_CSR="crossdesk.cn.csr" | ||||
| SERVER_CERT="crossdesk.cn_bundle.crt" | ||||
| FULLCHAIN_CERT="crossdesk.cn_fullchain.crt" | ||||
|  | ||||
| # 证书主题 | ||||
| SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP" | ||||
|  | ||||
| # 1. 生成根证书 | ||||
| echo "Generating root private key..." | ||||
| openssl genrsa -out "$ROOT_KEY" 4096 | ||||
|  | ||||
| echo "Generating self-signed root certificate..." | ||||
| openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ" | ||||
|  | ||||
| # 2. 生成服务器私钥 | ||||
| echo "Generating server private key..." | ||||
| openssl genrsa -out "$SERVER_KEY" 2048 | ||||
|  | ||||
| # 3. 生成服务器 CSR | ||||
| echo "Generating server CSR..." | ||||
| openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ" | ||||
|  | ||||
| # 4. 生成临时 OpenSSL 配置文件,加入 SAN | ||||
| SAN_CONF="san.cnf" | ||||
| cat > $SAN_CONF <<EOL | ||||
| [ req ] | ||||
| default_bits = 2048 | ||||
| distinguished_name = req_distinguished_name | ||||
| req_extensions = req_ext | ||||
| prompt = no | ||||
|  | ||||
| [ req_distinguished_name ] | ||||
| C = CN | ||||
| ST = Zhejiang | ||||
| L = Hangzhou | ||||
| O = CrossDesk | ||||
| OU = CrossDesk | ||||
| CN = $SERVER_IP | ||||
|  | ||||
| [ req_ext ] | ||||
| subjectAltName = IP:$SERVER_IP | ||||
| EOL | ||||
|  | ||||
| # 5. 用根证书签发服务器证书(包含 SAN) | ||||
| echo "Signing server certificate with root certificate..." | ||||
| openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \ | ||||
|   -out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext | ||||
|  | ||||
| # 6. 生成完整链证书 | ||||
| cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT" | ||||
|  | ||||
| # 7. 清理中间文件 | ||||
| rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT" | ||||
|  | ||||
| echo "Generation complete. Deployment files:" | ||||
| echo "  Client root certificate: $ROOT_CERT" | ||||
| echo "  Server private key: $SERVER_KEY" | ||||
| echo "  Server certificate: $SERVER_CERT" | ||||
| ``` | ||||
| 执行 | ||||
| ``` | ||||
| chmod +x generate_certs.sh | ||||
| ./generate_certs.sh 服务器公网IP | ||||
|  | ||||
| # 例如 ./generate_certs.sh 111.111.111.111 | ||||
| ``` | ||||
| 输出如下: | ||||
| ``` | ||||
| Generating root private key... | ||||
| Generating self-signed root certificate... | ||||
| Generating server private key... | ||||
| Generating server CSR... | ||||
| Signing server certificate with root certificate... | ||||
| Certificate request self-signature ok | ||||
| subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx | ||||
| cleaning up intermediate files... | ||||
| Generation complete. Deployment files:: | ||||
|   Client root certificate:: crossdesk.cn_root.crt | ||||
|   Server private key: crossdesk.cn.key | ||||
|   Server certificate: crossdesk.cn_bundle.crt | ||||
| ``` | ||||
|  | ||||
| #### 服务端 | ||||
| 将 **crossdesk.cn.key** 和 **crossdesk.cn_bundle.crt** 放置到 **/path/to/your/certs** 目录下。 | ||||
|  | ||||
| #### 客户端 | ||||
| 1. 点击右上角设置进入设置页面。<br> | ||||
| <img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br> | ||||
|  | ||||
| 3. 点击点击**自托管服务器配置**。<br><br> | ||||
| <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br> | ||||
|  | ||||
| 5. 在**证书文件路径**选择框中找到 **crossdesk.cn_root.crt** 的存放路径,选中 **crossdesk.cn_root.crt**,点击确认。<br><br> | ||||
| <img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><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> | ||||
|  | ||||
| # 常见问题 | ||||
| 见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。 | ||||
							
								
								
									
										231
									
								
								README_EN.md
									
									
									
									
									
								
							
							
						
						
									
										231
									
								
								README_EN.md
									
									
									
									
									
								
							| @@ -1,9 +1,15 @@ | ||||
| # CrossDesk | ||||
|  | ||||
| #### Bridging work, uniting efficiency | ||||
| []() | ||||
| [](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](README_EN.md) | ||||
| [ [中文](README.md) / English ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -17,15 +23,15 @@ CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkund | ||||
|  | ||||
| Enter the remote desktop ID in the menu bar’s “Remote ID” field and click “→” to initiate a remote connection. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| If the remote desktop requires a connection password, you must enter the correct password on your side to successfully establish the connection. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| Before connecting, you can customize configuration options in the settings, such as language and video encoding format. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## How to build | ||||
|  | ||||
| @@ -49,20 +55,7 @@ git submodule init | ||||
|  | ||||
| git submodule update | ||||
|  | ||||
| xmake b 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.   | ||||
| 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: | ||||
| ``` | ||||
| export CUDA_PATH=/usr/local/cuda | ||||
| export XMAKE_GLOBALDIR=/data | ||||
|  | ||||
| xmake b --root crossdesk | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| Run: | ||||
| @@ -70,10 +63,45 @@ Run: | ||||
| xmake r crossdesk | ||||
| ``` | ||||
|  | ||||
| #### 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.   | ||||
| 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: | ||||
| ``` | ||||
| export CUDA_PATH=/usr/local/cuda | ||||
| export XMAKE_GLOBALDIR=/data | ||||
|  | ||||
| xmake b --root -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| 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" | ||||
| ``` | ||||
| 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 | ||||
| If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment. | ||||
|  | ||||
| <img width="108" height="57" alt="image" src="https://github.com/user-attachments/assets/26e8b9f3-b326-410e-9466-dd073eaf675a" /> | ||||
| <img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" /> | ||||
|  | ||||
| ## About Xmake | ||||
| #### Installing Xmake | ||||
| @@ -114,3 +142,164 @@ xmake r -d crossdesk | ||||
| ``` | ||||
|  | ||||
| For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) . | ||||
|  | ||||
| ## Self-Hosted Server | ||||
| It is recommended to deploy CrossDesk Server using Docker. | ||||
| ``` | ||||
| sudo docker run -d \ | ||||
|   --name crossdesk_server \ | ||||
|   --network host \ | ||||
|   -e EXTERNAL_IP=150.158.81.30 \ | ||||
|   -e INTERNAL_IP=10.0.4.3 \ | ||||
|   -e CROSSDESK_SERVER_PORT=9099 \ | ||||
|   -v /path/to/your/certs:/crossdesk-server/certs \ | ||||
|   -v /path/to/your/db:/crossdesk-server/db \ | ||||
|   -v /path/to/your/logs:/crossdesk-server/logs \ | ||||
|   crossdesk/crossdesk-server:latest | ||||
| ``` | ||||
|  | ||||
| The parameters you need to pay attention to are as follows: | ||||
|  | ||||
| - **EXTERNAL_IP**: The server's public IP, corresponding to the **Server Address** in the CrossDesk client **Self-Hosted Server Configuration**. | ||||
|  | ||||
| - **INTERNAL_IP**: The server's internal IP. | ||||
|  | ||||
| - **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**. | ||||
|  | ||||
| - **/path/to/your/certs**: Directory for certificate files. | ||||
|  | ||||
| - **/path/to/your/db**: CrossDesk Server device management database. | ||||
|  | ||||
| - **/path/to/your/logs**: Log directory. | ||||
|  | ||||
| **Note**:   | ||||
| - **/path/to/your/ is an example path; please replace it with your actual path. The mounted directories must be created in advance, otherwise the container will fail.** | ||||
| - **The server must open the following ports: 3478/udp, 3478/tcp, 30000-60000/udp, CROSSDESK_SERVER_PORT/tcp, 443/tcp.** | ||||
|  | ||||
| ## Certificate Files | ||||
| The client needs to load the root certificate, and the server needs to load the server private key and server certificate. | ||||
|  | ||||
| If you already have an SSL certificate, you can skip the following certificate generation steps. | ||||
|  | ||||
| For users without a certificate, you can use the script below to generate the certificate files: | ||||
| ``` | ||||
| # Create certificate generation script | ||||
| vim generate_certs.sh | ||||
| ``` | ||||
| Copy the following into the script: | ||||
| ``` | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| # Check arguments | ||||
| if [ "$#" -ne 1 ]; then | ||||
|     echo "Usage: $0 <SERVER_IP>" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| SERVER_IP="$1" | ||||
|  | ||||
| # Filenames | ||||
| ROOT_KEY="crossdesk.cn_root.key" | ||||
| ROOT_CERT="crossdesk.cn_root.crt" | ||||
| SERVER_KEY="crossdesk.cn.key" | ||||
| SERVER_CSR="crossdesk.cn.csr" | ||||
| SERVER_CERT="crossdesk.cn_bundle.crt" | ||||
| FULLCHAIN_CERT="crossdesk.cn_fullchain.crt" | ||||
|  | ||||
| # Certificate subject | ||||
| SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP" | ||||
|  | ||||
| # 1. Generate root certificate | ||||
| echo "Generating root private key..." | ||||
| openssl genrsa -out "$ROOT_KEY" 4096 | ||||
|  | ||||
| echo "Generating self-signed root certificate..." | ||||
| openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ" | ||||
|  | ||||
| # 2. Generate server private key | ||||
| echo "Generating server private key..." | ||||
| openssl genrsa -out "$SERVER_KEY" 2048 | ||||
|  | ||||
| # 3. Generate server CSR | ||||
| echo "Generating server CSR..." | ||||
| openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ" | ||||
|  | ||||
| # 4. Create temporary OpenSSL config file with SAN | ||||
| SAN_CONF="san.cnf" | ||||
| cat > $SAN_CONF <<EOL | ||||
| [ req ] | ||||
| default_bits = 2048 | ||||
| distinguished_name = req_distinguished_name | ||||
| req_extensions = req_ext | ||||
| prompt = no | ||||
|  | ||||
| [ req_distinguished_name ] | ||||
| C = CN | ||||
| ST = Zhejiang | ||||
| L = Hangzhou | ||||
| O = CrossDesk | ||||
| OU = CrossDesk | ||||
| CN = $SERVER_IP | ||||
|  | ||||
| [ req_ext ] | ||||
| subjectAltName = IP:$SERVER_IP | ||||
| EOL | ||||
|  | ||||
| # 5. Sign server certificate with root certificate (including SAN) | ||||
| echo "Signing server certificate with root certificate..." | ||||
| openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \ | ||||
|   -out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext | ||||
|  | ||||
| # 6. Generate full chain certificate | ||||
| cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT" | ||||
|  | ||||
| # 7. Clean up intermediate files | ||||
| rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT" | ||||
|  | ||||
| echo "Generation complete. Deployment files:" | ||||
| echo "  Client root certificate: $ROOT_CERT" | ||||
| echo "  Server private key: $SERVER_KEY" | ||||
| echo "  Server certificate: $SERVER_CERT" | ||||
| ``` | ||||
| Execute: | ||||
| ``` | ||||
| chmod +x generate_certs.sh | ||||
| ./generate_certs.sh EXTERNAL_IP | ||||
|  | ||||
| # example ./generate_certs.sh 111.111.111.111 | ||||
| ``` | ||||
| Expected output: | ||||
| ``` | ||||
| Generating root private key... | ||||
| Generating self-signed root certificate... | ||||
| Generating server private key... | ||||
| Generating server CSR... | ||||
| Signing server certificate with root certificate... | ||||
| Certificate request self-signature ok | ||||
| subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx | ||||
| cleaning up intermediate files... | ||||
| Generation complete. Deployment files:: | ||||
|   Client root certificate:: crossdesk.cn_root.crt | ||||
|   Server private key: crossdesk.cn.key | ||||
|   Server certificate: crossdesk.cn_bundle.crt | ||||
| ``` | ||||
|  | ||||
| #### Server Side | ||||
| Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory. | ||||
|  | ||||
| #### Client Side | ||||
| 1. Click the settings icon in the top-right corner to enter the settings page.<br> | ||||
| <img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br> | ||||
|  | ||||
| 2. Click **Self-Hosted Server Configuration**.<br><br> | ||||
| <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br> | ||||
|  | ||||
| 3. In the **Certificate File Path** selection, locate and select the **crossdesk.cn_root.crt** file.<br><br> | ||||
| <img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><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> | ||||
|  | ||||
| # 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) | ||||
|  | ||||
| --- | ||||
							
								
								
									
										3644
									
								
								src/config_center/SimpleIni.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3644
									
								
								src/config_center/SimpleIni.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,64 +1,241 @@ | ||||
| #include "config_center.h" | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| ConfigCenter::ConfigCenter() {} | ||||
| ConfigCenter::ConfigCenter(const std::string& config_path, | ||||
|                            const std::string& cert_file_path) | ||||
|     : config_path_(config_path), | ||||
|       cert_file_path_(cert_file_path), | ||||
|       cert_file_path_default_(cert_file_path) { | ||||
|   ini_.SetUnicode(true); | ||||
|   Load(); | ||||
| } | ||||
|  | ||||
| ConfigCenter::~ConfigCenter() {} | ||||
|  | ||||
| int ConfigCenter::Load() { | ||||
|   SI_Error rc = ini_.LoadFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     Save(); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   language_ = static_cast<LANGUAGE>( | ||||
|       ini_.GetLongValue(section_, "language", static_cast<long>(language_))); | ||||
|  | ||||
|   video_quality_ = static_cast<VIDEO_QUALITY>(ini_.GetLongValue( | ||||
|       section_, "video_quality", static_cast<long>(video_quality_))); | ||||
|  | ||||
|   video_frame_rate_ = static_cast<VIDEO_FRAME_RATE>(ini_.GetLongValue( | ||||
|       section_, "video_frame_rate", static_cast<long>(video_frame_rate_))); | ||||
|  | ||||
|   video_encode_format_ = static_cast<VIDEO_ENCODE_FORMAT>( | ||||
|       ini_.GetLongValue(section_, "video_encode_format", | ||||
|                         static_cast<long>(video_encode_format_))); | ||||
|  | ||||
|   hardware_video_codec_ = ini_.GetBoolValue(section_, "hardware_video_codec", | ||||
|                                             hardware_video_codec_); | ||||
|  | ||||
|   enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_); | ||||
|   enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_); | ||||
|   server_host_ = ini_.GetValue(section_, "server_host", server_host_.c_str()); | ||||
|   server_port_ = static_cast<int>( | ||||
|       ini_.GetLongValue(section_, "server_port", server_port_)); | ||||
|   cert_file_path_ = | ||||
|       ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str()); | ||||
|   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; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::Save() { | ||||
|   ini_.SetLongValue(section_, "language", static_cast<long>(language_)); | ||||
|   ini_.SetLongValue(section_, "video_quality", | ||||
|                     static_cast<long>(video_quality_)); | ||||
|   ini_.SetLongValue(section_, "video_frame_rate", | ||||
|                     static_cast<long>(video_frame_rate_)); | ||||
|   ini_.SetLongValue(section_, "video_encode_format", | ||||
|                     static_cast<long>(video_encode_format_)); | ||||
|   ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_); | ||||
|   ini_.SetBoolValue(section_, "enable_turn", enable_turn_); | ||||
|   ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_); | ||||
|   ini_.SetValue(section_, "server_host", server_host_.c_str()); | ||||
|   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) { | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| // setters | ||||
|  | ||||
| int ConfigCenter::SetLanguage(LANGUAGE language) { | ||||
|   language_ = language; | ||||
|   ini_.SetLongValue(section_, "language", static_cast<long>(language_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) { | ||||
|   video_quality_ = video_quality; | ||||
|   ini_.SetLongValue(section_, "video_quality", | ||||
|                     static_cast<long>(video_quality_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate) { | ||||
|   video_frame_rate_ = video_frame_rate; | ||||
|   ini_.SetLongValue(section_, "video_frame_rate", | ||||
|                     static_cast<long>(video_frame_rate_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetVideoEncodeFormat( | ||||
|     VIDEO_ENCODE_FORMAT video_encode_format) { | ||||
|   video_encode_format_ = video_encode_format; | ||||
|   ini_.SetLongValue(section_, "video_encode_format", | ||||
|                     static_cast<long>(video_encode_format_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) { | ||||
|   hardware_video_codec_ = hardware_video_codec; | ||||
|   ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetTurn(bool enable_turn) { | ||||
|   enable_turn_ = enable_turn; | ||||
|   ini_.SetBoolValue(section_, "enable_turn", enable_turn_); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetSrtp(bool enable_srtp) { | ||||
|   enable_srtp_ = enable_srtp; | ||||
|   ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; } | ||||
| int ConfigCenter::SetServerHost(const std::string& server_host) { | ||||
|   server_host_ = server_host; | ||||
|   ini_.SetValue(section_, "server_host", server_host_.c_str()); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() { | ||||
| int ConfigCenter::SetServerPort(int server_port) { | ||||
|   server_port_ = server_port; | ||||
|   ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) { | ||||
|   cert_file_path_ = cert_file_path; | ||||
|   ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { | ||||
|   enable_self_hosted_ = enable_self_hosted; | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   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_; } | ||||
|  | ||||
| ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() const { | ||||
|   return video_quality_; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::GetVideoFrameRate() { | ||||
|   int fps = video_frame_rate_ == VIDEO_FRAME_RATE::FPS_30 ? 30 : 60; | ||||
|   return fps; | ||||
| ConfigCenter::VIDEO_FRAME_RATE ConfigCenter::GetVideoFrameRate() const { | ||||
|   return video_frame_rate_; | ||||
| } | ||||
|  | ||||
| ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() { | ||||
| ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() const { | ||||
|   return video_encode_format_; | ||||
| } | ||||
|  | ||||
| bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; } | ||||
| bool ConfigCenter::IsHardwareVideoCodec() const { | ||||
|   return hardware_video_codec_; | ||||
| } | ||||
|  | ||||
| bool ConfigCenter::IsEnableTurn() { return enable_turn_; } | ||||
| bool ConfigCenter::IsEnableTurn() const { return enable_turn_; } | ||||
|  | ||||
| bool ConfigCenter::IsEnableSrtp() { return enable_srtp_; } | ||||
| bool ConfigCenter::IsEnableSrtp() const { return enable_srtp_; } | ||||
|  | ||||
| std::string ConfigCenter::GetServerHost() const { return server_host_; } | ||||
|  | ||||
| int ConfigCenter::GetServerPort() const { return server_port_; } | ||||
|  | ||||
| std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; } | ||||
|  | ||||
| std::string ConfigCenter::GetDefaultServerHost() const { | ||||
|   return server_host_default_; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::GetDefaultServerPort() const { return server_port_default_; } | ||||
|  | ||||
| std::string ConfigCenter::GetDefaultCertFilePath() const { | ||||
|   return cert_file_path_default_; | ||||
| } | ||||
|  | ||||
| bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; } | ||||
|  | ||||
| bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; } | ||||
| @@ -7,18 +7,24 @@ | ||||
| #ifndef _CONFIG_CENTER_H_ | ||||
| #define _CONFIG_CENTER_H_ | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "SimpleIni.h" | ||||
|  | ||||
| class ConfigCenter { | ||||
|  public: | ||||
|   enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 }; | ||||
|   enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 }; | ||||
|   enum class VIDEO_FRAME_RATE { FPS_30 = 0, FPS_60 = 1 }; | ||||
|   enum class VIDEO_ENCODE_FORMAT { AV1 = 0, H264 = 1 }; | ||||
|   enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 }; | ||||
|  | ||||
|  public: | ||||
|   ConfigCenter(); | ||||
|   explicit ConfigCenter( | ||||
|       const std::string& config_path = "config.ini", | ||||
|       const std::string& cert_file_path = "crossdesk.cn_root.crt"); | ||||
|   ~ConfigCenter(); | ||||
|  | ||||
|  public: | ||||
|   // write config | ||||
|   int SetLanguage(LANGUAGE language); | ||||
|   int SetVideoQuality(VIDEO_QUALITY video_quality); | ||||
|   int SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate); | ||||
| @@ -26,25 +32,53 @@ class ConfigCenter { | ||||
|   int SetHardwareVideoCodec(bool hardware_video_codec); | ||||
|   int SetTurn(bool enable_turn); | ||||
|   int SetSrtp(bool enable_srtp); | ||||
|   int SetServerHost(const std::string& server_host); | ||||
|   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); | ||||
|  | ||||
|  public: | ||||
|   LANGUAGE GetLanguage(); | ||||
|   VIDEO_QUALITY GetVideoQuality(); | ||||
|   int GetVideoFrameRate(); | ||||
|   VIDEO_ENCODE_FORMAT GetVideoEncodeFormat(); | ||||
|   bool IsHardwareVideoCodec(); | ||||
|   bool IsEnableTurn(); | ||||
|   bool IsEnableSrtp(); | ||||
|   // read config | ||||
|  | ||||
|   LANGUAGE GetLanguage() const; | ||||
|   VIDEO_QUALITY GetVideoQuality() const; | ||||
|   VIDEO_FRAME_RATE GetVideoFrameRate() const; | ||||
|   VIDEO_ENCODE_FORMAT GetVideoEncodeFormat() const; | ||||
|   bool IsHardwareVideoCodec() const; | ||||
|   bool IsEnableTurn() const; | ||||
|   bool IsEnableSrtp() const; | ||||
|   std::string GetServerHost() const; | ||||
|   int GetServerPort() const; | ||||
|   std::string GetCertFilePath() const; | ||||
|   std::string GetDefaultServerHost() const; | ||||
|   int GetDefaultServerPort() const; | ||||
|   std::string GetDefaultCertFilePath() const; | ||||
|   bool IsSelfHosted() const; | ||||
|   bool IsMinimizeToTray() const; | ||||
|  | ||||
|   int Load(); | ||||
|   int Save(); | ||||
|  | ||||
|  private: | ||||
|   // Default value should be same with parameters in localization.h | ||||
|   std::string config_path_; | ||||
|   std::string cert_file_path_; | ||||
|   CSimpleIniA ini_; | ||||
|   const char* section_ = "Settings"; | ||||
|  | ||||
|   LANGUAGE language_ = LANGUAGE::CHINESE; | ||||
|   VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM; | ||||
|   VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30; | ||||
|   VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1; | ||||
|   VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264; | ||||
|   bool hardware_video_codec_ = false; | ||||
|   bool enable_turn_ = false; | ||||
|   bool enable_srtp_ = false; | ||||
|   std::string server_host_ = "api.crossdesk.cn"; | ||||
|   int server_port_ = 9099; | ||||
|   std::string server_host_default_ = "api.crossdesk.cn"; | ||||
|   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
											
										
									
								
							
							
								
								
									
										63
									
								
								src/gui/assets/layouts/layout.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/gui/assets/layouts/layout.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-06-14 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _LAYOUT_STYLE_H_ | ||||
| #define _LAYOUT_STYLE_H_ | ||||
|  | ||||
| #define MENU_WINDOW_WIDTH_CN 300 | ||||
| #define MENU_WINDOW_HEIGHT_CN 280 | ||||
| #define LOCAL_WINDOW_WIDTH_CN 300 | ||||
| #define LOCAL_WINDOW_HEIGHT_CN 280 | ||||
| #define REMOTE_WINDOW_WIDTH_CN 300 | ||||
| #define REMOTE_WINDOW_HEIGHT_CN 280 | ||||
| #define MENU_WINDOW_WIDTH_EN 190 | ||||
| #define MENU_WINDOW_HEIGHT_EN 245 | ||||
| #define IPUT_WINDOW_WIDTH 160 | ||||
| #define INPUT_WINDOW_PADDING_CN 66 | ||||
| #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 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 165 | ||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 171 | ||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_TURN_CHECKBOX_PADDING_CN 171 | ||||
| #define ENABLE_TURN_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_SRTP_CHECKBOX_PADDING_CN 171 | ||||
| #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 | ||||
| #define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN 137 | ||||
| #define SETTINGS_SELECT_WINDOW_WIDTH 73 | ||||
| #define SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH 130 | ||||
| #define SETTINGS_OK_BUTTON_PADDING_CN 65 | ||||
| #define SETTINGS_OK_BUTTON_PADDING_EN 83 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91 | ||||
|  | ||||
| #endif | ||||
| @@ -8,6 +8,10 @@ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| #if _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
| namespace localization { | ||||
| 
 | ||||
| static std::vector<std::string> local_desktop = { | ||||
| @@ -91,10 +95,22 @@ static std::vector<std::string> enable_hardware_video_codec = { | ||||
|     "Enable Hardware Video Codec:"}; | ||||
| static std::vector<std::string> enable_turn = { | ||||
|     reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"}; | ||||
| 
 | ||||
| static std::vector<std::string> enable_srtp = { | ||||
|     reinterpret_cast<const char*>(u8"启用SRTP:"), "Enable SRTP:"}; | ||||
| 
 | ||||
| static std::vector<std::string> self_hosted_server_config = { | ||||
|     reinterpret_cast<const char*>(u8"自托管服务器配置"), | ||||
|     "Self-Hosted Server Config"}; | ||||
| static std::vector<std::string> self_hosted_server_settings = { | ||||
|     reinterpret_cast<const char*>(u8"自托管服务器设置"), | ||||
|     "Self-Hosted Server Settings"}; | ||||
| static std::vector<std::string> self_hosted_server_address = { | ||||
|     reinterpret_cast<const char*>(u8"服务器地址:"), "Server Address:"}; | ||||
| static std::vector<std::string> self_hosted_server_port = { | ||||
|     reinterpret_cast<const char*>(u8"服务器端口:"), "Server Port:"}; | ||||
| static std::vector<std::string> self_hosted_server_certificate_path = { | ||||
|     reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"}; | ||||
| static std::vector<std::string> select_a_file = { | ||||
|     reinterpret_cast<const char*>(u8"请选择文件"), "Please select a file"}; | ||||
| static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"), | ||||
|                                       "OK"}; | ||||
| static std::vector<std::string> cancel = { | ||||
| @@ -143,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 | ||||
| @@ -1,6 +1,6 @@ | ||||
| #include <random> | ||||
| 
 | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
| @@ -1,4 +1,4 @@ | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
| @@ -11,7 +11,7 @@ | ||||
| #include "device_controller_factory.h" | ||||
| #include "fa_regular_400.h" | ||||
| #include "fa_solid_900.h" | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "platform.h" | ||||
| #include "rd_log.h" | ||||
| @@ -171,7 +171,8 @@ Render::~Render() {} | ||||
| 
 | ||||
| int Render::SaveSettingsIntoCacheFile() { | ||||
|   cd_cache_mutex_.lock(); | ||||
|   std::ofstream cd_cache_file(cache_path_ + "/cache.cd", std::ios::binary); | ||||
|   std::ofstream cd_cache_file(cache_path_ + "/secure_cache.enc", | ||||
|                               std::ios::binary); | ||||
|   if (!cd_cache_file) { | ||||
|     cd_cache_mutex_.unlock(); | ||||
|     return -1; | ||||
| @@ -181,18 +182,6 @@ int Render::SaveSettingsIntoCacheFile() { | ||||
|          sizeof(cd_cache_.client_id_with_password)); | ||||
|   memcpy(cd_cache_.client_id_with_password, client_id_with_password_, | ||||
|          sizeof(client_id_with_password_)); | ||||
|   memcpy(&cd_cache_.language, &language_button_value_, | ||||
|          sizeof(language_button_value_)); | ||||
|   memcpy(&cd_cache_.video_quality, &video_quality_button_value_, | ||||
|          sizeof(video_quality_button_value_)); | ||||
|   memcpy(&cd_cache_.video_frame_rate, &video_frame_rate_button_value_, | ||||
|          sizeof(video_frame_rate_button_value_)); | ||||
|   memcpy(&cd_cache_.video_encode_format, &video_encode_format_button_value_, | ||||
|          sizeof(video_encode_format_button_value_)); | ||||
|   memcpy(&cd_cache_.enable_hardware_video_codec, &enable_hardware_video_codec_, | ||||
|          sizeof(enable_hardware_video_codec_)); | ||||
|   memcpy(&cd_cache_.enable_turn, &enable_turn_, sizeof(enable_turn_)); | ||||
|   memcpy(&cd_cache_.enable_srtp, &enable_srtp_, sizeof(enable_srtp_)); | ||||
|   memcpy(&cd_cache_.key, &aes128_key_, sizeof(aes128_key_)); | ||||
|   memcpy(&cd_cache_.iv, &aes128_iv_, sizeof(aes128_iv_)); | ||||
| 
 | ||||
| @@ -200,49 +189,19 @@ int Render::SaveSettingsIntoCacheFile() { | ||||
|   cd_cache_file.close(); | ||||
|   cd_cache_mutex_.unlock(); | ||||
| 
 | ||||
|   config_center_.SetLanguage((ConfigCenter::LANGUAGE)language_button_value_); | ||||
|   config_center_.SetVideoQuality( | ||||
|       (ConfigCenter::VIDEO_QUALITY)video_quality_button_value_); | ||||
|   config_center_.SetVideoFrameRate( | ||||
|       (ConfigCenter::VIDEO_FRAME_RATE)video_frame_rate_button_value_); | ||||
|   config_center_.SetVideoEncodeFormat( | ||||
|       (ConfigCenter::VIDEO_ENCODE_FORMAT)video_encode_format_button_value_); | ||||
|   config_center_.SetHardwareVideoCodec(enable_hardware_video_codec_); | ||||
|   config_center_.SetTurn(enable_turn_); | ||||
|   config_center_.SetSrtp(enable_srtp_); | ||||
| 
 | ||||
|   LOG_INFO("Save settings into cache file success"); | ||||
| 
 | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| int Render::LoadSettingsFromCacheFile() { | ||||
|   cd_cache_mutex_.lock(); | ||||
|   std::ifstream cd_cache_file(cache_path_ + "/cache.cd", std::ios::binary); | ||||
|   std::ifstream cd_cache_file(cache_path_ + "/secure_cache.enc", | ||||
|                               std::ios::binary); | ||||
|   if (!cd_cache_file) { | ||||
|     cd_cache_mutex_.unlock(); | ||||
| 
 | ||||
|     LOG_INFO("Init cache file by using default settings"); | ||||
|     memset(password_saved_, 0, sizeof(password_saved_)); | ||||
|     memset(aes128_key_, 0, sizeof(aes128_key_)); | ||||
|     memset(aes128_iv_, 0, sizeof(aes128_iv_)); | ||||
|     language_button_value_ = 0; | ||||
|     video_quality_button_value_ = 0; | ||||
|     video_encode_format_button_value_ = 1; | ||||
|     enable_hardware_video_codec_ = false; | ||||
|     enable_turn_ = false; | ||||
|     enable_srtp_ = false; | ||||
| 
 | ||||
|     config_center_.SetLanguage((ConfigCenter::LANGUAGE)language_button_value_); | ||||
|     config_center_.SetVideoQuality( | ||||
|         (ConfigCenter::VIDEO_QUALITY)video_quality_button_value_); | ||||
|     config_center_.SetVideoFrameRate( | ||||
|         (ConfigCenter::VIDEO_FRAME_RATE)video_frame_rate_button_value_); | ||||
|     config_center_.SetVideoEncodeFormat( | ||||
|         (ConfigCenter::VIDEO_ENCODE_FORMAT)video_encode_format_button_value_); | ||||
|     config_center_.SetHardwareVideoCodec(enable_hardware_video_codec_); | ||||
|     config_center_.SetTurn(enable_turn_); | ||||
|     config_center_.SetSrtp(enable_srtp_); | ||||
| 
 | ||||
|     thumbnail_.reset(); | ||||
|     thumbnail_ = std::make_unique<Thumbnail>(cache_path_ + "/thumbnails/"); | ||||
| @@ -289,13 +248,14 @@ int Render::LoadSettingsFromCacheFile() { | ||||
|   thumbnail_ = std::make_unique<Thumbnail>(cache_path_ + "/thumbnails/", | ||||
|                                            aes128_key_, aes128_iv_); | ||||
| 
 | ||||
|   language_button_value_ = cd_cache_.language; | ||||
|   video_quality_button_value_ = cd_cache_.video_quality; | ||||
|   video_frame_rate_button_value_ = cd_cache_.video_frame_rate; | ||||
|   video_encode_format_button_value_ = cd_cache_.video_encode_format; | ||||
|   enable_hardware_video_codec_ = cd_cache_.enable_hardware_video_codec; | ||||
|   enable_turn_ = cd_cache_.enable_turn; | ||||
|   enable_srtp_ = cd_cache_.enable_srtp; | ||||
|   language_button_value_ = (int)config_center_->GetLanguage(); | ||||
|   video_quality_button_value_ = (int)config_center_->GetVideoQuality(); | ||||
|   video_frame_rate_button_value_ = (int)config_center_->GetVideoFrameRate(); | ||||
|   video_encode_format_button_value_ = | ||||
|       (int)config_center_->GetVideoEncodeFormat(); | ||||
|   enable_hardware_video_codec_ = config_center_->IsHardwareVideoCodec(); | ||||
|   enable_turn_ = config_center_->IsEnableTurn(); | ||||
|   enable_srtp_ = config_center_->IsEnableSrtp(); | ||||
| 
 | ||||
|   language_button_value_last_ = language_button_value_; | ||||
|   video_quality_button_value_last_ = video_quality_button_value_; | ||||
| @@ -304,17 +264,6 @@ int Render::LoadSettingsFromCacheFile() { | ||||
|   enable_turn_last_ = enable_turn_; | ||||
|   enable_srtp_last_ = enable_srtp_; | ||||
| 
 | ||||
|   config_center_.SetLanguage((ConfigCenter::LANGUAGE)language_button_value_); | ||||
|   config_center_.SetVideoQuality( | ||||
|       (ConfigCenter::VIDEO_QUALITY)video_quality_button_value_); | ||||
|   config_center_.SetVideoFrameRate( | ||||
|       (ConfigCenter::VIDEO_FRAME_RATE)video_frame_rate_button_value_); | ||||
|   config_center_.SetVideoEncodeFormat( | ||||
|       (ConfigCenter::VIDEO_ENCODE_FORMAT)video_encode_format_button_value_); | ||||
|   config_center_.SetHardwareVideoCodec(enable_hardware_video_codec_); | ||||
|   config_center_.SetTurn(enable_turn_); | ||||
|   config_center_.SetSrtp(enable_srtp_); | ||||
| 
 | ||||
|   LOG_INFO("Load settings from cache file"); | ||||
| 
 | ||||
|   return 0; | ||||
| @@ -328,18 +277,21 @@ int Render::ScreenCapturerInit() { | ||||
|   last_frame_time_ = std::chrono::duration_cast<std::chrono::milliseconds>( | ||||
|                          std::chrono::steady_clock::now().time_since_epoch()) | ||||
|                          .count(); | ||||
|   int fps = config_center_.GetVideoFrameRate(); | ||||
|   int fps = config_center_->GetVideoFrameRate() == | ||||
|                     ConfigCenter::VIDEO_FRAME_RATE::FPS_30 | ||||
|                 ? 30 | ||||
|                 : 60; | ||||
|   LOG_INFO("Init screen capturer with {} fps", fps); | ||||
| 
 | ||||
|   int screen_capturer_init_ret = screen_capturer_->Init( | ||||
|       fps, | ||||
|       [this](unsigned char* data, int size, int width, int height, | ||||
|              const char* display_name) -> void { | ||||
|       [this, fps](unsigned char* data, int size, int width, int height, | ||||
|                   const char* display_name) -> void { | ||||
|         auto now_time = std::chrono::duration_cast<std::chrono::milliseconds>( | ||||
|                             std::chrono::steady_clock::now().time_since_epoch()) | ||||
|                             .count(); | ||||
|         auto duration = now_time - last_frame_time_; | ||||
|         if (duration * config_center_.GetVideoFrameRate() >= 1000) {  // ~60 FPS
 | ||||
|         if (duration * fps >= 1000) {  // ~60 FPS
 | ||||
|           XVideoFrame frame; | ||||
|           frame.data = (const char*)data; | ||||
|           frame.size = size; | ||||
| @@ -474,25 +426,53 @@ int Render::StopKeyboardCapturer() { | ||||
| 
 | ||||
| int Render::CreateConnectionPeer() { | ||||
|   params_.use_cfg_file = false; | ||||
|   params_.signal_server_ip = "api.crossdesk.cn"; | ||||
|   params_.signal_server_port = 9099; | ||||
|   params_.stun_server_ip = "150.158.81.30"; | ||||
| 
 | ||||
|   std::string server_ip; | ||||
|   int server_port; | ||||
|   std::string server_cert_path; | ||||
| 
 | ||||
|   if (config_center_->IsSelfHosted()) { | ||||
|     server_ip = config_center_->GetServerHost(); | ||||
|     server_port = config_center_->GetServerPort(); | ||||
|     server_cert_path = config_center_->GetCertFilePath(); | ||||
|   } else { | ||||
|     server_ip = config_center_->GetDefaultServerHost(); | ||||
|     server_port = config_center_->GetDefaultServerPort(); | ||||
|     server_cert_path = config_center_->GetDefaultCertFilePath(); | ||||
|   } | ||||
| 
 | ||||
|   strncpy((char*)params_.signal_server_ip, server_ip.c_str(), | ||||
|           sizeof(params_.signal_server_ip) - 1); | ||||
|   params_.signal_server_ip[sizeof(params_.signal_server_ip) - 1] = '\0'; | ||||
|   params_.signal_server_port = server_port; | ||||
|   strncpy((char*)params_.stun_server_ip, server_ip.c_str(), | ||||
|           sizeof(params_.stun_server_ip) - 1); | ||||
|   params_.stun_server_ip[sizeof(params_.stun_server_ip) - 1] = '\0'; | ||||
|   params_.stun_server_port = 3478; | ||||
|   params_.turn_server_ip = "150.158.81.30"; | ||||
|   strncpy((char*)params_.turn_server_ip, server_ip.c_str(), | ||||
|           sizeof(params_.turn_server_ip) - 1); | ||||
|   params_.turn_server_ip[sizeof(params_.turn_server_ip) - 1] = '\0'; | ||||
|   params_.turn_server_port = 3478; | ||||
|   params_.turn_server_username = "dijunkun"; | ||||
|   params_.turn_server_password = "dijunkunpw"; | ||||
|   params_.tls_cert_path = std::filesystem::exists(cert_path_) | ||||
|                               ? cert_path_.c_str() | ||||
|                               : "certs/crossdesk.cn_root.crt"; | ||||
|   params_.log_path = dll_log_path_.c_str(); | ||||
|   params_.hardware_acceleration = config_center_.IsHardwareVideoCodec(); | ||||
|   params_.av1_encoding = config_center_.GetVideoEncodeFormat() == | ||||
|   strncpy((char*)params_.turn_server_username, "dijunkun", | ||||
|           sizeof(params_.turn_server_username) - 1); | ||||
|   params_.turn_server_username[sizeof(params_.turn_server_username) - 1] = '\0'; | ||||
|   strncpy((char*)params_.turn_server_password, "dijunkunpw", | ||||
|           sizeof(params_.turn_server_password) - 1); | ||||
|   params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0'; | ||||
|   strncpy(params_.tls_cert_path, server_cert_path.c_str(), | ||||
|           sizeof(params_.tls_cert_path) - 1); | ||||
|   params_.tls_cert_path[sizeof(params_.tls_cert_path) - 1] = '\0'; | ||||
| 
 | ||||
|   strncpy(params_.log_path, dll_log_path_.c_str(), | ||||
|           sizeof(params_.log_path) - 1); | ||||
|   params_.log_path[sizeof(params_.log_path) - 1] = '\0'; | ||||
|   params_.hardware_acceleration = config_center_->IsHardwareVideoCodec(); | ||||
|   params_.av1_encoding = config_center_->GetVideoEncodeFormat() == | ||||
|                                  ConfigCenter::VIDEO_ENCODE_FORMAT::AV1 | ||||
|                              ? true | ||||
|                              : false; | ||||
|   params_.enable_turn = config_center_.IsEnableTurn(); | ||||
|   params_.enable_srtp = config_center_.IsEnableSrtp(); | ||||
|   params_.enable_turn = config_center_->IsEnableTurn(); | ||||
|   params_.enable_srtp = config_center_->IsEnableSrtp(); | ||||
|   params_.on_receive_video_buffer = nullptr; | ||||
|   params_.on_receive_audio_buffer = OnReceiveAudioBufferCb; | ||||
|   params_.on_receive_data_buffer = OnReceiveDataBufferCb; | ||||
| @@ -608,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; | ||||
| } | ||||
| 
 | ||||
| @@ -890,6 +881,20 @@ int Render::Run() { | ||||
|     exec_log_path_ = path_manager_->GetLogPath().string(); | ||||
|     dll_log_path_ = path_manager_->GetLogPath().string(); | ||||
|     cache_path_ = path_manager_->GetCachePath().string(); | ||||
|     config_center_ = | ||||
|         std::make_unique<ConfigCenter>(cache_path_ + "/config.ini", cert_path_); | ||||
|     strncpy(signal_server_ip_tmp_, config_center_->GetServerHost().c_str(), | ||||
|             sizeof(signal_server_ip_tmp_) - 1); | ||||
|     signal_server_ip_tmp_[sizeof(signal_server_ip_tmp_) - 1] = '\0'; | ||||
|     strncpy(signal_server_port_tmp_, | ||||
|             std::to_string(config_center_->GetServerPort()).c_str(), | ||||
|             sizeof(signal_server_port_tmp_) - 1); | ||||
|     signal_server_port_tmp_[sizeof(signal_server_port_tmp_) - 1] = '\0'; | ||||
|     strncpy(cert_file_path_, cert_path_.c_str(), sizeof(cert_file_path_) - 1); | ||||
|     cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; | ||||
|   } else { | ||||
|     std::cerr << "Failed to create PathManager" << std::endl; | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   InitializeLogger(); | ||||
| @@ -974,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: | ||||
| @@ -152,6 +155,8 @@ class Render { | ||||
|   int RemoteWindow(); | ||||
|   int RecentConnectionsWindow(); | ||||
|   int SettingWindow(); | ||||
|   int SelfHostedServerWindow(); | ||||
|   int ShowSimpleFileBrowser(); | ||||
|   int ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|   int ControlBar(std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|   int AboutWindow(); | ||||
| @@ -261,7 +266,7 @@ class Render { | ||||
|  private: | ||||
|   CDCache cd_cache_; | ||||
|   std::mutex cd_cache_mutex_; | ||||
|   ConfigCenter config_center_; | ||||
|   std::unique_ptr<ConfigCenter> config_center_; | ||||
|   ConfigCenter::LANGUAGE localization_language_ = | ||||
|       ConfigCenter::LANGUAGE::CHINESE; | ||||
|   std::unique_ptr<PathManager> path_manager_; | ||||
| @@ -296,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; | ||||
| @@ -333,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; | ||||
| @@ -383,6 +391,7 @@ class Render { | ||||
|   bool password_validating_ = false; | ||||
|   uint32_t password_validating_time_ = 0; | ||||
|   bool show_settings_window_ = false; | ||||
|   bool show_self_hosted_server_config_window_ = false; | ||||
|   bool rejoin_ = false; | ||||
|   bool local_id_copied_ = false; | ||||
|   bool show_password_ = true; | ||||
| @@ -431,13 +440,24 @@ class Render { | ||||
|   bool enable_hardware_video_codec_ = false; | ||||
|   bool enable_turn_ = false; | ||||
|   bool enable_srtp_ = false; | ||||
|   char signal_server_ip_[256] = "api.crossdesk.cn"; | ||||
|   char signal_server_port_[6] = "9099"; | ||||
|   char cert_file_path_[256] = ""; | ||||
|   bool enable_self_hosted_server_ = false; | ||||
|   int language_button_value_last_ = 0; | ||||
|   int video_quality_button_value_last_ = 0; | ||||
|   int video_encode_format_button_value_last_ = 0; | ||||
|   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; | ||||
|   bool self_hosted_server_config_window_pos_reset_ = true; | ||||
|   std::string selected_current_file_path_ = ""; | ||||
|   std::string selected_file_ = ""; | ||||
|   /* ------ main window property end ------ */ | ||||
| 
 | ||||
|   /* ------ sub stream window property start ------ */ | ||||
| @@ -1,4 +1,4 @@ | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
| @@ -57,6 +57,7 @@ int Render::TitleBar(bool main_window) { | ||||
| 
 | ||||
|       { | ||||
|         SettingWindow(); | ||||
|         SelfHostedServerWindow(); | ||||
|         AboutWindow(); | ||||
|       } | ||||
|     } | ||||
| @@ -138,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 | ||||
| @@ -1,4 +1,4 @@ | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
| @@ -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); | ||||
| @@ -1,4 +1,4 @@ | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
| @@ -1,4 +1,4 @@ | ||||
| #include "layout_style.h" | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
| @@ -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()); | ||||
| @@ -108,10 +108,10 @@ int Render::SettingWindow() { | ||||
|       ImGui::Separator(); | ||||
| 
 | ||||
|       { | ||||
|         const char* video_frame_rate_items[] = {"30", "60"}; | ||||
|         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()); | ||||
| @@ -133,11 +133,11 @@ int Render::SettingWindow() { | ||||
| 
 | ||||
|       { | ||||
|         const char* video_encode_format_items[] = { | ||||
|             localization::av1[localization_language_index_].c_str(), | ||||
|             localization::h264[localization_language_index_].c_str()}; | ||||
|             localization::h264[localization_language_index_].c_str(), | ||||
|             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()); | ||||
| @@ -211,6 +211,48 @@ int Render::SettingWindow() { | ||||
|         ImGui::Checkbox("##enable_srtp", &enable_srtp_); | ||||
|       } | ||||
| 
 | ||||
|       ImGui::Separator(); | ||||
| 
 | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 1); | ||||
| 
 | ||||
|         if (ImGui::Button(localization::self_hosted_server_config | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str())) { | ||||
|           show_self_hosted_server_config_window_ = true; | ||||
|         } | ||||
| 
 | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         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(); | ||||
|       } | ||||
| @@ -221,7 +263,7 @@ int Render::SettingWindow() { | ||||
|         ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN); | ||||
|       } | ||||
| 
 | ||||
|       settings_items_offset += settings_items_padding; | ||||
|       settings_items_offset += settings_items_padding + 10; | ||||
|       ImGui::SetCursorPosY(settings_items_offset); | ||||
|       ImGui::PopStyleVar(); | ||||
| 
 | ||||
| @@ -229,12 +271,13 @@ int Render::SettingWindow() { | ||||
|       if (ImGui::Button( | ||||
|               localization::ok[localization_language_index_].c_str())) { | ||||
|         show_settings_window_ = false; | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
| 
 | ||||
|         // Language
 | ||||
|         if (language_button_value_ == 0) { | ||||
|           config_center_.SetLanguage(ConfigCenter::LANGUAGE::CHINESE); | ||||
|           config_center_->SetLanguage(ConfigCenter::LANGUAGE::CHINESE); | ||||
|         } else { | ||||
|           config_center_.SetLanguage(ConfigCenter::LANGUAGE::ENGLISH); | ||||
|           config_center_->SetLanguage(ConfigCenter::LANGUAGE::ENGLISH); | ||||
|         } | ||||
|         language_button_value_last_ = language_button_value_; | ||||
|         localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_; | ||||
| @@ -244,50 +287,55 @@ int Render::SettingWindow() { | ||||
| 
 | ||||
|         // Video quality
 | ||||
|         if (video_quality_button_value_ == 0) { | ||||
|           config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH); | ||||
|           config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH); | ||||
|         } else if (video_quality_button_value_ == 1) { | ||||
|           config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM); | ||||
|           config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM); | ||||
|         } else { | ||||
|           config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW); | ||||
|           config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW); | ||||
|         } | ||||
|         video_quality_button_value_last_ = video_quality_button_value_; | ||||
| 
 | ||||
|         // Video encode format
 | ||||
|         if (video_encode_format_button_value_ == 0) { | ||||
|           config_center_.SetVideoEncodeFormat( | ||||
|               ConfigCenter::VIDEO_ENCODE_FORMAT::AV1); | ||||
|         } else if (video_encode_format_button_value_ == 1) { | ||||
|           config_center_.SetVideoEncodeFormat( | ||||
|           config_center_->SetVideoEncodeFormat( | ||||
|               ConfigCenter::VIDEO_ENCODE_FORMAT::H264); | ||||
|         } else if (video_encode_format_button_value_ == 1) { | ||||
|           config_center_->SetVideoEncodeFormat( | ||||
|               ConfigCenter::VIDEO_ENCODE_FORMAT::AV1); | ||||
|         } | ||||
|         video_encode_format_button_value_last_ = | ||||
|             video_encode_format_button_value_; | ||||
| 
 | ||||
|         // Hardware video codec
 | ||||
|         if (enable_hardware_video_codec_) { | ||||
|           config_center_.SetHardwareVideoCodec(true); | ||||
|           config_center_->SetHardwareVideoCodec(true); | ||||
|         } else { | ||||
|           config_center_.SetHardwareVideoCodec(false); | ||||
|           config_center_->SetHardwareVideoCodec(false); | ||||
|         } | ||||
|         enable_hardware_video_codec_last_ = enable_hardware_video_codec_; | ||||
| 
 | ||||
|         // TURN mode
 | ||||
|         if (enable_turn_) { | ||||
|           config_center_.SetTurn(true); | ||||
|           config_center_->SetTurn(true); | ||||
|         } else { | ||||
|           config_center_.SetTurn(false); | ||||
|           config_center_->SetTurn(false); | ||||
|         } | ||||
|         enable_turn_last_ = enable_turn_; | ||||
| 
 | ||||
|         // SRTP
 | ||||
|         if (enable_srtp_) { | ||||
|           config_center_.SetSrtp(true); | ||||
|           config_center_->SetSrtp(true); | ||||
|         } else { | ||||
|           config_center_.SetSrtp(false); | ||||
|           config_center_->SetSrtp(false); | ||||
|         } | ||||
|         enable_srtp_last_ = enable_srtp_; | ||||
| 
 | ||||
|         SaveSettingsIntoCacheFile(); | ||||
|         if (enable_self_hosted_server_) { | ||||
|           config_center_->SetSelfHosted(true); | ||||
|         } else { | ||||
|           config_center_->SetSelfHosted(false); | ||||
|         } | ||||
| 
 | ||||
|         settings_window_pos_reset_ = true; | ||||
| 
 | ||||
|         // Recreate peer instance
 | ||||
| @@ -306,6 +354,8 @@ int Render::SettingWindow() { | ||||
|       if (ImGui::Button( | ||||
|               localization::cancel[localization_language_index_].c_str())) { | ||||
|         show_settings_window_ = false; | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
| 
 | ||||
|         if (language_button_value_ != language_button_value_last_) { | ||||
|           language_button_value_ = language_button_value_last_; | ||||
|         } | ||||
							
								
								
									
										270
									
								
								src/gui/windows/server_settings_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								src/gui/windows/server_settings_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,270 @@ | ||||
| #include <filesystem> | ||||
| #include <vector> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| std::vector<std::string> GetRootEntries() { | ||||
|   std::vector<std::string> roots; | ||||
| #ifdef _WIN32 | ||||
|   DWORD mask = GetLogicalDrives(); | ||||
|   for (char letter = 'A'; letter <= 'Z'; ++letter) { | ||||
|     if (mask & 1) { | ||||
|       roots.push_back(std::string(1, letter) + ":\\"); | ||||
|     } | ||||
|     mask >>= 1; | ||||
|   } | ||||
| #else | ||||
|   roots.push_back("/"); | ||||
| #endif | ||||
|   return roots; | ||||
| } | ||||
|  | ||||
| int Render::ShowSimpleFileBrowser() { | ||||
|   std::string display_text; | ||||
|   if (!selected_file_.empty()) { | ||||
|     display_text = std::filesystem::path(selected_file_).filename().string(); | ||||
|   } else if (selected_current_file_path_ != "Root") { | ||||
|     display_text = | ||||
|         std::filesystem::path(selected_current_file_path_).filename().string(); | ||||
|     if (display_text.empty()) { | ||||
|       display_text = selected_current_file_path_; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (display_text.empty()) { | ||||
|     display_text = | ||||
|         localization::select_a_file[localization_language_index_].c_str(); | ||||
|   } | ||||
|  | ||||
|   if (ImGui::BeginCombo("##select_a_file", display_text.c_str())) { | ||||
|     if (selected_current_file_path_ == "Root" || | ||||
|         !std::filesystem::exists(selected_current_file_path_) || | ||||
|         !std::filesystem::is_directory(selected_current_file_path_)) { | ||||
|       auto roots = GetRootEntries(); | ||||
|       for (const auto& root : roots) { | ||||
|         if (ImGui::Selectable(root.c_str())) { | ||||
|           selected_current_file_path_ = root; | ||||
|           selected_file_.clear(); | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       std::filesystem::path p(selected_current_file_path_); | ||||
|  | ||||
|       if (ImGui::Selectable("..")) { | ||||
|         if (p.has_parent_path() && p != p.root_path()) | ||||
|           selected_current_file_path_ = p.parent_path().string(); | ||||
|         else | ||||
|           selected_current_file_path_ = "Root"; | ||||
|         selected_file_.clear(); | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         for (const auto& entry : | ||||
|              std::filesystem::directory_iterator(selected_current_file_path_)) { | ||||
|           std::string name = entry.path().filename().string(); | ||||
|           if (entry.is_directory()) { | ||||
|             if (ImGui::Selectable(name.c_str())) { | ||||
|               selected_current_file_path_ = entry.path().string(); | ||||
|               selected_file_.clear(); | ||||
|             } | ||||
|           } else { | ||||
|             if (ImGui::Selectable(name.c_str())) { | ||||
|               selected_file_ = entry.path().string(); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } catch (const std::exception& e) { | ||||
|         ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", e.what()); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ImGui::EndCombo(); | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::SelfHostedServerWindow() { | ||||
|   if (show_self_hosted_server_config_window_) { | ||||
|     if (self_hosted_server_config_window_pos_reset_) { | ||||
|       const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN, | ||||
|                    SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN)); | ||||
|       } else { | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN, | ||||
|                    SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN)); | ||||
|       } | ||||
|  | ||||
|       self_hosted_server_config_window_pos_reset_ = false; | ||||
|     } | ||||
|  | ||||
|     // Settings | ||||
|     { | ||||
|       static int settings_items_padding = 30; | ||||
|       int settings_items_offset = 0; | ||||
|  | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|       ImGui::Begin(localization::self_hosted_server_settings | ||||
|                        [localization_language_index_] | ||||
|                            .c_str(), | ||||
|                    nullptr, | ||||
|                    ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                        ImGuiWindowFlags_NoSavedSettings); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::Text("%s", localization::self_hosted_server_address | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str()); | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::InputText("##signal_server_ip_tmp_", signal_server_ip_tmp_, | ||||
|                          IM_ARRAYSIZE(signal_server_ip_tmp_), | ||||
|                          ImGuiInputTextFlags_AlwaysOverwrite); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::self_hosted_server_port[localization_language_index_] | ||||
|                 .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::InputText("##signal_server_port_tmp_", signal_server_port_tmp_, | ||||
|                          IM_ARRAYSIZE(signal_server_port_tmp_)); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::Text("%s", localization::self_hosted_server_certificate_path | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); | ||||
|  | ||||
|         ShowSimpleFileBrowser(); | ||||
|       } | ||||
|  | ||||
|       if (stream_window_inited_) { | ||||
|         ImGui::EndDisabled(); | ||||
|       } | ||||
|  | ||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|         ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN); | ||||
|       } else { | ||||
|         ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN); | ||||
|       } | ||||
|  | ||||
|       settings_items_offset += settings_items_padding + 10; | ||||
|       ImGui::SetCursorPosY(settings_items_offset); | ||||
|       ImGui::PopStyleVar(); | ||||
|  | ||||
|       // OK | ||||
|       if (ImGui::Button( | ||||
|               localization::ok[localization_language_index_].c_str())) { | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
|  | ||||
|         config_center_->SetServerHost(signal_server_ip_tmp_); | ||||
|         config_center_->SetServerPort(atoi(signal_server_port_tmp_)); | ||||
|         config_center_->SetCertFilePath(selected_file_); | ||||
|         strncpy(signal_server_ip_, signal_server_ip_tmp_, | ||||
|                 sizeof(signal_server_ip_) - 1); | ||||
|         signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0'; | ||||
|         strncpy(signal_server_port_, signal_server_port_tmp_, | ||||
|                 sizeof(signal_server_port_) - 1); | ||||
|         signal_server_port_[sizeof(signal_server_port_) - 1] = '\0'; | ||||
|         strncpy(cert_file_path_, selected_file_.c_str(), | ||||
|                 sizeof(cert_file_path_) - 1); | ||||
|         cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; | ||||
|  | ||||
|         self_hosted_server_config_window_pos_reset_ = true; | ||||
|       } | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|       // Cancel | ||||
|       if (ImGui::Button( | ||||
|               localization::cancel[localization_language_index_].c_str())) { | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
|         self_hosted_server_config_window_pos_reset_ = true; | ||||
|  | ||||
|         strncpy(signal_server_ip_tmp_, signal_server_ip_, | ||||
|                 sizeof(signal_server_ip_tmp_) - 1); | ||||
|         signal_server_ip_tmp_[sizeof(signal_server_ip_tmp_) - 1] = '\0'; | ||||
|         strncpy(signal_server_port_tmp_, signal_server_port_, | ||||
|                 sizeof(signal_server_port_tmp_) - 1); | ||||
|         signal_server_port_tmp_[sizeof(signal_server_port_tmp_) - 1] = '\0'; | ||||
|         config_center_->SetServerHost(signal_server_ip_tmp_); | ||||
|         config_center_->SetServerPort(atoi(signal_server_port_tmp_)); | ||||
|         selected_file_.clear(); | ||||
|       } | ||||
|  | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::End(); | ||||
|       ImGui::PopStyleVar(2); | ||||
|       ImGui::PopStyleColor(); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-06-14 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _LAYOUT_STYLE_H_ | ||||
| #define _LAYOUT_STYLE_H_ | ||||
|  | ||||
| #define MENU_WINDOW_WIDTH_CN 300 | ||||
| #define MENU_WINDOW_HEIGHT_CN 280 | ||||
| #define LOCAL_WINDOW_WIDTH_CN 300 | ||||
| #define LOCAL_WINDOW_HEIGHT_CN 280 | ||||
| #define REMOTE_WINDOW_WIDTH_CN 300 | ||||
| #define REMOTE_WINDOW_HEIGHT_CN 280 | ||||
| #define MENU_WINDOW_WIDTH_EN 190 | ||||
| #define MENU_WINDOW_HEIGHT_EN 245 | ||||
| #define IPUT_WINDOW_WIDTH 160 | ||||
| #define INPUT_WINDOW_PADDING_CN 66 | ||||
| #define INPUT_WINDOW_PADDING_EN 96 | ||||
| #define SETTINGS_WINDOW_WIDTH_CN 182 | ||||
| #define SETTINGS_WINDOW_WIDTH_EN 248 | ||||
| #define SETTINGS_WINDOW_HEIGHT_CN 275 | ||||
| #define SETTINGS_WINDOW_HEIGHT_EN 275 | ||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_CN 100 | ||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 100 | ||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 100 | ||||
| #define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 100 | ||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 151 | ||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_TURN_CHECKBOX_PADDING_CN 151 | ||||
| #define ENABLE_TURN_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_SRTP_CHECKBOX_PADDING_CN 151 | ||||
| #define ENABLE_SRTP_CHECKBOX_PADDING_EN 218 | ||||
| #define SETTINGS_SELECT_WINDOW_WIDTH 73 | ||||
| #define SETTINGS_OK_BUTTON_PADDING_CN 55 | ||||
| #define SETTINGS_OK_BUTTON_PADDING_EN 78 | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										2
									
								
								thirdparty/minirtc
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								thirdparty/minirtc
									
									
									
									
										vendored
									
									
								
							 Submodule thirdparty/minirtc updated: dd3f6df18b...40eaf93b42
									
								
							
							
								
								
									
										46
									
								
								xmake.lua
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								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") | ||||
| @@ -120,26 +123,43 @@ target("device_controller") | ||||
|          "src/device_controller/keyboard/linux", {public = true}) | ||||
|     end | ||||
|  | ||||
| target("thumbnail") | ||||
|     set_kind("object") | ||||
|     add_packages("libyuv", "openssl3") | ||||
|     add_deps("rd_log", "common") | ||||
|     add_files("src/thumbnail/*.cpp") | ||||
|     add_includedirs("src/thumbnail", {public = true}) | ||||
|  | ||||
| target("config_center") | ||||
|     set_kind("object") | ||||
|     add_deps("rd_log") | ||||
|     add_files("src/config_center/*.cpp") | ||||
|     add_includedirs("src/config_center", {public = true}) | ||||
|  | ||||
| target("localization") | ||||
| target("assets") | ||||
|     set_kind("headeronly") | ||||
|     add_includedirs("src/localization", {public = true}) | ||||
|     add_includedirs("src/gui/assets/localization",  | ||||
|         "src/gui/assets/fonts",  | ||||
|         "src/gui/assets/icons", | ||||
|         "src/gui/assets/layouts", {public = true}) | ||||
|  | ||||
| target("single_window") | ||||
| target("gui") | ||||
|     set_kind("object") | ||||
|     add_packages("libyuv", "openssl3") | ||||
|     add_deps("rd_log", "common", "localization", "config_center", "minirtc",  | ||||
|         "path_manager", "screen_capturer", "speaker_capturer", "device_controller") | ||||
|     add_files("src/single_window/*.cpp") | ||||
|     add_includedirs("src/single_window", {public = true}) | ||||
|     add_includedirs("fonts", {public = true}) | ||||
|     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", | ||||
|         "src/gui/windows/*.cpp") | ||||
|     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") | ||||
|     add_deps("rd_log", "common", "single_window") | ||||
|     add_files("src/gui/main.cpp") | ||||
|     add_deps("rd_log", "common", "gui") | ||||
|     add_files("src/app/main.cpp") | ||||
		Reference in New Issue
	
	Block a user