Compare commits

...

38 Commits

Author SHA1 Message Date
dijunkun
18f4973d0a [fix] remove duplicate 'v' prefix in GitHub release name 2025-11-14 16:10:00 +08:00
dijunkun
37ede5861c [fix] update MiniRTC to fix crash when multiple peers join to remote server 2025-11-14 14:37:15 +08:00
dijunkun
497454ac51 [ci] remove 'v' prefix from Debian package version 2025-11-13 15:04:24 +08:00
dijunkun
4ebb7a6a4d [chore] update version format 2025-11-13 14:32:35 +08:00
dijunkun
7d1910df71 [ci] append build date to artifact names and write download URLs to version.json 2025-11-13 14:13:54 +08:00
dijunkun
52e70a26f3 [chore] update README 2025-11-13 00:06:14 +08:00
dijunkun
adb6cee326 [fix] fix version check issue 2025-11-12 00:21:18 +08:00
dijunkun
941b5e5cdc [ci] update version.json when new tag pushed 2025-11-12 00:11:32 +08:00
dijunkun
c602dea58f [chore] update README 2025-11-11 22:30:17 +08:00
dijunkun
e9ced9fa4f [ci] switch macos-13 to macos-15-intel due to the macOS-13 based runner images are being deprecated 2025-11-11 22:28:35 +08:00
dijunkun
6ab5e7487f [chore] update README 2025-11-11 21:59:50 +08:00
dijunkun
e3143f3e7a [feat] add automatic version check 2025-11-11 15:38:31 +08:00
dijunkun
538c17d182 [feat] disable AV1 encoding when connected to a Web client 2025-11-11 10:52:28 +08:00
dijunkun
2ad32ec2b4 [feat] add VideoQuality configuration option to limit maximum video resolution 2025-11-11 10:07:35 +08:00
dijunkun
b28f1dca81 [feat] update MiniRTC module 2025-11-11 01:32:40 +08:00
dijunkun
6947f7e1c3 Merge branch 'self-hosted-server' into libdatachannel 2025-11-11 01:18:24 +08:00
dijunkun
b1df10c0de [fix] fix frame rate and video quality settings not being applied correctly, fixes #24 2025-11-11 01:11:33 +08:00
dijunkun
3817b222fd Merge branch 'libdatachannel' of https://github.com/kunkundi/crossdesk into libdatachannel 2025-11-11 01:00:19 +08:00
dijunkun
910cc9b587 [fix] stop devices control and capture when client peer destroyed 2025-11-11 00:59:54 +08:00
dijunkun
2ee3e93afe [feat] do not control mouse in debug mode 2025-11-11 00:56:23 +08:00
dijunkun
8875c6a6a1 [feat] update MiniRTC module 2025-11-11 00:53:10 +08:00
dijunkun
28062f5574 [config] use 60 fps streaming by default 2025-11-10 17:03:14 +08:00
dijunkun
017af3eea4 [feat] support multiple web clients connecting simultaneously 2025-11-10 16:54:56 +08:00
dijunkun
78eb069cc8 [feat] enable speaker capturer by default 2025-11-10 15:02:21 +08:00
dijunkun
0d591f769d [feat] send control commands via JSON 2025-11-06 17:31:06 +08:00
dijunkun
d4726355a7 [feat] update minirtc: support web streaming by using libdatachannel 2025-11-06 02:54:03 +08:00
dijunkun
b78c9cf7d1 [feat] optimize Windows display device names 2025-11-05 20:36:04 +08:00
dijunkun
b3132db785 [fix] resolve issue where configuration settings were not saved 2025-11-03 10:55:19 +08:00
dijunkun
43db021326 [feat] use CROSSDESK_DEBUG compile-time variable to control config file path 2025-10-31 17:31:50 +08:00
dijunkun
2c622bc76e [ci] update GitHub Actions to use macos-13 runner instead of macos-15 for Intel builds 2025-10-27 22:31:23 +08:00
dijunkun
b790c7d08e [chore] update README 2025-10-27 22:11:30 +08:00
dijunkun
0ca90d2516 [chore] move minirtc into submodules 2025-10-27 21:36:11 +08:00
dijunkun
401bfe4483 [refactor] add namespace 'crossdesk' to codebase 2025-10-27 21:09:39 +08:00
dijunkun
3b34c26555 [feat] enable custom configuration of Coturn server port 2025-10-27 16:04:40 +08:00
dijunkun
b668b3c936 [chore] update README 2025-10-27 13:42:49 +08:00
dijunkun
cc19ec125a [ci] update close-issue.yml 2025-10-27 11:04:36 +08:00
dijunkun
ffa77fdf44 Merge branch 'run-in-bg' into self-hosted-server 2025-10-27 10:41:16 +08:00
dijunkun
47cf806532 [ci] add permissions to close-issue.yml 2025-10-25 17:06:14 +08:00
87 changed files with 3084 additions and 2441 deletions

View File

@@ -35,12 +35,14 @@ jobs:
id: set_deb_version id: set_deb_version
run: | run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}" LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
else else
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}" LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
fi fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -99,12 +101,14 @@ jobs:
id: set_deb_version id: set_deb_version
run: | run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}" LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
else else
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}" LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
fi fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -160,9 +164,11 @@ jobs:
run: | run: |
VERSION="${GITHUB_REF##*/}" VERSION="${GITHUB_REF##*/}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
VERSION_NUM="${VERSION#v}-${SHORT_SHA}" BUILD_DATE=$(date +%Y%m%d)
VERSION_NUM="v${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}" echo "VERSION_NUM=${VERSION_NUM}"
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
- name: Cache xmake dependencies - name: Cache xmake dependencies
uses: actions/cache@v4 uses: actions/cache@v4
@@ -223,7 +229,9 @@ jobs:
$version = $version -replace '^v', '' $version = $version -replace '^v', ''
$version = $version -replace '/', '-' $version = $version -replace '/', '-'
$SHORT_SHA = $env:GITHUB_SHA.Substring(0,7) $SHORT_SHA = $env:GITHUB_SHA.Substring(0,7)
echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV $BUILD_DATE = Get-Date -Format "yyyyMMdd"
echo "VERSION_NUM=v$version-$BUILD_DATE-$SHORT_SHA" >> $env:GITHUB_ENV
echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV
- name: Cache xmake dependencies - name: Cache xmake dependencies
uses: actions/cache@v4 uses: actions/cache@v4
@@ -292,7 +300,7 @@ jobs:
run: | run: |
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y
xmake b -vy crossdesk xmake b -vy crossdesk
- name: Decode and save certificate - name: Decode and save certificate
shell: powershell shell: powershell
run: | run: |
@@ -332,17 +340,25 @@ jobs:
run: | run: |
VERSION="${GITHUB_REF##*/}" VERSION="${GITHUB_REF##*/}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
VERSION_NUM="${VERSION#v}-${SHORT_SHA}" BUILD_DATE=$(date +%Y%m%d)
BUILD_DATE_ISO=$(date +%Y-%m-%d)
VERSION_NUM="${VERSION#v}-${BUILD_DATE}-${SHORT_SHA}"
VERSION_WITH_V="v${VERSION_NUM}"
VERSION_ONLY="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_OUTPUT
echo "VERSION_ONLY=${VERSION_ONLY}" >> $GITHUB_OUTPUT
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
- name: Rename artifacts - name: Rename artifacts
run: | run: |
mkdir -p release mkdir -p release
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}.pkg cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}.pkg cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}.deb cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb
cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}.deb cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}.exe cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe
- name: List release files - name: List release files
run: ls -lh release/ run: ls -lh release/
@@ -350,8 +366,8 @@ jobs:
- name: Upload to Versioned GitHub Release - name: Upload to Versioned GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: v${{ steps.version.outputs.VERSION_NUM }} tag_name: ${{ steps.version.outputs.VERSION_WITH_V }}
name: Release v${{ steps.version.outputs.VERSION_NUM }} name: Release ${{ steps.version.outputs.VERSION_WITH_V }}
draft: false draft: false
prerelease: false prerelease: false
files: release/* files: release/*
@@ -385,3 +401,45 @@ jobs:
remote_host: ${{ secrets.SERVER_HOST }} remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }} remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }} remote_key: ${{ secrets.SERVER_KEY }}
- name: Generate version.json
run: |
cat > version.json << EOF
{
"version": "${{ steps.version.outputs.VERSION_ONLY }}",
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
"downloads": {
"windows-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe",
"filename": "crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe"
},
"macos-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg",
"filename": "crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg"
},
"macos-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg",
"filename": "crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg"
},
"linux-amd64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb",
"filename": "crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
},
"linux-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb",
"filename": "crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
}
}
}
EOF
cat version.json
- name: Upload version.json to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --delete
path: version.json
remote_path: /var/www/html/version/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}

View File

@@ -5,6 +5,11 @@ on:
# run every day at midnight # run every day at midnight
- cron: "0 0 * * *" - cron: "0 0 * * *"
permissions:
issues: write
pull-requests: write
contents: read
jobs: jobs:
close_inactive_issues: close_inactive_issues:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -53,6 +58,14 @@ jobs:
// check inactivity // check inactivity
if (now - lastActivityTime > inactivePeriod) { if (now - lastActivityTime > inactivePeriod) {
console.log(`Closing inactive issue: #${issue.number} (No recent replies for 7 days)`); console.log(`Closing inactive issue: #${issue.number} (No recent replies for 7 days)`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: "This issue has been automatically closed due to inactivity for 7 days."
});
await github.rest.issues.update({ await github.rest.issues.update({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,

View File

@@ -1,49 +0,0 @@
name: Update GitHub Pages Downloads
on:
push:
tags:
- "v*"
jobs:
update-pages:
runs-on: ubuntu-latest
steps:
- name: Checkout CrossDesk repo
uses: actions/checkout@v4
- name: Set version number
id: version
run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
VERSION_NUM="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION_NUM#v}-${SHORT_SHA}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
- name: Checkout Pages repo
uses: actions/checkout@v4
with:
repository: kunkundi/kunkundi.github.io
token: ${{ secrets.GH_PAGES_PAT }}
path: pages
- name: Update download links
run: |
cd pages
sed -E -i "s/crossdesk-win-x64-[0-9]+\.[0-9]+\.[0-9]+\.exe/crossdesk-win-x64-${VERSION_NUM}.exe/g" index.html
sed -E -i "s/crossdesk-macos-x64-[0-9]+\.[0-9]+\.[0-9]+\.pkg/crossdesk-macos-x64-${VERSION_NUM}.pkg/g" index.html
sed -E -i "s/crossdesk-macos-arm64-[0-9]+\.[0-9]+\.[0-9]+\.pkg/crossdesk-macos-arm64-${VERSION_NUM}.pkg/g" index.html
sed -E -i "s/crossdesk-linux-amd64-[0-9]+\.[0-9]+\.[0-9]+\.deb/crossdesk-linux-amd64-${VERSION_NUM}.deb/g" index.html
sed -E -i "s/crossdesk-linux-arm64-[0-9]+\.[0-9]+\.[0-9]+\.deb/crossdesk-linux-arm64-${VERSION_NUM}.deb/g" index.html
- name: Commit & Push changes
run: |
cd pages
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add index.html
git commit -m "Update download links to v${VERSION_NUM}" || echo "No changes to commit"
git push origin main
env:
VERSION_NUM: ${{ env.VERSION_NUM }}

4
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "thirdparty/minirtc"] [submodule "submodules/minirtc"]
path = thirdparty/minirtc path = submodules/minirtc
url = https://github.com/kunkundi/minirtc.git url = https://github.com/kunkundi/minirtc.git

View File

@@ -1,8 +1,8 @@
# CrossDesk # CrossDesk
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)]() [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen.svg)]()
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server) [![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/web-client)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions) [![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags) [![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]() [![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
@@ -15,10 +15,17 @@
## 简介 ## 简介
CrossDesk 是一个轻量级的跨平台远程桌面软件。 CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备
CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)视频软硬编解码H264/AV1音频编解码[Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。 CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)视频软硬编解码H264/AV1音频编解码[Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。
## 系统要求
| 平台 | 最低版本 |
|----------------|---------------------------|
| **Windows** | Windows 10 及以上 (64 位) |
| **macOS** | macOS Intel 15.0 及以上 ( 大于 14.0 小于 15.0 的版本可自行编译实现兼容 )<br> macOS Apple Silicon 14.0 及以上 |
| **Linux** | Ubuntu 22.04 及以上 ( 低版本可自行编译实现兼容 ) |
## 使用 ## 使用
@@ -33,6 +40,12 @@ CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频
发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。 发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c) ![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
### Web 客户端
浏览器访问 [CrossDesk Web Client](https://web.crossdesk.cn/)。
输入 **远程设备 ID****密码**,点击连接即可接入远程设备。如图,**iOS Safari 远程控制 Win11**
<img width="645" height="300" alt="_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=932911462648581698 skey=@crypt_1f5153b1_b550ca7462b5009ce03c991cca2a92a7 mmweb_appid=wx_webfilehelper" src="https://github.com/user-attachments/assets/a5109e6f-752c-4654-9f4e-7e161bddf43e" />
## 如何编译 ## 如何编译
依赖: 依赖:
@@ -146,7 +159,10 @@ sudo docker run -d \
--network host \ --network host \
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \ -e EXTERNAL_IP=xxx.xxx.xxx.xxx \
-e INTERNAL_IP=xxx.xxx.xxx.xxx \ -e INTERNAL_IP=xxx.xxx.xxx.xxx \
-e CROSSDESK_SERVER_PORT=9099 \ -e CROSSDESK_SERVER_PORT=xxxx \
-e COTURN_PORT=xxxx \
-e MIN_PORT=xxxxx \
-e MAX_PORT=xxxxx \
-v /path/to/your/certs:/crossdesk-server/certs \ -v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \ -v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \ -v /path/to/your/logs:/crossdesk-server/logs \
@@ -161,6 +177,10 @@ sudo docker run -d \
- CROSSDESK_SERVER_PORT自托管服务使用的端口对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口** - CROSSDESK_SERVER_PORT自托管服务使用的端口对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口**
- COTURN_PORT: COTURN 服务使用的端口, 对应 CrossDesk 客户端**自托管服务器配置**中填写的**中继服务端口**
- MIN_PORT/MAX_PORTCOTURN 服务使用的端口范围例如MIN_PORT=50000, MAX_PORT=60000范围可根据客户端数量调整。
- /path/to/your/certs证书文件目录 - /path/to/your/certs证书文件目录
- /path/to/your/dbCrossDesk Server 设备管理数据库 - /path/to/your/dbCrossDesk Server 设备管理数据库
@@ -169,7 +189,7 @@ sudo docker run -d \
**注意** **注意**
- **/path/to/your/ 是示例路径,请替换为你自己的实际路径。挂载的目录必须事先创建好,否则容器会报错。** - **/path/to/your/ 是示例路径,请替换为你自己的实际路径。挂载的目录必须事先创建好,否则容器会报错。**
- **服务器需开放端口3478/udp3478/tcp30000-60000/udpCROSSDESK_SERVER_PORT/tcp443/tcp。** - **服务器需开放端口3478/udp3478/tcpMIN_PORT-MAX_PORT/udpCROSSDESK_SERVER_PORT/tcp。**
## 证书文件 ## 证书文件
客户端需加载根证书文件,服务端需加载服务器私钥和服务器证书文件。 客户端需加载根证书文件,服务端需加载服务器私钥和服务器证书文件。
@@ -297,4 +317,4 @@ Generation complete. Deployment files::
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br> <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
# 常见问题 # 常见问题
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。 见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。

View File

@@ -1,8 +1,8 @@
# CrossDesk # CrossDesk
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)]() [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen.svg)]()
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server) [![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/web-client)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions) [![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags) [![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]() [![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
@@ -19,6 +19,15 @@ CrossDesk is a lightweight cross-platform remote desktop software.
CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)). CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)).
## System Requirements
| Platform | Minimum Version |
|-----------|-----------------|
| **Windows** | Windows 10 or later (64-bit) |
| **macOS** | macOS Intel 15.0 or later *(versions between 14.0 and 15.0 can be built manually for compatibility)*<br>macOS Apple Silicon 14.0 or later |
| **Linux** | Ubuntu 22.04 or later *(older versions can be built manually for compatibility)* |
## Usage ## Usage
Enter the remote desktop ID in the menu bars “Remote ID” field and click “→” to initiate a remote connection. Enter the remote desktop ID in the menu bars “Remote ID” field and click “→” to initiate a remote connection.
@@ -33,6 +42,13 @@ Before connecting, you can customize configuration options in the settings, such
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c) ![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
### Web Client
Visit [CrossDesk Web Client](https://web.crossdesk.cn/).
Enter the **Remote Device ID** and **Password**, then click Connect to access the remote device. As shown, **iOS Safari remotely controlling Windows 11**:
<img width="645" height="300" alt="_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=932911462648581698 skey=@crypt_1f5153b1_b550ca7462b5009ce03c991cca2a92a7 mmweb_appid=wx_webfilehelper" src="https://github.com/user-attachments/assets/a5109e6f-752c-4654-9f4e-7e161bddf43e" />
## How to build ## How to build
Requirements: Requirements:
@@ -149,13 +165,16 @@ It is recommended to deploy CrossDesk Server using Docker.
sudo docker run -d \ sudo docker run -d \
--name crossdesk_server \ --name crossdesk_server \
--network host \ --network host \
-e EXTERNAL_IP=150.158.81.30 \ -e EXTERNAL_IP=xxx.xxx.xxx.xxx \
-e INTERNAL_IP=10.0.4.3 \ -e INTERNAL_IP=xxx.xxx.xxx.xxx \
-e CROSSDESK_SERVER_PORT=9099 \ -e CROSSDESK_SERVER_PORT=xxxx \
-e COTURN_PORT=xxxx \
-e MIN_PORT=xxxxx \
-e MAX_PORT=xxxxx \
-v /path/to/your/certs:/crossdesk-server/certs \ -v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \ -v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \ -v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:latest crossdesk/crossdesk-server:v1.1.0
``` ```
The parameters you need to pay attention to are as follows: The parameters you need to pay attention to are as follows:
@@ -166,6 +185,10 @@ The parameters you need to pay attention to are as follows:
- **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**. - **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
- **COTURN_PORT**: The port used by Coturn, corresponding to the **Relay Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
- **MIN_PORT** and **MAX_PORT**: The range of ports used by the self-hosted server, corresponding to the **Minimum Port** and **Maximum Port** in the CrossDesk client **Self-Hosted Server Configuration**. Example: 50000-60000. It depends on the number of devices connected to the server.
- **/path/to/your/certs**: Directory for certificate files. - **/path/to/your/certs**: Directory for certificate files.
- **/path/to/your/db**: CrossDesk Server device management database. - **/path/to/your/db**: CrossDesk Server device management database.
@@ -174,7 +197,7 @@ The parameters you need to pay attention to are as follows:
**Note**: **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.** - **/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.** - **The server must open the following ports: 3478/udp, 3478/tcp, 30000-60000/udp, CROSSDESK_SERVER_PORT/tcp.**
## Certificate Files ## Certificate Files
The client needs to load the root certificate, and the server needs to load the server private key and server certificate. The client needs to load the root certificate, and the server needs to load the server private key and server certificate.
@@ -302,4 +325,4 @@ Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/y
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br> <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
# FAQ # FAQ
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) . See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .

View File

@@ -9,7 +9,10 @@ ARCHITECTURE="amd64"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>" MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client." DESCRIPTION="A simple cross-platform remote desktop client."
DEB_DIR="${PKG_NAME}-${APP_VERSION}" # Remove 'v' prefix from version for Debian package (Debian version must start with digit)
DEB_VERSION="${APP_VERSION#v}"
DEB_DIR="${PKG_NAME}-${DEB_VERSION}"
DEBIAN_DIR="$DEB_DIR/DEBIAN" DEBIAN_DIR="$DEB_DIR/DEBIAN"
BIN_DIR="$DEB_DIR/usr/bin" BIN_DIR="$DEB_DIR/usr/bin"
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs" CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
@@ -35,7 +38,7 @@ done
cat > "$DEBIAN_DIR/control" << EOF cat > "$DEBIAN_DIR/control" << EOF
Package: $PKG_NAME Package: $PKG_NAME
Version: $APP_VERSION Version: $DEB_VERSION
Architecture: $ARCHITECTURE Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER Maintainer: $MAINTAINER
Description: $DESCRIPTION Description: $DESCRIPTION
@@ -50,7 +53,7 @@ EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
[Desktop Entry] [Desktop Entry]
Version=$APP_VERSION Version=$DEB_VERSION
Name=$APP_NAME Name=$APP_NAME
Comment=$DESCRIPTION Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME Exec=/usr/bin/$PKG_NAME

View File

@@ -9,7 +9,10 @@ ARCHITECTURE="arm64"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>" MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client." DESCRIPTION="A simple cross-platform remote desktop client."
DEB_DIR="${PKG_NAME}-${APP_VERSION}" # Remove 'v' prefix from version for Debian package (Debian version must start with digit)
DEB_VERSION="${APP_VERSION#v}"
DEB_DIR="${PKG_NAME}-${DEB_VERSION}"
DEBIAN_DIR="$DEB_DIR/DEBIAN" DEBIAN_DIR="$DEB_DIR/DEBIAN"
BIN_DIR="$DEB_DIR/usr/bin" BIN_DIR="$DEB_DIR/usr/bin"
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs" CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
@@ -35,7 +38,7 @@ done
cat > "$DEBIAN_DIR/control" << EOF cat > "$DEBIAN_DIR/control" << EOF
Package: $PKG_NAME Package: $PKG_NAME
Version: $APP_VERSION Version: $DEB_VERSION
Architecture: $ARCHITECTURE Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER Maintainer: $MAINTAINER
Description: $DESCRIPTION Description: $DESCRIPTION
@@ -49,7 +52,7 @@ EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
[Desktop Entry] [Desktop Entry]
Version=$APP_VERSION Version=$DEB_VERSION
Name=$APP_NAME Name=$APP_NAME
Comment=$DESCRIPTION Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME Exec=/usr/bin/$PKG_NAME

View File

@@ -1,5 +1,5 @@
#ifdef _WIN32 #ifdef _WIN32
#ifdef DESK_PORT_DEBUG #ifdef CROSSDESK_DEBUG
#pragma comment(linker, "/subsystem:\"console\"") #pragma comment(linker, "/subsystem:\"console\"")
#else #else
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
@@ -9,8 +9,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) { int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
Render render; crossdesk::Render render;
render.Run(); render.Run();
return 0; return 0;

View File

@@ -9,6 +9,8 @@
#include <string> #include <string>
namespace crossdesk {
class DisplayInfo { class DisplayInfo {
public: public:
DisplayInfo(std::string name, int left, int top, int right, int bottom) DisplayInfo(std::string name, int left, int top, int right, int bottom)
@@ -40,5 +42,5 @@ class DisplayInfo {
int width = 0; int width = 0;
int height = 0; int height = 0;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -19,6 +19,8 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
namespace crossdesk {
std::string GetMac() { std::string GetMac() {
char mac_addr[16]; char mac_addr[16];
int len = 0; int len = 0;
@@ -39,21 +41,21 @@ std::string GetMac() {
#elif __APPLE__ #elif __APPLE__
std::string if_name = "en0"; std::string if_name = "en0";
struct ifaddrs *addrs; struct ifaddrs* addrs;
struct ifaddrs *cursor; struct ifaddrs* cursor;
const struct sockaddr_dl *dlAddr; const struct sockaddr_dl* dlAddr;
if (!getifaddrs(&addrs)) { if (!getifaddrs(&addrs)) {
cursor = addrs; cursor = addrs;
while (cursor != 0) { while (cursor != 0) {
const struct sockaddr_dl *socAddr = const struct sockaddr_dl* socAddr =
(const struct sockaddr_dl *)cursor->ifa_addr; (const struct sockaddr_dl*)cursor->ifa_addr;
if ((cursor->ifa_addr->sa_family == AF_LINK) && if ((cursor->ifa_addr->sa_family == AF_LINK) &&
(socAddr->sdl_type == IFT_ETHER) && (socAddr->sdl_type == IFT_ETHER) &&
strcmp(if_name.c_str(), cursor->ifa_name) == 0) { strcmp(if_name.c_str(), cursor->ifa_name) == 0) {
dlAddr = (const struct sockaddr_dl *)cursor->ifa_addr; dlAddr = (const struct sockaddr_dl*)cursor->ifa_addr;
const unsigned char *base = const unsigned char* base =
(const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen]; (const unsigned char*)&dlAddr->sdl_data[dlAddr->sdl_nlen];
for (int i = 0; i < dlAddr->sdl_alen; i++) { for (int i = 0; i < dlAddr->sdl_alen; i++) {
len += len +=
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]); snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
@@ -77,8 +79,8 @@ std::string GetMac() {
close(sock); close(sock);
return ""; return "";
} }
struct ifreq *it = ifc.ifc_req; struct ifreq* it = ifc.ifc_req;
const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq)); const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
for (; it != end; ++it) { for (; it != end; ++it) {
std::strcpy(ifr.ifr_name, it->ifr_name); std::strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
@@ -122,4 +124,5 @@ std::string GetHostName() {
} }
#endif #endif
return hostname; return hostname;
} }
} // namespace crossdesk

View File

@@ -9,7 +9,10 @@
#include <iostream> #include <iostream>
namespace crossdesk {
std::string GetMac(); std::string GetMac();
std::string GetHostName(); std::string GetHostName();
} // namespace crossdesk
#endif #endif

View File

@@ -1,5 +1,7 @@
#include "config_center.h" #include "config_center.h"
namespace crossdesk {
ConfigCenter::ConfigCenter(const std::string& config_path, ConfigCenter::ConfigCenter(const std::string& config_path,
const std::string& cert_file_path) const std::string& cert_file_path)
: config_path_(config_path), : config_path_(config_path),
@@ -36,9 +38,12 @@ int ConfigCenter::Load() {
enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_); enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_);
enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_); enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_);
server_host_ = ini_.GetValue(section_, "server_host", server_host_.c_str()); signal_server_host_ = ini_.GetValue(section_, "signal_server_host",
server_port_ = static_cast<int>( signal_server_host_.c_str());
ini_.GetLongValue(section_, "server_port", server_port_)); signal_server_port_ = static_cast<int>(
ini_.GetLongValue(section_, "signal_server_port", signal_server_port_));
coturn_server_port_ = static_cast<int>(
ini_.GetLongValue(section_, "coturn_server_port", coturn_server_port_));
cert_file_path_ = cert_file_path_ =
ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str()); ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str());
enable_self_hosted_ = enable_self_hosted_ =
@@ -61,8 +66,9 @@ int ConfigCenter::Save() {
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_); ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
ini_.SetBoolValue(section_, "enable_turn", enable_turn_); ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_); ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
ini_.SetValue(section_, "server_host", server_host_.c_str()); ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_)); ini_.SetLongValue(section_, "signal_server_port",
static_cast<long>(signal_server_port_));
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_); ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
ini_.SetBoolValue(section_, "enable_minimize_to_tray", ini_.SetBoolValue(section_, "enable_minimize_to_tray",
@@ -152,9 +158,9 @@ int ConfigCenter::SetSrtp(bool enable_srtp) {
return 0; return 0;
} }
int ConfigCenter::SetServerHost(const std::string& server_host) { int ConfigCenter::SetServerHost(const std::string& signal_server_host) {
server_host_ = server_host; signal_server_host_ = signal_server_host;
ini_.SetValue(section_, "server_host", server_host_.c_str()); ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
SI_Error rc = ini_.SaveFile(config_path_.c_str()); SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) { if (rc < 0) {
return -1; return -1;
@@ -162,9 +168,21 @@ int ConfigCenter::SetServerHost(const std::string& server_host) {
return 0; return 0;
} }
int ConfigCenter::SetServerPort(int server_port) { int ConfigCenter::SetServerPort(int signal_server_port) {
server_port_ = server_port; signal_server_port_ = signal_server_port;
ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_)); ini_.SetLongValue(section_, "signal_server_port",
static_cast<long>(signal_server_port_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetCoturnServerPort(int coturn_server_port) {
coturn_server_port_ = coturn_server_port;
ini_.SetLongValue(section_, "coturn_server_port",
static_cast<long>(coturn_server_port_));
SI_Error rc = ini_.SaveFile(config_path_.c_str()); SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) { if (rc < 0) {
return -1; return -1;
@@ -184,6 +202,7 @@ int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) {
int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
enable_self_hosted_ = enable_self_hosted; enable_self_hosted_ = enable_self_hosted;
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
SI_Error rc = ini_.SaveFile(config_path_.c_str()); SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) { if (rc < 0) {
return -1; return -1;
@@ -193,6 +212,12 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) { int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) {
enable_minimize_to_tray_ = enable_minimize_to_tray; enable_minimize_to_tray_ = enable_minimize_to_tray;
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; return 0;
} }
@@ -220,17 +245,27 @@ bool ConfigCenter::IsEnableTurn() const { return enable_turn_; }
bool ConfigCenter::IsEnableSrtp() const { return enable_srtp_; } bool ConfigCenter::IsEnableSrtp() const { return enable_srtp_; }
std::string ConfigCenter::GetServerHost() const { return server_host_; } std::string ConfigCenter::GetSignalServerHost() const {
return signal_server_host_;
}
int ConfigCenter::GetServerPort() const { return server_port_; } int ConfigCenter::GetSignalServerPort() const { return signal_server_port_; }
int ConfigCenter::GetCoturnServerPort() const { return coturn_server_port_; }
std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; } std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; }
std::string ConfigCenter::GetDefaultServerHost() const { std::string ConfigCenter::GetDefaultServerHost() const {
return server_host_default_; return signal_server_host_default_;
} }
int ConfigCenter::GetDefaultServerPort() const { return server_port_default_; } int ConfigCenter::GetDefaultSignalServerPort() const {
return server_port_default_;
}
int ConfigCenter::GetDefaultCoturnServerPort() const {
return coturn_server_port_default_;
}
std::string ConfigCenter::GetDefaultCertFilePath() const { std::string ConfigCenter::GetDefaultCertFilePath() const {
return cert_file_path_default_; return cert_file_path_default_;
@@ -238,4 +273,5 @@ std::string ConfigCenter::GetDefaultCertFilePath() const {
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; } bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; } bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; }
} // namespace crossdesk

View File

@@ -11,6 +11,8 @@
#include "SimpleIni.h" #include "SimpleIni.h"
namespace crossdesk {
class ConfigCenter { class ConfigCenter {
public: public:
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 }; enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 };
@@ -32,8 +34,9 @@ class ConfigCenter {
int SetHardwareVideoCodec(bool hardware_video_codec); int SetHardwareVideoCodec(bool hardware_video_codec);
int SetTurn(bool enable_turn); int SetTurn(bool enable_turn);
int SetSrtp(bool enable_srtp); int SetSrtp(bool enable_srtp);
int SetServerHost(const std::string& server_host); int SetServerHost(const std::string& signal_server_host);
int SetServerPort(int server_port); int SetServerPort(int signal_server_port);
int SetCoturnServerPort(int coturn_server_port);
int SetCertFilePath(const std::string& cert_file_path); int SetCertFilePath(const std::string& cert_file_path);
int SetSelfHosted(bool enable_self_hosted); int SetSelfHosted(bool enable_self_hosted);
int SetMinimizeToTray(bool enable_minimize_to_tray); int SetMinimizeToTray(bool enable_minimize_to_tray);
@@ -47,11 +50,13 @@ class ConfigCenter {
bool IsHardwareVideoCodec() const; bool IsHardwareVideoCodec() const;
bool IsEnableTurn() const; bool IsEnableTurn() const;
bool IsEnableSrtp() const; bool IsEnableSrtp() const;
std::string GetServerHost() const; std::string GetSignalServerHost() const;
int GetServerPort() const; int GetSignalServerPort() const;
int GetCoturnServerPort() const;
std::string GetCertFilePath() const; std::string GetCertFilePath() const;
std::string GetDefaultServerHost() const; std::string GetDefaultServerHost() const;
int GetDefaultServerPort() const; int GetDefaultSignalServerPort() const;
int GetDefaultCoturnServerPort() const;
std::string GetDefaultCertFilePath() const; std::string GetDefaultCertFilePath() const;
bool IsSelfHosted() const; bool IsSelfHosted() const;
bool IsMinimizeToTray() const; bool IsMinimizeToTray() const;
@@ -67,18 +72,20 @@ class ConfigCenter {
LANGUAGE language_ = LANGUAGE::CHINESE; LANGUAGE language_ = LANGUAGE::CHINESE;
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM; VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30; VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_60;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264; VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
bool hardware_video_codec_ = false; bool hardware_video_codec_ = false;
bool enable_turn_ = false; bool enable_turn_ = false;
bool enable_srtp_ = false; bool enable_srtp_ = false;
std::string server_host_ = "api.crossdesk.cn"; std::string signal_server_host_ = "api.crossdesk.cn";
int server_port_ = 9099; std::string signal_server_host_default_ = "api.crossdesk.cn";
std::string server_host_default_ = "api.crossdesk.cn"; int signal_server_port_ = 9099;
int server_port_default_ = 9099; int server_port_default_ = 9099;
int coturn_server_port_ = 3478;
int coturn_server_port_default_ = 3478;
std::string cert_file_path_default_ = ""; std::string cert_file_path_default_ = "";
bool enable_self_hosted_ = false; bool enable_self_hosted_ = false;
bool enable_minimize_to_tray_ = false; bool enable_minimize_to_tray_ = false;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -9,7 +9,13 @@
#include <stdio.h> #include <stdio.h>
#include <nlohmann/json.hpp>
#include <string>
#include "display_info.h" #include "display_info.h"
using json = nlohmann::json;
namespace crossdesk {
typedef enum { typedef enum {
mouse = 0, mouse = 0,
@@ -53,7 +59,7 @@ typedef struct {
int* bottom; int* bottom;
} HostInfo; } HostInfo;
typedef struct { struct RemoteAction {
ControlType type; ControlType type;
union { union {
Mouse m; Mouse m;
@@ -62,7 +68,111 @@ typedef struct {
bool a; bool a;
int d; int d;
}; };
} RemoteAction;
// parse
std::string to_json() const { return ToJson(*this); }
bool from_json(const std::string& json_str) {
RemoteAction temp;
if (!FromJson(json_str, temp)) return false;
*this = temp;
return true;
}
static std::string ToJson(const RemoteAction& a) {
json j;
j["type"] = a.type;
switch (a.type) {
case ControlType::mouse:
j["mouse"] = {
{"x", a.m.x}, {"y", a.m.y}, {"s", a.m.s}, {"flag", a.m.flag}};
break;
case ControlType::keyboard:
j["keyboard"] = {{"key_value", a.k.key_value}, {"flag", a.k.flag}};
break;
case ControlType::audio_capture:
j["audio_capture"] = a.a;
break;
case ControlType::display_id:
j["display_id"] = a.d;
break;
case ControlType::host_infomation: {
json displays = json::array();
for (size_t idx = 0; idx < a.i.display_num; idx++) {
displays.push_back(
{{"name", a.i.display_list ? a.i.display_list[idx] : ""},
{"left", a.i.left ? a.i.left[idx] : 0},
{"top", a.i.top ? a.i.top[idx] : 0},
{"right", a.i.right ? a.i.right[idx] : 0},
{"bottom", a.i.bottom ? a.i.bottom[idx] : 0}});
}
j["host_info"] = {{"host_name", a.i.host_name},
{"display_num", a.i.display_num},
{"displays", displays}};
break;
}
}
return j.dump();
}
static bool FromJson(const std::string& json_str, RemoteAction& out) {
try {
json j = json::parse(json_str);
out.type = (ControlType)j.at("type").get<int>();
switch (out.type) {
case ControlType::mouse:
out.m.x = j.at("mouse").at("x").get<float>();
out.m.y = j.at("mouse").at("y").get<float>();
out.m.s = j.at("mouse").at("s").get<int>();
out.m.flag = (MouseFlag)j.at("mouse").at("flag").get<int>();
break;
case ControlType::keyboard:
out.k.key_value = j.at("keyboard").at("key_value").get<size_t>();
out.k.flag = (KeyFlag)j.at("keyboard").at("flag").get<int>();
break;
case ControlType::audio_capture:
out.a = j.at("audio_capture").get<bool>();
break;
case ControlType::display_id:
out.d = j.at("display_id").get<int>();
break;
case ControlType::host_infomation: {
std::string host_name =
j.at("host_info").at("host_name").get<std::string>();
strncpy(out.i.host_name, host_name.c_str(), sizeof(out.i.host_name));
out.i.host_name[sizeof(out.i.host_name) - 1] = '\0';
out.i.host_name_size = host_name.size();
out.i.display_num = j.at("host_info").at("display_num").get<size_t>();
auto displays = j.at("host_info").at("displays");
out.i.display_list =
(char**)malloc(out.i.display_num * sizeof(char*));
out.i.left = (int*)malloc(out.i.display_num * sizeof(int));
out.i.top = (int*)malloc(out.i.display_num * sizeof(int));
out.i.right = (int*)malloc(out.i.display_num * sizeof(int));
out.i.bottom = (int*)malloc(out.i.display_num * sizeof(int));
for (size_t idx = 0; idx < out.i.display_num; idx++) {
std::string name = displays[idx].at("name").get<std::string>();
out.i.display_list[idx] = (char*)malloc(name.size() + 1);
strcpy(out.i.display_list[idx], name.c_str());
out.i.left[idx] = displays[idx].at("left").get<int>();
out.i.top[idx] = displays[idx].at("top").get<int>();
out.i.right[idx] = displays[idx].at("right").get<int>();
out.i.bottom[idx] = displays[idx].at("bottom").get<int>();
}
break;
}
}
return true;
} catch (const std::exception& e) {
printf("Failed to parse RemoteAction JSON: %s\n", e.what());
return false;
}
}
};
// int key_code, bool is_down // int key_code, bool is_down
typedef void (*OnKeyAction)(int, bool, void*); typedef void (*OnKeyAction)(int, bool, void*);
@@ -79,5 +189,5 @@ class DeviceController {
// virtual int Hook(); // virtual int Hook();
// virtual int Unhook(); // virtual int Unhook();
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -11,6 +11,8 @@
#include "keyboard_capturer.h" #include "keyboard_capturer.h"
#include "mouse_controller.h" #include "mouse_controller.h"
namespace crossdesk {
class DeviceControllerFactory { class DeviceControllerFactory {
public: public:
enum Device { Mouse = 0, Keyboard }; enum Device { Mouse = 0, Keyboard };
@@ -30,5 +32,5 @@ class DeviceControllerFactory {
} }
} }
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -3,6 +3,8 @@
#include "keyboard_converter.h" #include "keyboard_converter.h"
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr; static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr; static void* g_user_ptr = nullptr;
@@ -67,3 +69,4 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -13,20 +13,22 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class KeyboardCapturer : public DeviceController { class KeyboardCapturer : public DeviceController {
public: public:
KeyboardCapturer(); KeyboardCapturer();
virtual ~KeyboardCapturer(); virtual ~KeyboardCapturer();
public: public:
virtual int Hook(OnKeyAction on_key_action, void *user_ptr); virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook(); virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down); virtual int SendKeyboardCommand(int key_code, bool is_down);
private: private:
Display *display_; Display* display_;
Window root_; Window root_;
bool running_; bool running_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -3,12 +3,14 @@
#include "keyboard_converter.h" #include "keyboard_converter.h"
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr; static OnKeyAction g_on_key_action = nullptr;
static void *g_user_ptr = nullptr; static void* g_user_ptr = nullptr;
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
CGEventRef event, void *userInfo) { CGEventRef event, void* userInfo) {
KeyboardCapturer *keyboard_capturer = (KeyboardCapturer *)userInfo; KeyboardCapturer* keyboard_capturer = (KeyboardCapturer*)userInfo;
if (!keyboard_capturer) { if (!keyboard_capturer) {
LOG_ERROR("keyboard_capturer is nullptr"); LOG_ERROR("keyboard_capturer is nullptr");
return event; return event;
@@ -91,7 +93,7 @@ KeyboardCapturer::KeyboardCapturer() {}
KeyboardCapturer::~KeyboardCapturer() {} KeyboardCapturer::~KeyboardCapturer() {}
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void *user_ptr) { int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
g_on_key_action = on_key_action; g_on_key_action = on_key_action;
g_user_ptr = user_ptr; g_user_ptr = user_ptr;
@@ -164,4 +166,5 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,13 +11,15 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class KeyboardCapturer : public DeviceController { class KeyboardCapturer : public DeviceController {
public: public:
KeyboardCapturer(); KeyboardCapturer();
virtual ~KeyboardCapturer(); virtual ~KeyboardCapturer();
public: public:
virtual int Hook(OnKeyAction on_key_action, void *user_ptr); virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook(); virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down); virtual int SendKeyboardCommand(int key_code, bool is_down);
@@ -33,5 +35,5 @@ class KeyboardCapturer : public DeviceController {
bool command_flag_ = false; bool command_flag_ = false;
int fn_key_code_ = 0x3F; int fn_key_code_ = 0x3F;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -2,6 +2,8 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr; static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr; static void* g_user_ptr = nullptr;
@@ -53,4 +55,5 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
SendInput(1, &input, sizeof(INPUT)); SendInput(1, &input, sizeof(INPUT));
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,18 +11,21 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class KeyboardCapturer : public DeviceController { class KeyboardCapturer : public DeviceController {
public: public:
KeyboardCapturer(); KeyboardCapturer();
virtual ~KeyboardCapturer(); virtual ~KeyboardCapturer();
public: public:
virtual int Hook(OnKeyAction on_key_action, void *user_ptr); virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook(); virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down); virtual int SendKeyboardCommand(int key_code, bool is_down);
private: private:
HHOOK keyboard_hook_ = nullptr; HHOOK keyboard_hook_ = nullptr;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -9,6 +9,8 @@
#include <map> #include <map>
namespace crossdesk {
// Windows vkCode to macOS CGKeyCode (104 keys) // Windows vkCode to macOS CGKeyCode (104 keys)
std::map<int, int> vkCodeToCGKeyCode = { std::map<int, int> vkCodeToCGKeyCode = {
// A-Z // A-Z
@@ -736,5 +738,5 @@ std::map<int, int> x11KeySymToCgKeyCode = {
{0xFFEB, 0x37}, // Left Command {0xFFEB, 0x37}, // Left Command
{0xFFEC, 0x36}, // Right Command {0xFFEC, 0x36}, // Right Command
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -4,6 +4,8 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {} MouseController::MouseController() {}
MouseController::~MouseController() { Destroy(); } MouseController::~MouseController() { Destroy(); }
@@ -121,4 +123,5 @@ void MouseController::SimulateMouseWheel(int direction_button, int count) {
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime); XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
} }
XFlush(display_); XFlush(display_);
} }
} // namespace crossdesk

View File

@@ -15,6 +15,8 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController { class MouseController : public DeviceController {
public: public:
MouseController(); MouseController();
@@ -37,5 +39,5 @@ class MouseController : public DeviceController {
int screen_width_ = 0; int screen_width_ = 0;
int screen_height_ = 0; int screen_height_ = 0;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -4,6 +4,8 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {} MouseController::MouseController() {}
MouseController::~MouseController() {} MouseController::~MouseController() {}
@@ -98,4 +100,5 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,6 +11,8 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController { class MouseController : public DeviceController {
public: public:
MouseController(); MouseController();
@@ -26,5 +28,5 @@ class MouseController : public DeviceController {
bool left_dragging_ = false; bool left_dragging_ = false;
bool right_dragging_ = false; bool right_dragging_ = false;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -2,6 +2,8 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {} MouseController::MouseController() {}
MouseController::~MouseController() {} MouseController::~MouseController() {}
@@ -69,4 +71,5 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,6 +11,8 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController { class MouseController : public DeviceController {
public: public:
MouseController(); MouseController();
@@ -24,5 +26,5 @@ class MouseController : public DeviceController {
private: private:
std::vector<DisplayInfo> display_info_list_; std::vector<DisplayInfo> display_info_list_;
}; };
} // namespace crossdesk
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -29,8 +29,8 @@
#endif #endif
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228 #define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275 #define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 165 #define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 195
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 165 #define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 195
#define LANGUAGE_SELECT_WINDOW_PADDING_CN 120 #define LANGUAGE_SELECT_WINDOW_PADDING_CN 120
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 167 #define LANGUAGE_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120 #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120

View File

@@ -12,6 +12,9 @@
#if _WIN32 #if _WIN32
#include <Windows.h> #include <Windows.h>
#endif #endif
namespace crossdesk {
namespace localization { namespace localization {
static std::vector<std::string> local_desktop = { static std::vector<std::string> local_desktop = {
@@ -106,7 +109,9 @@ static std::vector<std::string> self_hosted_server_settings = {
static std::vector<std::string> self_hosted_server_address = { static std::vector<std::string> self_hosted_server_address = {
reinterpret_cast<const char*>(u8"服务器地址:"), "Server Address:"}; reinterpret_cast<const char*>(u8"服务器地址:"), "Server Address:"};
static std::vector<std::string> self_hosted_server_port = { static std::vector<std::string> self_hosted_server_port = {
reinterpret_cast<const char*>(u8"服务端口:"), "Server Port:"}; reinterpret_cast<const char*>(u8"信令服务端口:"), "Signal Service Port:"};
static std::vector<std::string> self_hosted_server_coturn_server_port = {
reinterpret_cast<const char*>(u8"中继服务端口:"), "Relay Service Port:"};
static std::vector<std::string> self_hosted_server_certificate_path = { static std::vector<std::string> self_hosted_server_certificate_path = {
reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"}; reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"};
static std::vector<std::string> select_a_file = { static std::vector<std::string> select_a_file = {
@@ -153,8 +158,12 @@ static std::vector<std::string> no_such_id = {
static std::vector<std::string> about = { static std::vector<std::string> about = {
reinterpret_cast<const char*>(u8"关于"), "About"}; reinterpret_cast<const char*>(u8"关于"), "About"};
static std::vector<std::string> new_version_available = {
reinterpret_cast<const char*>(u8"新版本可用"), "New Version Available"};
static std::vector<std::string> version = { static std::vector<std::string> version = {
reinterpret_cast<const char*>(u8"版本"), "Version"}; reinterpret_cast<const char*>(u8"版本"), "Version"};
static std::vector<std::string> access_website = {
reinterpret_cast<const char*>(u8"访问官网: "), "Access Website: "};
static std::vector<std::string> confirm_delete_connection = { static std::vector<std::string> confirm_delete_connection = {
reinterpret_cast<const char*>(u8"确认删除此连接"), reinterpret_cast<const char*>(u8"确认删除此连接"),
@@ -167,4 +176,5 @@ static std::vector<std::string> minimize_to_tray = {
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"}; static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
#endif #endif
} // namespace localization } // namespace localization
} // namespace crossdesk
#endif #endif

View File

@@ -5,6 +5,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
int Render::LocalWindow() { int Render::LocalWindow() {
ImGui::SetNextWindowPos(ImVec2(-1.0f, title_bar_height_), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(-1.0f, title_bar_height_), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
@@ -287,4 +289,5 @@ int Render::LocalWindow() {
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -2,6 +2,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
int Render::RecentConnectionsWindow() { int Render::RecentConnectionsWindow() {
ImGui::SetNextWindowPos( ImGui::SetNextWindowPos(
ImVec2(0, title_bar_height_ + local_window_height_ - 1.0f), ImVec2(0, title_bar_height_ + local_window_height_ - 1.0f),
@@ -284,3 +286,4 @@ int Render::ConfirmDeleteConnection() {
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,7 +3,9 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
static int InputTextCallback(ImGuiInputTextCallbackData *data); namespace crossdesk {
static int InputTextCallback(ImGuiInputTextCallbackData* data);
int Render::RemoteWindow() { int Render::RemoteWindow() {
ImGui::SetNextWindowPos(ImVec2(local_window_width_ + 1.0f, title_bar_height_), ImGui::SetNextWindowPos(ImVec2(local_window_width_ + 1.0f, title_bar_height_),
@@ -77,7 +79,7 @@ int Render::RemoteWindow() {
enter_pressed) { enter_pressed) {
connect_button_pressed_ = true; connect_button_pressed_ = true;
bool found = false; bool found = false;
for (auto &[id, props] : recent_connections_) { for (auto& [id, props] : recent_connections_) {
if (id.find(remote_id) != std::string::npos) { if (id.find(remote_id) != std::string::npos) {
found = true; found = true;
if (client_properties_.find(remote_id) != if (client_properties_.find(remote_id) !=
@@ -101,7 +103,7 @@ int Render::RemoteWindow() {
if (need_to_rejoin_) { if (need_to_rejoin_) {
need_to_rejoin_ = false; need_to_rejoin_ = false;
for (const auto &[_, props] : client_properties_) { for (const auto& [_, props] : client_properties_) {
if (props->rejoin_) { if (props->rejoin_) {
ConnectTo(props->remote_id_, props->remote_password_, ConnectTo(props->remote_id_, props->remote_password_,
props->remember_password_); props->remember_password_);
@@ -117,7 +119,7 @@ int Render::RemoteWindow() {
return 0; return 0;
} }
static int InputTextCallback(ImGuiInputTextCallbackData *data) { static int InputTextCallback(ImGuiInputTextCallbackData* data) {
if (data->BufTextLen > 3 && data->Buf[3] != ' ') { if (data->BufTextLen > 3 && data->Buf[3] != ' ') {
data->InsertChars(3, " "); data->InsertChars(3, " ");
} }
@@ -129,7 +131,7 @@ static int InputTextCallback(ImGuiInputTextCallbackData *data) {
return 0; return 0;
} }
int Render::ConnectTo(const std::string &remote_id, const char *password, int Render::ConnectTo(const std::string& remote_id, const char* password,
bool remember_password) { bool remember_password) {
LOG_INFO("Connect to [{}]", remote_id); LOG_INFO("Connect to [{}]", remote_id);
focused_remote_id_ = remote_id; focused_remote_id_ = remote_id;
@@ -143,6 +145,10 @@ int Render::ConnectTo(const std::string &remote_id, const char *password,
memcpy(&props->params_, &params_, sizeof(Params)); memcpy(&props->params_, &params_, sizeof(Params));
props->params_.user_id = props->local_id_.c_str(); props->params_.user_id = props->local_id_.c_str();
props->peer_ = CreatePeer(&props->params_); props->peer_ = CreatePeer(&props->params_);
for (auto& display_info : display_info_list_) {
AddVideoStream(peer_, display_info.name.c_str());
}
AddAudioStream(props->peer_, props->audio_label_.c_str()); AddAudioStream(props->peer_, props->audio_label_.c_str());
AddDataStream(props->peer_, props->data_label_.c_str()); AddDataStream(props->peer_, props->data_label_.c_str());
@@ -178,4 +184,5 @@ int Render::ConnectTo(const std::string &remote_id, const char *password,
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -16,11 +16,14 @@
#include "platform.h" #include "platform.h"
#include "rd_log.h" #include "rd_log.h"
#include "screen_capturer_factory.h" #include "screen_capturer_factory.h"
#include "version_checker.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#define MOUSE_GRAB_PADDING 5 #define MOUSE_GRAB_PADDING 5
namespace crossdesk {
std::vector<char> Render::SerializeRemoteAction(const RemoteAction& action) { std::vector<char> Render::SerializeRemoteAction(const RemoteAction& action) {
std::vector<char> buffer; std::vector<char> buffer;
buffer.push_back(static_cast<char>(action.type)); buffer.push_back(static_cast<char>(action.type));
@@ -256,6 +259,7 @@ int Render::LoadSettingsFromCacheFile() {
enable_hardware_video_codec_ = config_center_->IsHardwareVideoCodec(); enable_hardware_video_codec_ = config_center_->IsHardwareVideoCodec();
enable_turn_ = config_center_->IsEnableTurn(); enable_turn_ = config_center_->IsEnableTurn();
enable_srtp_ = config_center_->IsEnableSrtp(); enable_srtp_ = config_center_->IsEnableSrtp();
enable_self_hosted_ = config_center_->IsSelfHosted();
language_button_value_last_ = language_button_value_; language_button_value_last_ = language_button_value_;
video_quality_button_value_last_ = video_quality_button_value_; video_quality_button_value_last_ = video_quality_button_value_;
@@ -263,6 +267,7 @@ int Render::LoadSettingsFromCacheFile() {
enable_hardware_video_codec_last_ = enable_hardware_video_codec_; enable_hardware_video_codec_last_ = enable_hardware_video_codec_;
enable_turn_last_ = enable_turn_; enable_turn_last_ = enable_turn_;
enable_srtp_last_ = enable_srtp_; enable_srtp_last_ = enable_srtp_;
enable_self_hosted_last_ = enable_self_hosted_;
LOG_INFO("Load settings from cache file"); LOG_INFO("Load settings from cache file");
@@ -427,39 +432,57 @@ int Render::StopKeyboardCapturer() {
int Render::CreateConnectionPeer() { int Render::CreateConnectionPeer() {
params_.use_cfg_file = false; params_.use_cfg_file = false;
std::string server_ip; std::string signal_server_ip;
int server_port; int signal_server_port;
std::string server_cert_path; int coturn_server_port;
std::string tls_cert_path;
if (config_center_->IsSelfHosted()) { if (config_center_->IsSelfHosted()) {
server_ip = config_center_->GetServerHost(); signal_server_ip = config_center_->GetSignalServerHost();
server_port = config_center_->GetServerPort(); signal_server_port = config_center_->GetSignalServerPort();
server_cert_path = config_center_->GetCertFilePath(); coturn_server_port = config_center_->GetCoturnServerPort();
tls_cert_path = config_center_->GetCertFilePath();
} else { } else {
server_ip = config_center_->GetDefaultServerHost(); signal_server_ip = config_center_->GetDefaultServerHost();
server_port = config_center_->GetDefaultServerPort(); signal_server_port = config_center_->GetDefaultSignalServerPort();
server_cert_path = config_center_->GetDefaultCertFilePath(); coturn_server_port = config_center_->GetDefaultCoturnServerPort();
tls_cert_path = config_center_->GetDefaultCertFilePath();
} }
strncpy((char*)params_.signal_server_ip, server_ip.c_str(), // self hosted server config
strncpy(signal_server_ip_self_, config_center_->GetSignalServerHost().c_str(),
sizeof(signal_server_ip_self_) - 1);
signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
strncpy(signal_server_port_self_,
std::to_string(config_center_->GetSignalServerPort()).c_str(),
sizeof(signal_server_port_self_) - 1);
signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
strncpy(coturn_server_port_self_,
std::to_string(config_center_->GetCoturnServerPort()).c_str(),
sizeof(coturn_server_port_self_) - 1);
coturn_server_port_self_[sizeof(coturn_server_port_self_) - 1] = '\0';
tls_cert_path_self_ = config_center_->GetCertFilePath();
// peer config
strncpy((char*)params_.signal_server_ip, signal_server_ip.c_str(),
sizeof(params_.signal_server_ip) - 1); sizeof(params_.signal_server_ip) - 1);
params_.signal_server_ip[sizeof(params_.signal_server_ip) - 1] = '\0'; params_.signal_server_ip[sizeof(params_.signal_server_ip) - 1] = '\0';
params_.signal_server_port = server_port; params_.signal_server_port = signal_server_port;
strncpy((char*)params_.stun_server_ip, server_ip.c_str(), strncpy((char*)params_.stun_server_ip, signal_server_ip.c_str(),
sizeof(params_.stun_server_ip) - 1); sizeof(params_.stun_server_ip) - 1);
params_.stun_server_ip[sizeof(params_.stun_server_ip) - 1] = '\0'; params_.stun_server_ip[sizeof(params_.stun_server_ip) - 1] = '\0';
params_.stun_server_port = 3478; params_.stun_server_port = coturn_server_port;
strncpy((char*)params_.turn_server_ip, server_ip.c_str(), strncpy((char*)params_.turn_server_ip, signal_server_ip.c_str(),
sizeof(params_.turn_server_ip) - 1); sizeof(params_.turn_server_ip) - 1);
params_.turn_server_ip[sizeof(params_.turn_server_ip) - 1] = '\0'; params_.turn_server_ip[sizeof(params_.turn_server_ip) - 1] = '\0';
params_.turn_server_port = 3478; params_.turn_server_port = coturn_server_port;
strncpy((char*)params_.turn_server_username, "crossdesk", strncpy((char*)params_.turn_server_username, "crossdesk",
sizeof(params_.turn_server_username) - 1); sizeof(params_.turn_server_username) - 1);
params_.turn_server_username[sizeof(params_.turn_server_username) - 1] = '\0'; params_.turn_server_username[sizeof(params_.turn_server_username) - 1] = '\0';
strncpy((char*)params_.turn_server_password, "crossdeskpw", strncpy((char*)params_.turn_server_password, "crossdeskpw",
sizeof(params_.turn_server_password) - 1); sizeof(params_.turn_server_password) - 1);
params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0'; params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0';
strncpy(params_.tls_cert_path, server_cert_path.c_str(), strncpy(params_.tls_cert_path, tls_cert_path.c_str(),
sizeof(params_.tls_cert_path) - 1); sizeof(params_.tls_cert_path) - 1);
params_.tls_cert_path[sizeof(params_.tls_cert_path) - 1] = '\0'; params_.tls_cert_path[sizeof(params_.tls_cert_path) - 1] = '\0';
@@ -473,6 +496,8 @@ int Render::CreateConnectionPeer() {
: false; : false;
params_.enable_turn = config_center_->IsEnableTurn(); params_.enable_turn = config_center_->IsEnableTurn();
params_.enable_srtp = config_center_->IsEnableSrtp(); params_.enable_srtp = config_center_->IsEnableSrtp();
params_.video_quality =
static_cast<VideoQuality>(config_center_->GetVideoQuality());
params_.on_receive_video_buffer = nullptr; params_.on_receive_video_buffer = nullptr;
params_.on_receive_audio_buffer = OnReceiveAudioBufferCb; params_.on_receive_audio_buffer = OnReceiveAudioBufferCb;
params_.on_receive_data_buffer = OnReceiveDataBufferCb; params_.on_receive_data_buffer = OnReceiveDataBufferCb;
@@ -544,6 +569,14 @@ void Render::UpdateInteractions() {
screen_capturer_is_started_ = false; screen_capturer_is_started_ = false;
} }
if (start_speaker_capturer_ && !speaker_capturer_is_started_) {
StartSpeakerCapturer();
speaker_capturer_is_started_ = true;
} else if (!start_speaker_capturer_ && speaker_capturer_is_started_) {
StopSpeakerCapturer();
speaker_capturer_is_started_ = false;
}
if (start_mouse_controller_ && !mouse_controller_is_started_) { if (start_mouse_controller_ && !mouse_controller_is_started_) {
StartMouseController(); StartMouseController();
mouse_controller_is_started_ = true; mouse_controller_is_started_ = true;
@@ -874,6 +907,9 @@ int Render::DrawStreamWindow() {
} }
int Render::Run() { int Render::Run() {
latest_version_ = CheckUpdate();
update_available_ = IsNewerVersion(CROSSDESK_VERSION, latest_version_);
path_manager_ = std::make_unique<PathManager>("CrossDesk"); path_manager_ = std::make_unique<PathManager>("CrossDesk");
if (path_manager_) { if (path_manager_) {
cert_path_ = cert_path_ =
@@ -883,13 +919,14 @@ int Render::Run() {
cache_path_ = path_manager_->GetCachePath().string(); cache_path_ = path_manager_->GetCachePath().string();
config_center_ = config_center_ =
std::make_unique<ConfigCenter>(cache_path_ + "/config.ini", cert_path_); std::make_unique<ConfigCenter>(cache_path_ + "/config.ini", cert_path_);
strncpy(signal_server_ip_tmp_, config_center_->GetServerHost().c_str(), strncpy(signal_server_ip_self_,
sizeof(signal_server_ip_tmp_) - 1); config_center_->GetSignalServerHost().c_str(),
signal_server_ip_tmp_[sizeof(signal_server_ip_tmp_) - 1] = '\0'; sizeof(signal_server_ip_self_) - 1);
strncpy(signal_server_port_tmp_, signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
std::to_string(config_center_->GetServerPort()).c_str(), strncpy(signal_server_port_self_,
sizeof(signal_server_port_tmp_) - 1); std::to_string(config_center_->GetSignalServerPort()).c_str(),
signal_server_port_tmp_[sizeof(signal_server_port_tmp_) - 1] = '\0'; sizeof(signal_server_port_self_) - 1);
signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
strncpy(cert_file_path_, cert_path_.c_str(), sizeof(cert_file_path_) - 1); strncpy(cert_file_path_, cert_path_.c_str(), sizeof(cert_file_path_) - 1);
cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; cert_file_path_[sizeof(cert_file_path_) - 1] = '\0';
} else { } else {
@@ -1032,9 +1069,9 @@ void Render::MainLoop() {
remote_action.i.host_name[host_name.size()] = '\0'; remote_action.i.host_name[host_name.size()] = '\0';
remote_action.i.host_name_size = host_name.size(); remote_action.i.host_name_size = host_name.size();
std::vector<char> serialized = SerializeRemoteAction(remote_action); std::string msg = remote_action.to_json();
int ret = SendDataFrame(peer_, serialized.data(), serialized.size(), int ret =
data_label_.c_str()); SendDataFrame(peer_, msg.data(), msg.size(), data_label_.c_str());
FreeRemoteAction(remote_action); FreeRemoteAction(remote_action);
if (0 == ret) { if (0 == ret) {
need_to_send_host_info_ = false; need_to_send_host_info_ = false;
@@ -1160,6 +1197,10 @@ void Render::CleanupPeers() {
LOG_INFO("[{}] Leave connection [{}]", client_id_, client_id_); LOG_INFO("[{}] Leave connection [{}]", client_id_, client_id_);
LeaveConnection(peer_, client_id_); LeaveConnection(peer_, client_id_);
is_client_mode_ = false; is_client_mode_ = false;
StopScreenCapturer();
StopSpeakerCapturer();
StopMouseController();
StopKeyboardCapturer();
LOG_INFO("Destroy peer [{}]", client_id_); LOG_INFO("Destroy peer [{}]", client_id_);
DestroyPeer(&peer_); DestroyPeer(&peer_);
} }
@@ -1412,4 +1453,5 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
} }
break; break;
} }
} }
} // namespace crossdesk

View File

@@ -33,6 +33,7 @@
#include "win_tray.h" #include "win_tray.h"
#endif #endif
namespace crossdesk {
class Render { class Render {
public: public:
struct SubStreamWindowProperties { struct SubStreamWindowProperties {
@@ -48,10 +49,10 @@ class Render {
bool connection_established_ = false; bool connection_established_ = false;
bool rejoin_ = false; bool rejoin_ = false;
bool net_traffic_stats_button_pressed_ = false; bool net_traffic_stats_button_pressed_ = false;
bool mouse_control_button_pressed_ = false; bool mouse_control_button_pressed_ = true;
bool mouse_controller_is_started_ = false; bool mouse_controller_is_started_ = false;
bool audio_capture_button_pressed_ = false; bool audio_capture_button_pressed_ = true;
bool control_mouse_ = false; bool control_mouse_ = true;
bool streaming_ = false; bool streaming_ = false;
bool is_control_bar_in_left_ = true; bool is_control_bar_in_left_ = true;
bool control_bar_hovered_ = false; bool control_bar_hovered_ = false;
@@ -306,11 +307,15 @@ class Render {
#endif #endif
// main window properties // main window properties
std::string latest_version_ = "";
bool update_available_ = false;
bool start_mouse_controller_ = false; bool start_mouse_controller_ = false;
bool mouse_controller_is_started_ = false; bool mouse_controller_is_started_ = false;
bool start_screen_capturer_ = false; bool start_screen_capturer_ = false;
bool screen_capturer_is_started_ = false; bool screen_capturer_is_started_ = false;
bool start_keyboard_capturer_ = false; bool start_speaker_capturer_ = false;
bool speaker_capturer_is_started_ = false;
bool start_keyboard_capturer_ = true;
bool keyboard_capturer_is_started_ = false; bool keyboard_capturer_is_started_ = false;
bool foucs_on_main_window_ = false; bool foucs_on_main_window_ = false;
bool foucs_on_stream_window_ = false; bool foucs_on_stream_window_ = false;
@@ -429,35 +434,43 @@ class Render {
KeyboardCapturer* keyboard_capturer_ = nullptr; KeyboardCapturer* keyboard_capturer_ = nullptr;
std::vector<DisplayInfo> display_info_list_; std::vector<DisplayInfo> display_info_list_;
uint64_t last_frame_time_; uint64_t last_frame_time_;
bool show_new_version_icon_ = false;
bool show_new_version_icon_in_menu_ = true;
uint64_t new_version_icon_last_trigger_time_ = 0;
uint64_t new_version_icon_render_start_time_ = 0;
char client_id_[10] = ""; char client_id_[10] = "";
char client_id_display_[12] = ""; char client_id_display_[12] = "";
char client_id_with_password_[17] = ""; char client_id_with_password_[17] = "";
char password_saved_[7] = ""; char password_saved_[7] = "";
int language_button_value_ = 0; int language_button_value_ = 0;
int video_quality_button_value_ = 0; int video_quality_button_value_ = 0;
int video_frame_rate_button_value_ = 0; int video_frame_rate_button_value_ = 1;
int video_encode_format_button_value_ = 0; int video_encode_format_button_value_ = 0;
bool enable_hardware_video_codec_ = false; bool enable_hardware_video_codec_ = false;
bool enable_turn_ = false; bool enable_turn_ = false;
bool enable_srtp_ = false; bool enable_srtp_ = false;
char signal_server_ip_[256] = "api.crossdesk.cn"; char signal_server_ip_[256] = "api.crossdesk.cn";
char signal_server_port_[6] = "9099"; char signal_server_port_[6] = "9099";
char coturn_server_port_[6] = "3478";
char cert_file_path_[256] = ""; char cert_file_path_[256] = "";
bool enable_self_hosted_server_ = false; bool enable_self_hosted_ = false;
int language_button_value_last_ = 0; int language_button_value_last_ = 0;
int video_quality_button_value_last_ = 0; int video_quality_button_value_last_ = 0;
int video_frame_rate_button_value_last_ = 0;
int video_encode_format_button_value_last_ = 0; int video_encode_format_button_value_last_ = 0;
bool enable_hardware_video_codec_last_ = false; bool enable_hardware_video_codec_last_ = false;
bool enable_turn_last_ = false; bool enable_turn_last_ = false;
bool enable_srtp_last_ = false; bool enable_srtp_last_ = false;
bool enable_self_hosted_last_ = false;
bool enable_minimize_to_tray_ = false; bool enable_minimize_to_tray_ = false;
bool enable_minimize_to_tray_last_ = false; bool enable_minimize_to_tray_last_ = false;
char signal_server_ip_tmp_[256] = "api.crossdesk.cn"; char signal_server_ip_self_[256] = "";
char signal_server_port_tmp_[6] = "9099"; char signal_server_port_self_[6] = "";
char coturn_server_port_self_[6] = "";
std::string tls_cert_path_self_ = "";
bool settings_window_pos_reset_ = true; bool settings_window_pos_reset_ = true;
bool self_hosted_server_config_window_pos_reset_ = true; bool self_hosted_server_config_window_pos_reset_ = true;
std::string selected_current_file_path_ = ""; std::string selected_current_file_path_ = "";
std::string selected_file_ = "";
bool show_file_browser_ = true; bool show_file_browser_ = true;
/* ------ main window property end ------ */ /* ------ main window property end ------ */
@@ -466,6 +479,9 @@ class Render {
client_properties_; client_properties_;
void CloseTab(decltype(client_properties_)::iterator& it); void CloseTab(decltype(client_properties_)::iterator& it);
/* ------ stream window property end ------ */ /* ------ stream window property end ------ */
};
/* ------ server mode ------ */
std::unordered_map<std::string, ConnectionStatus> connection_status_;
};
} // namespace crossdesk
#endif #endif

View File

@@ -6,10 +6,7 @@
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#ifdef DESK_PORT_DEBUG namespace crossdesk {
#else
#define MOUSE_CONTROL 1
#endif
int Render::SendKeyCommand(int key_code, bool is_down) { int Render::SendKeyCommand(int key_code, bool is_down) {
RemoteAction remote_action; RemoteAction remote_action;
@@ -26,8 +23,9 @@ int Render::SendKeyCommand(int key_code, bool is_down) {
client_properties_.end()) { client_properties_.end()) {
auto props = client_properties_[controlled_remote_id_]; auto props = client_properties_[controlled_remote_id_];
if (props->connection_status_ == ConnectionStatus::Connected) { if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action, std::string msg = remote_action.to_json();
sizeof(remote_action), props->data_label_.c_str()); SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
} }
} }
} }
@@ -92,8 +90,10 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
if (props->control_bar_hovered_ || props->display_selectable_hovered_) { if (props->control_bar_hovered_ || props->display_selectable_hovered_) {
remote_action.m.flag = MouseFlag::move; remote_action.m.flag = MouseFlag::move;
} }
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str()); std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
} else if (SDL_EVENT_MOUSE_WHEEL == event.type && } else if (SDL_EVENT_MOUSE_WHEEL == event.type &&
last_mouse_event.button.x >= props->stream_render_rect_.x && last_mouse_event.button.x >= props->stream_render_rect_.x &&
last_mouse_event.button.x <= props->stream_render_rect_.x + last_mouse_event.button.x <= props->stream_render_rect_.x +
@@ -125,8 +125,9 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
(float)(event.button.y - props->stream_render_rect_.y) / (float)(event.button.y - props->stream_render_rect_.y) /
render_height; render_height;
SendDataFrame(props->peer_, (const char*)&remote_action, std::string msg = remote_action.to_json();
sizeof(remote_action), props->data_label_.c_str()); SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
} }
} }
@@ -224,6 +225,8 @@ void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
props->video_height_ = video_frame->height; props->video_height_ = video_frame->height;
props->video_size_ = video_frame->size; props->video_size_ = video_frame->size;
LOG_ERROR("receive: {}x{}", props->video_width_, props->video_height_);
if (need_to_update_render_rect) { if (need_to_update_render_rect) {
render->UpdateRenderRect(); render->UpdateRenderRect();
} }
@@ -277,64 +280,54 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
return; return;
} }
std::string json_str(data, size);
RemoteAction remote_action; RemoteAction remote_action;
memcpy(&remote_action, data, size);
try {
remote_action.from_json(json_str);
} catch (const std::exception& e) {
LOG_ERROR("Failed to parse RemoteAction JSON: {}", e.what());
return;
}
std::string remote_id(user_id, user_id_size); std::string remote_id(user_id, user_id_size);
if (render->client_properties_.find(remote_id) != if (render->client_properties_.find(remote_id) !=
render->client_properties_.end()) { render->client_properties_.end()) {
// local // local
auto props = render->client_properties_.find(remote_id)->second; auto props = render->client_properties_.find(remote_id)->second;
RemoteAction host_info; if (remote_action.type == ControlType::host_infomation &&
if (DeserializeRemoteAction(data, size, host_info)) { props->remote_host_name_.empty()) {
if (ControlType::host_infomation == host_info.type &&
props->remote_host_name_.empty()) {
props->remote_host_name_ =
std::string(host_info.i.host_name, host_info.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
for (int i = 0; i < host_info.i.display_num; i++) {
props->display_info_list_.push_back(DisplayInfo(
std::string(host_info.i.display_list[i]), host_info.i.left[i],
host_info.i.top[i], host_info.i.right[i], host_info.i.bottom[i]));
LOG_INFO("Remote display [{}:{}], bound [({}, {}) ({}, {})]", i + 1,
props->display_info_list_[i].name,
props->display_info_list_[i].left,
props->display_info_list_[i].top,
props->display_info_list_[i].right,
props->display_info_list_[i].bottom);
}
}
} else {
props->remote_host_name_ = std::string(remote_action.i.host_name, props->remote_host_name_ = std::string(remote_action.i.host_name,
remote_action.i.host_name_size); remote_action.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
LOG_ERROR("No remote display detected");
for (int i = 0; i < remote_action.i.display_num; i++) {
props->display_info_list_.push_back(
DisplayInfo(remote_action.i.display_list[i],
remote_action.i.left[i], remote_action.i.top[i],
remote_action.i.right[i], remote_action.i.bottom[i]));
}
} }
FreeRemoteAction(host_info); FreeRemoteAction(remote_action);
} else { } else {
// remote // remote
if (ControlType::mouse == remote_action.type && render->mouse_controller_) { if (remote_action.type == ControlType::mouse && render->mouse_controller_) {
render->mouse_controller_->SendMouseCommand(remote_action, render->mouse_controller_->SendMouseCommand(remote_action,
render->selected_display_); render->selected_display_);
} else if (ControlType::audio_capture == remote_action.type) { } else if (remote_action.type == ControlType::audio_capture) {
if (remote_action.a) { if (remote_action.a && !render->start_speaker_capturer_)
render->StartSpeakerCapturer(); render->StartSpeakerCapturer();
render->audio_capture_ = true; else if (!remote_action.a && render->start_speaker_capturer_)
} else {
render->StopSpeakerCapturer(); render->StopSpeakerCapturer();
render->audio_capture_ = false; } else if (remote_action.type == ControlType::keyboard &&
}
} else if (ControlType::keyboard == remote_action.type &&
render->keyboard_capturer_) { render->keyboard_capturer_) {
render->keyboard_capturer_->SendKeyboardCommand( render->keyboard_capturer_->SendKeyboardCommand(
(int)remote_action.k.key_value, (int)remote_action.k.key_value,
remote_action.k.flag == KeyFlag::key_down); remote_action.k.flag == KeyFlag::key_down);
} else if (ControlType::display_id == remote_action.type) { } else if (remote_action.type == ControlType::display_id &&
if (render->screen_capturer_) { render->screen_capturer_) {
render->selected_display_ = remote_action.d; render->selected_display_ = remote_action.d;
render->screen_capturer_->SwitchTo(remote_action.d); render->screen_capturer_->SwitchTo(remote_action.d);
}
} }
} }
} }
@@ -407,7 +400,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
props->connection_status_ = status; props->connection_status_ = status;
switch (status) { switch (status) {
case ConnectionStatus::Connected: case ConnectionStatus::Connected: {
if (!render->need_to_create_stream_window_ && if (!render->need_to_create_stream_window_ &&
!render->client_properties_.empty()) { !render->client_properties_.empty()) {
render->need_to_create_stream_window_ = true; render->need_to_create_stream_window_ = true;
@@ -418,14 +411,10 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
(int)render->stream_window_width_, (int)render->stream_window_width_,
(int)(render->stream_window_height_ - render->title_bar_height_)}; (int)(render->stream_window_height_ - render->title_bar_height_)};
break; break;
}
case ConnectionStatus::Disconnected: case ConnectionStatus::Disconnected:
case ConnectionStatus::Failed: case ConnectionStatus::Failed:
case ConnectionStatus::Closed: case ConnectionStatus::Closed: {
render->password_validating_time_ = 0;
render->start_screen_capturer_ = false;
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->control_mouse_ = false;
props->connection_established_ = false; props->connection_established_ = false;
props->mouse_control_button_pressed_ = false; props->mouse_control_button_pressed_ = false;
if (props->dst_buffer_) { if (props->dst_buffer_) {
@@ -434,8 +423,10 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
props->texture_width_); props->texture_width_);
} }
render->CleanSubStreamWindowProperties(props); render->CleanSubStreamWindowProperties(props);
break; break;
case ConnectionStatus::IncorrectPassword: }
case ConnectionStatus::IncorrectPassword: {
render->password_validating_ = false; render->password_validating_ = false;
render->password_validating_time_++; render->password_validating_time_++;
if (render->connect_button_pressed_) { if (render->connect_button_pressed_) {
@@ -445,37 +436,58 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
localization::connect[render->localization_language_index_]; localization::connect[render->localization_language_index_];
} }
break; break;
case ConnectionStatus::NoSuchTransmissionId: }
case ConnectionStatus::NoSuchTransmissionId: {
if (render->connect_button_pressed_) { if (render->connect_button_pressed_) {
props->connection_established_ = false; props->connection_established_ = false;
render->connect_button_label_ = render->connect_button_label_ =
localization::connect[render->localization_language_index_]; localization::connect[render->localization_language_index_];
} }
break; break;
}
default: default:
break; break;
} }
} else { } else {
render->is_client_mode_ = false; render->is_client_mode_ = false;
render->show_connection_status_window_ = true; render->show_connection_status_window_ = true;
render->connection_status_[remote_id] = status;
switch (status) { switch (status) {
case ConnectionStatus::Connected: case ConnectionStatus::Connected: {
render->need_to_send_host_info_ = true; render->need_to_send_host_info_ = true;
render->start_screen_capturer_ = true; render->start_screen_capturer_ = true;
render->start_mouse_controller_ = true; render->start_speaker_capturer_ = true;
break; #ifdef CROSSDESK_DEBUG
case ConnectionStatus::Closed:
render->start_screen_capturer_ = false;
render->start_mouse_controller_ = false; render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false; render->start_keyboard_capturer_ = false;
render->need_to_send_host_info_ = false; #else
if (props) props->connection_established_ = false; render->start_mouse_controller_ = true;
if (render->audio_capture_) { #endif
render->StopSpeakerCapturer(); break;
render->audio_capture_ = false; }
case ConnectionStatus::Closed: {
if (std::all_of(render->connection_status_.begin(),
render->connection_status_.end(), [](const auto& kv) {
return kv.second == ConnectionStatus::Closed ||
kv.second == ConnectionStatus::Failed ||
kv.second == ConnectionStatus::Disconnected;
})) {
render->start_screen_capturer_ = false;
render->start_speaker_capturer_ = false;
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->need_to_send_host_info_ = false;
if (props) props->connection_established_ = false;
if (render->audio_capture_) {
render->StopSpeakerCapturer();
render->audio_capture_ = false;
}
render->connection_status_.erase(remote_id);
} }
break; break;
}
default: default:
break; break;
} }
@@ -542,4 +554,5 @@ void Render::NetStatusReport(const char* client_id, size_t client_id_size,
if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) { if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) {
props->net_traffic_stats_ = *net_traffic_stats; props->net_traffic_stats_ = *net_traffic_stats;
} }
} }
} // namespace crossdesk

View File

@@ -3,6 +3,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
int CountDigits(int number) { int CountDigits(int number) {
if (number == 0) return 1; if (number == 0) return 1;
return (int)std::floor(std::log10(std::abs(number))) + 1; return (int)std::floor(std::log10(std::abs(number))) + 1;
@@ -66,8 +68,9 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
remote_action.type = ControlType::display_id; remote_action.type = ControlType::display_id;
remote_action.d = i; remote_action.d = i;
if (props->connection_status_ == ConnectionStatus::Connected) { if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action, std::string msg = remote_action.to_json();
sizeof(remote_action), props->data_label_.c_str()); SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
} }
} }
props->display_selectable_hovered_ = ImGui::IsWindowHovered(); props->display_selectable_hovered_ = ImGui::IsWindowHovered();
@@ -140,8 +143,9 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
RemoteAction remote_action; RemoteAction remote_action;
remote_action.type = ControlType::audio_capture; remote_action.type = ControlType::audio_capture;
remote_action.a = props->audio_capture_button_pressed_; remote_action.a = props->audio_capture_button_pressed_;
SendDataFrame(props->peer_, (const char*)&remote_action, std::string msg = remote_action.to_json();
sizeof(remote_action), props->data_label_.c_str()); SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
} }
} }
if (!props->audio_capture_button_pressed_) { if (!props->audio_capture_button_pressed_) {
@@ -324,3 +328,4 @@ int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -1,6 +1,8 @@
#include "localization.h" #include "localization.h"
#include "render.h" #include "render.h"
namespace crossdesk {
int Render::StatusBar() { int Render::StatusBar() {
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e; static bool a, b, c, d, e;
@@ -35,4 +37,5 @@ int Render::StatusBar() {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::EndChild(); ImGui::EndChild();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,6 +3,9 @@
#include "render.h" #include "render.h"
#define BUTTON_PADDING 36.0f #define BUTTON_PADDING 36.0f
#define NEW_VERSION_ICON_RENDER_TIME_INTERVAL 2000
namespace crossdesk {
int Render::TitleBar(bool main_window) { int Render::TitleBar(bool main_window) {
ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
@@ -37,13 +40,59 @@ int Render::TitleBar(bool main_window) {
localization::settings[localization_language_index_].c_str())) { localization::settings[localization_language_index_].c_str())) {
show_settings_window_ = true; show_settings_window_ = true;
} }
if (ImGui::MenuItem(
localization::about[localization_language_index_].c_str())) { show_new_version_icon_in_menu_ = false;
std::string about_str =
localization::about[localization_language_index_];
if (update_available_) {
auto now_time =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
// every 2 seconds
if (now_time - new_version_icon_last_trigger_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL) {
show_new_version_icon_ = true;
new_version_icon_render_start_time_ = now_time;
new_version_icon_last_trigger_time_ = now_time;
}
// render for 1 second
if (show_new_version_icon_) {
about_str = about_str + " " + ICON_FA_TRIANGLE_EXCLAMATION;
if (now_time - new_version_icon_render_start_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL / 2) {
show_new_version_icon_ = false;
}
} else {
about_str = about_str + " ";
}
}
if (ImGui::MenuItem(about_str.c_str())) {
show_about_window_ = true; show_about_window_ = true;
} }
if (update_available_ && ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
std::string new_version_available_str =
localization::new_version_available
[localization_language_index_] +
": " + latest_version_;
ImGui::Text("%s", new_version_available_str.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
ImGui::SetWindowFontScale(1.0f); ImGui::SetWindowFontScale(1.0f);
ImGui::EndMenu(); ImGui::EndMenu();
} else {
show_new_version_icon_in_menu_ = true;
} }
float menu_bar_line_size = 15.0f; float menu_bar_line_size = 15.0f;
draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y - 6), draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y - 6),
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y - 6), ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y - 6),
@@ -55,6 +104,33 @@ int Render::TitleBar(bool main_window) {
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y + 6), ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y + 6),
IM_COL32(0, 0, 0, 255)); IM_COL32(0, 0, 0, 255));
if (update_available_ && show_new_version_icon_in_menu_) {
auto now_time = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
// every 2 seconds
if (now_time - new_version_icon_last_trigger_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL) {
show_new_version_icon_ = true;
new_version_icon_render_start_time_ = now_time;
new_version_icon_last_trigger_time_ = now_time;
}
// render for 1 second
if (show_new_version_icon_) {
ImGui::SetWindowFontScale(0.6f);
ImGui::SetCursorPos(ImVec2(bar_pos_x + 10, bar_pos_y - 17));
ImGui::Text(ICON_FA_TRIANGLE_EXCLAMATION);
ImGui::SetWindowFontScale(1.0f);
if (now_time - new_version_icon_render_start_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL / 2) {
show_new_version_icon_ = false;
}
}
}
{ {
SettingWindow(); SettingWindow();
SelfHostedServerWindow(); SelfHostedServerWindow();
@@ -171,4 +247,5 @@ int Render::TitleBar(bool main_window) {
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleColor(); ImGui::PopStyleColor();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -4,6 +4,8 @@
#include "localization.h" #include "localization.h"
namespace crossdesk {
// callback for the message-only window that handles tray icon messages // callback for the message-only window that handles tray icon messages
static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam, static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam) { LPARAM lParam) {
@@ -109,4 +111,5 @@ bool WinTray::HandleTrayMessage(MSG* msg) {
} }
} }
return true; return true;
} }
} // namespace crossdesk

View File

@@ -14,6 +14,8 @@
#define WM_TRAY_CALLBACK (WM_USER + 1) #define WM_TRAY_CALLBACK (WM_USER + 1)
namespace crossdesk {
class WinTray { class WinTray {
public: public:
WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip, WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
@@ -32,5 +34,5 @@ class WinTray {
int language_index_; int language_index_;
NOTIFYICONDATA nid_; NOTIFYICONDATA nid_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -1,18 +1,53 @@
#include <cstdlib>
#include <string>
#include "layout.h" #include "layout.h"
#include "localization.h" #include "localization.h"
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
void Hyperlink(const std::string& label, const std::string& url,
const float window_width) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 255, 255));
ImGui::SetCursorPosX(window_width * 0.1f);
ImGui::Text("%s", label.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.6f);
ImGui::TextUnformatted(url.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
#if defined(_WIN32)
std::string cmd = "start " + url;
#elif defined(__APPLE__)
std::string cmd = "open " + url;
#else
std::string cmd = "xdg-open " + url;
#endif
system(cmd.c_str()); // open browser
}
}
}
int Render::AboutWindow() { int Render::AboutWindow() {
if (show_about_window_) { if (show_about_window_) {
const ImGuiViewport *viewport = ImGui::GetMainViewport(); const ImGuiViewport* viewport = ImGui::GetMainViewport();
float about_window_height = latest_version_.empty()
? about_window_height_
: about_window_height_ + 20.0f;
ImGui::SetNextWindowPos(ImVec2( ImGui::SetNextWindowPos(ImVec2(
(viewport->WorkSize.x - viewport->WorkPos.x - about_window_width_) / 2, (viewport->WorkSize.x - viewport->WorkPos.x - about_window_width_) / 2,
(viewport->WorkSize.y - viewport->WorkPos.y - about_window_height_) / (viewport->WorkSize.y - viewport->WorkPos.y - about_window_height) /
2)); 2));
ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height_)); ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
@@ -34,17 +69,30 @@ int Render::AboutWindow() {
#endif #endif
std::string text = localization::version[localization_language_index_] + std::string text = localization::version[localization_language_index_] +
": CrossDesk v" + version; ": CrossDesk " + version;
ImGui::SetCursorPosX(about_window_width_ * 0.1f);
ImGui::Text("%s", text.c_str()); ImGui::Text("%s", text.c_str());
if (update_available_) {
std::string latest_version =
localization::new_version_available[localization_language_index_] +
": " + latest_version_;
std::string access_website =
localization::access_website[localization_language_index_];
Hyperlink(latest_version, "https://crossdesk.cn", about_window_width_);
}
ImGui::Text(""); ImGui::Text("");
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved."; std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
std::string license_text = "Licensed under GNU LGPL v3."; std::string license_text = "Licensed under GNU LGPL v3.";
ImGui::SetCursorPosX(about_window_width_ * 0.1f);
ImGui::Text("%s", copyright_text.c_str()); ImGui::Text("%s", copyright_text.c_str());
ImGui::SetCursorPosX(about_window_width_ * 0.1f);
ImGui::Text("%s", license_text.c_str()); ImGui::Text("%s", license_text.c_str());
ImGui::SetCursorPosX(about_window_width_ * 0.42f); ImGui::SetCursorPosX(about_window_width_ * 0.42f);
ImGui::SetCursorPosY(about_window_height_ * 0.75f); ImGui::SetCursorPosY(about_window_height * 0.75f);
// OK // OK
if (ImGui::Button(localization::ok[localization_language_index_].c_str())) { if (ImGui::Button(localization::ok[localization_language_index_].c_str())) {
show_about_window_ = false; show_about_window_ = false;
@@ -58,4 +106,5 @@ int Render::AboutWindow() {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,9 +3,11 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
bool Render::ConnectionStatusWindow( bool Render::ConnectionStatusWindow(
std::shared_ptr<SubStreamWindowProperties> &props) { std::shared_ptr<SubStreamWindowProperties>& props) {
const ImGuiViewport *viewport = ImGui::GetMainViewport(); const ImGuiViewport* viewport = ImGui::GetMainViewport();
bool ret_flag = false; bool ret_flag = false;
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
connection_status_window_width_) / connection_status_window_width_) /
@@ -168,4 +170,5 @@ bool Render::ConnectionStatusWindow(
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return ret_flag; return ret_flag;
} }
} // namespace crossdesk

View File

@@ -1,7 +1,9 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) { namespace crossdesk {
int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
double time_duration = double time_duration =
ImGui::GetTime() - props->control_bar_button_pressed_time_; ImGui::GetTime() - props->control_bar_button_pressed_time_;
if (props->control_window_width_is_changing_) { if (props->control_window_width_is_changing_) {
@@ -220,4 +222,5 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,6 +3,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
int Render::SettingWindow() { int Render::SettingWindow() {
if (show_settings_window_) { if (show_settings_window_) {
if (settings_window_pos_reset_) { if (settings_window_pos_reset_) {
@@ -80,11 +82,11 @@ int Render::SettingWindow() {
{ {
const char* video_quality_items[] = { const char* video_quality_items[] = {
localization::video_quality_high[localization_language_index_] localization::video_quality_low[localization_language_index_]
.c_str(), .c_str(),
localization::video_quality_medium[localization_language_index_] localization::video_quality_medium[localization_language_index_]
.c_str(), .c_str(),
localization::video_quality_low[localization_language_index_] localization::video_quality_high[localization_language_index_]
.c_str()}; .c_str()};
settings_items_offset += settings_items_padding; settings_items_offset += settings_items_padding;
@@ -229,8 +231,7 @@ int Render::SettingWindow() {
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN); ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN);
} }
ImGui::SetCursorPosY(settings_items_offset); ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_self_hosted_server", ImGui::Checkbox("##enable_self_hosted", &enable_self_hosted_);
&enable_self_hosted_server_);
} }
#if _WIN32 #if _WIN32
ImGui::Separator(); ImGui::Separator();
@@ -287,14 +288,23 @@ int Render::SettingWindow() {
// Video quality // Video quality
if (video_quality_button_value_ == 0) { if (video_quality_button_value_ == 0) {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH); config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
} else if (video_quality_button_value_ == 1) { } else if (video_quality_button_value_ == 1) {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM); config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
} else { } else {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW); config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
} }
video_quality_button_value_last_ = video_quality_button_value_; video_quality_button_value_last_ = video_quality_button_value_;
if (video_frame_rate_button_value_ == 0) {
config_center_->SetVideoFrameRate(
ConfigCenter::VIDEO_FRAME_RATE::FPS_30);
} else if (video_frame_rate_button_value_ == 1) {
config_center_->SetVideoFrameRate(
ConfigCenter::VIDEO_FRAME_RATE::FPS_60);
}
video_frame_rate_button_value_last_ = video_frame_rate_button_value_;
// Video encode format // Video encode format
if (video_encode_format_button_value_ == 0) { if (video_encode_format_button_value_ == 0) {
config_center_->SetVideoEncodeFormat( config_center_->SetVideoEncodeFormat(
@@ -330,11 +340,12 @@ int Render::SettingWindow() {
} }
enable_srtp_last_ = enable_srtp_; enable_srtp_last_ = enable_srtp_;
if (enable_self_hosted_server_) { if (enable_self_hosted_) {
config_center_->SetSelfHosted(true); config_center_->SetSelfHosted(true);
} else { } else {
config_center_->SetSelfHosted(false); config_center_->SetSelfHosted(false);
} }
enable_self_hosted_last_ = enable_self_hosted_;
settings_window_pos_reset_ = true; settings_window_pos_reset_ = true;
@@ -364,6 +375,11 @@ int Render::SettingWindow() {
video_quality_button_value_ = video_quality_button_value_last_; video_quality_button_value_ = video_quality_button_value_last_;
} }
if (video_frame_rate_button_value_ !=
video_frame_rate_button_value_last_) {
video_frame_rate_button_value_ = video_frame_rate_button_value_last_;
}
if (video_encode_format_button_value_ != if (video_encode_format_button_value_ !=
video_encode_format_button_value_last_) { video_encode_format_button_value_last_) {
video_encode_format_button_value_ = video_encode_format_button_value_ =
@@ -390,4 +406,5 @@ int Render::SettingWindow() {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -2,6 +2,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
int Render::MainWindow() { int Render::MainWindow() {
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
@@ -47,3 +49,4 @@ int Render::MainWindow() {
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -10,6 +10,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
std::vector<std::string> GetRootEntries() { std::vector<std::string> GetRootEntries() {
std::vector<std::string> roots; std::vector<std::string> roots;
#ifdef _WIN32 #ifdef _WIN32
@@ -33,8 +35,9 @@ int Render::ShowSimpleFileBrowser() {
selected_current_file_path_ = std::filesystem::current_path().string(); selected_current_file_path_ = std::filesystem::current_path().string();
} }
if (!selected_file_.empty()) { if (!tls_cert_path_self_.empty()) {
display_text = std::filesystem::path(selected_file_).filename().string(); display_text =
std::filesystem::path(tls_cert_path_self_).filename().string();
} else if (selected_current_file_path_ != "Root") { } else if (selected_current_file_path_ != "Root") {
display_text = display_text =
std::filesystem::path(selected_current_file_path_).filename().string(); std::filesystem::path(selected_current_file_path_).filename().string();
@@ -66,7 +69,7 @@ int Render::ShowSimpleFileBrowser() {
for (const auto& root : roots) { for (const auto& root : roots) {
if (ImGui::Selectable(root.c_str())) { if (ImGui::Selectable(root.c_str())) {
selected_current_file_path_ = root; selected_current_file_path_ = root;
selected_file_.clear(); tls_cert_path_self_.clear();
} }
} }
} else { } else {
@@ -81,7 +84,7 @@ int Render::ShowSimpleFileBrowser() {
} else { } else {
selected_current_file_path_ = "Root"; selected_current_file_path_ = "Root";
} }
selected_file_.clear(); tls_cert_path_self_.clear();
} }
try { try {
@@ -91,11 +94,11 @@ int Render::ShowSimpleFileBrowser() {
if (entry.is_directory()) { if (entry.is_directory()) {
if (ImGui::Selectable(name.c_str())) { if (ImGui::Selectable(name.c_str())) {
selected_current_file_path_ = entry.path().string(); selected_current_file_path_ = entry.path().string();
selected_file_.clear(); tls_cert_path_self_.clear();
} }
} else { } else {
if (ImGui::Selectable(name.c_str())) { if (ImGui::Selectable(name.c_str())) {
selected_file_ = entry.path().string(); tls_cert_path_self_ = entry.path().string();
file_selected = true; file_selected = true;
show_file_browser_ = false; show_file_browser_ = false;
} }
@@ -182,8 +185,8 @@ int Render::SelfHostedServerWindow() {
ImGui::SetCursorPosY(settings_items_offset); ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
ImGui::InputText("##signal_server_ip_tmp_", signal_server_ip_tmp_, ImGui::InputText("##signal_server_ip_self_", signal_server_ip_self_,
IM_ARRAYSIZE(signal_server_ip_tmp_), IM_ARRAYSIZE(signal_server_ip_self_),
ImGuiInputTextFlags_AlwaysOverwrite); ImGuiInputTextFlags_AlwaysOverwrite);
} }
@@ -205,8 +208,29 @@ int Render::SelfHostedServerWindow() {
ImGui::SetCursorPosY(settings_items_offset); ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
ImGui::InputText("##signal_server_port_tmp_", signal_server_port_tmp_, ImGui::InputText("##signal_server_port_self_", signal_server_port_self_,
IM_ARRAYSIZE(signal_server_port_tmp_)); IM_ARRAYSIZE(signal_server_port_self_));
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text("%s", localization::self_hosted_server_coturn_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("##coturn_server_port_self_", coturn_server_port_self_,
IM_ARRAYSIZE(coturn_server_port_self_));
} }
ImGui::Separator(); ImGui::Separator();
@@ -248,16 +272,20 @@ int Render::SelfHostedServerWindow() {
localization::ok[localization_language_index_].c_str())) { localization::ok[localization_language_index_].c_str())) {
show_self_hosted_server_config_window_ = false; show_self_hosted_server_config_window_ = false;
config_center_->SetServerHost(signal_server_ip_tmp_); config_center_->SetServerHost(signal_server_ip_self_);
config_center_->SetServerPort(atoi(signal_server_port_tmp_)); config_center_->SetServerPort(atoi(signal_server_port_self_));
config_center_->SetCertFilePath(selected_file_); config_center_->SetCoturnServerPort(atoi(coturn_server_port_self_));
strncpy(signal_server_ip_, signal_server_ip_tmp_, config_center_->SetCertFilePath(tls_cert_path_self_);
strncpy(signal_server_ip_, signal_server_ip_self_,
sizeof(signal_server_ip_) - 1); sizeof(signal_server_ip_) - 1);
signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0'; signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0';
strncpy(signal_server_port_, signal_server_port_tmp_, strncpy(signal_server_port_, signal_server_port_self_,
sizeof(signal_server_port_) - 1); sizeof(signal_server_port_) - 1);
signal_server_port_[sizeof(signal_server_port_) - 1] = '\0'; signal_server_port_[sizeof(signal_server_port_) - 1] = '\0';
strncpy(cert_file_path_, selected_file_.c_str(), strncpy(coturn_server_port_, coturn_server_port_self_,
sizeof(coturn_server_port_) - 1);
coturn_server_port_[sizeof(coturn_server_port_) - 1] = '\0';
strncpy(cert_file_path_, tls_cert_path_self_.c_str(),
sizeof(cert_file_path_) - 1); sizeof(cert_file_path_) - 1);
cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; cert_file_path_[sizeof(cert_file_path_) - 1] = '\0';
@@ -271,15 +299,15 @@ int Render::SelfHostedServerWindow() {
show_self_hosted_server_config_window_ = false; show_self_hosted_server_config_window_ = false;
self_hosted_server_config_window_pos_reset_ = true; self_hosted_server_config_window_pos_reset_ = true;
strncpy(signal_server_ip_tmp_, signal_server_ip_, strncpy(signal_server_ip_self_, signal_server_ip_,
sizeof(signal_server_ip_tmp_) - 1); sizeof(signal_server_ip_self_) - 1);
signal_server_ip_tmp_[sizeof(signal_server_ip_tmp_) - 1] = '\0'; signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
strncpy(signal_server_port_tmp_, signal_server_port_, strncpy(signal_server_port_self_, signal_server_port_,
sizeof(signal_server_port_tmp_) - 1); sizeof(signal_server_port_self_) - 1);
signal_server_port_tmp_[sizeof(signal_server_port_tmp_) - 1] = '\0'; signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
config_center_->SetServerHost(signal_server_ip_tmp_); config_center_->SetServerHost(signal_server_ip_self_);
config_center_->SetServerPort(atoi(signal_server_port_tmp_)); config_center_->SetServerPort(atoi(signal_server_port_self_));
selected_file_.clear(); tls_cert_path_self_.clear();
} }
ImGui::SetWindowFontScale(1.0f); ImGui::SetWindowFontScale(1.0f);
@@ -292,4 +320,5 @@ int Render::SelfHostedServerWindow() {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -2,6 +2,8 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk {
void Render::DrawConnectionStatusText( void Render::DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties>& props) { std::shared_ptr<SubStreamWindowProperties>& props) {
std::string text; std::string text;
@@ -199,4 +201,5 @@ int Render::StreamWindow() {
ImGui::End(); // End VideoBg ImGui::End(); // End VideoBg
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,6 +3,8 @@
#include <atomic> #include <atomic>
#include <filesystem> #include <filesystem>
namespace crossdesk {
namespace { namespace {
std::string g_log_dir = "logs"; std::string g_log_dir = "logs";
@@ -60,3 +62,4 @@ std::shared_ptr<spdlog::logger> get_logger() {
return g_logger; return g_logger;
} }
} // namespace crossdesk

View File

@@ -25,6 +25,8 @@
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
namespace crossdesk {
constexpr auto LOGGER_NAME = "crossdesk"; constexpr auto LOGGER_NAME = "crossdesk";
void InitLogger(const std::string& log_dir); void InitLogger(const std::string& log_dir);
@@ -35,5 +37,5 @@ std::shared_ptr<spdlog::logger> get_logger();
#define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__) #define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__)
#define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__) #define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__)
#define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__) #define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__)
} // namespace crossdesk
#endif #endif

View File

@@ -2,6 +2,8 @@
#include <cstdlib> #include <cstdlib>
namespace crossdesk {
PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {} PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {}
std::filesystem::path PathManager::GetConfigPath() { std::filesystem::path PathManager::GetConfigPath() {
@@ -16,7 +18,11 @@ std::filesystem::path PathManager::GetConfigPath() {
std::filesystem::path PathManager::GetCachePath() { std::filesystem::path PathManager::GetCachePath() {
#ifdef _WIN32 #ifdef _WIN32
#ifdef CROSSDESK_DEBUG
return "cache";
#else
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache"; return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache";
#endif
#elif __APPLE__ #elif __APPLE__
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
#else #else
@@ -70,22 +76,35 @@ std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) {
#endif #endif
std::string PathManager::GetHome() { std::string PathManager::GetHome() {
if (const char* home = getenv("HOME")) {
return std::string(home);
}
#ifdef _WIN32 #ifdef _WIN32
char path[MAX_PATH]; char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path))) if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path)))
return std::string(path); return std::string(path);
#else
if (const char* home = getenv("HOME")) {
return std::string(home);
}
#endif #endif
return {}; return {};
} }
std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var, std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var,
const std::string& def) { const std::string& def) {
#ifdef _WIN32
char* buffer = nullptr;
size_t size = 0;
if (_dupenv_s(&buffer, &size, env_var) == 0 && buffer != nullptr) {
std::filesystem::path result(buffer);
free(buffer);
return result;
}
#else
if (const char* val = getenv(env_var)) { if (const char* val = getenv(env_var)) {
return std::filesystem::path(val); return std::filesystem::path(val);
} }
#endif
return std::filesystem::path(def); return std::filesystem::path(def);
} }
} // namespace crossdesk

View File

@@ -14,6 +14,8 @@
#include <windows.h> #include <windows.h>
#endif #endif
namespace crossdesk {
class PathManager { class PathManager {
public: public:
explicit PathManager(const std::string& app_name); explicit PathManager(const std::string& app_name);
@@ -40,5 +42,5 @@ class PathManager {
private: private:
std::string app_name_; std::string app_name_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -6,6 +6,8 @@
#include "libyuv.h" #include "libyuv.h"
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
ScreenCapturerX11::ScreenCapturerX11() {} ScreenCapturerX11::ScreenCapturerX11() {}
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); } ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
@@ -171,4 +173,5 @@ void ScreenCapturerX11::OnFrame() {
} }
XDestroyImage(image); XDestroyImage(image);
} }
} // namespace crossdesk

View File

@@ -20,6 +20,8 @@
#include "screen_capturer.h" #include "screen_capturer.h"
namespace crossdesk {
class ScreenCapturerX11 : public ScreenCapturer { class ScreenCapturerX11 : public ScreenCapturer {
public: public:
ScreenCapturerX11(); ScreenCapturerX11();
@@ -60,5 +62,5 @@ class ScreenCapturerX11 : public ScreenCapturer {
std::vector<uint8_t> y_plane_; std::vector<uint8_t> y_plane_;
std::vector<uint8_t> uv_plane_; std::vector<uint8_t> uv_plane_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -2,6 +2,8 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
ScreenCapturerSck::ScreenCapturerSck() {} ScreenCapturerSck::ScreenCapturerSck() {}
ScreenCapturerSck::~ScreenCapturerSck() {} ScreenCapturerSck::~ScreenCapturerSck() {}
@@ -70,4 +72,5 @@ std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
void ScreenCapturerSck::OnFrame() {} void ScreenCapturerSck::OnFrame() {}
void ScreenCapturerSck::CleanUp() {} void ScreenCapturerSck::CleanUp() {}
} // namespace crossdesk

View File

@@ -16,6 +16,8 @@
#include "screen_capturer.h" #include "screen_capturer.h"
namespace crossdesk {
class ScreenCapturerSck : public ScreenCapturer { class ScreenCapturerSck : public ScreenCapturer {
public: public:
ScreenCapturerSck(); ScreenCapturerSck();
@@ -55,5 +57,5 @@ class ScreenCapturerSck : public ScreenCapturer {
private: private:
std::unique_ptr<ScreenCapturer> screen_capturer_sck_impl_; std::unique_ptr<ScreenCapturer> screen_capturer_sck_impl_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -22,9 +22,12 @@
#include "display_info.h" #include "display_info.h"
#include "rd_log.h" #include "rd_log.h"
static const int kFullDesktopScreenId = -1; using namespace crossdesk;
class ScreenCapturerSckImpl; class ScreenCapturerSckImpl;
static const int kFullDesktopScreenId = -1;
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture // The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
// was reported to be broken before macOS 13 - see http://crbug.com/40234870. // was reported to be broken before macOS 13 - see http://crbug.com/40234870.
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were // Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
@@ -424,10 +427,6 @@ void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
[SCShareableContent getShareableContentWithCompletionHandler:handler]; [SCShareableContent getShareableContentWithCompletionHandler:handler];
} }
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
return std::make_unique<ScreenCapturerSckImpl>();
}
@implementation SckHelper { @implementation SckHelper {
// This lock is to prevent the capturer being destroyed while an instance // This lock is to prevent the capturer being destroyed while an instance
// method is still running on another thread. // method is still running on another thread.
@@ -485,4 +484,8 @@ std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
_capturer = nullptr; _capturer = nullptr;
} }
@end @end
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
return std::make_unique<ScreenCapturerSckImpl>();
}

View File

@@ -11,6 +11,8 @@
#include "display_info.h" #include "display_info.h"
namespace crossdesk {
class ScreenCapturer { class ScreenCapturer {
public: public:
typedef std::function<void(unsigned char*, int, int, int, const char*)> typedef std::function<void(unsigned char*, int, int, int, const char*)>
@@ -30,5 +32,5 @@ class ScreenCapturer {
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0; virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
virtual int SwitchTo(int monitor_index) = 0; virtual int SwitchTo(int monitor_index) = 0;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -16,6 +16,8 @@
#include "screen_capturer_sck.h" #include "screen_capturer_sck.h"
#endif #endif
namespace crossdesk {
class ScreenCapturerFactory { class ScreenCapturerFactory {
public: public:
virtual ~ScreenCapturerFactory() {} virtual ~ScreenCapturerFactory() {}
@@ -34,5 +36,5 @@ class ScreenCapturerFactory {
#endif #endif
} }
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -10,35 +10,46 @@
#include "libyuv.h" #include "libyuv.h"
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
static std::vector<DisplayInfo> gs_display_list; static std::vector<DisplayInfo> gs_display_list;
std::string WideToUtf8(const wchar_t *wideStr) { std::string WideToUtf8(const std::wstring& wstr) {
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0, if (wstr.empty()) return {};
nullptr, nullptr); int size_needed = WideCharToMultiByte(
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
std::string result(size_needed, 0); std::string result(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr, WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
nullptr); size_needed, nullptr, nullptr);
result.pop_back();
return result; return result;
} }
std::string CleanDisplayName(const std::wstring& wide_name) {
std::string name = WideToUtf8(wide_name);
name.erase(std::remove_if(name.begin(), name.end(),
[](unsigned char c) { return !std::isalnum(c); }),
name.end());
return name;
}
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc, BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
[[maybe_unused]] LPRECT lprc, LPARAM data) { [[maybe_unused]] LPRECT lprc, LPARAM data) {
MONITORINFOEX monitor_info_; MONITORINFOEX monitor_info_;
monitor_info_.cbSize = sizeof(MONITORINFOEX); monitor_info_.cbSize = sizeof(MONITORINFOEX);
if (GetMonitorInfo(hmonitor, &monitor_info_)) { if (GetMonitorInfo(hmonitor, &monitor_info_)) {
std::string display_name = CleanDisplayName(monitor_info_.szDevice);
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) { if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
gs_display_list.insert( gs_display_list.insert(
gs_display_list.begin(), gs_display_list.begin(),
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice), {(void*)hmonitor, display_name,
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom}); monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR *)data = hmonitor; *(HMONITOR*)data = hmonitor;
} else { } else {
gs_display_list.push_back(DisplayInfo( gs_display_list.push_back(DisplayInfo(
(void *)hmonitor, WideToUtf8(monitor_info_.szDevice), (void*)hmonitor, display_name,
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom)); monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
@@ -81,7 +92,7 @@ bool ScreenCapturerWgc::IsWgcSupported() {
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */ /* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
return winrt::Windows::Foundation::Metadata::ApiInformation:: return winrt::Windows::Foundation::Metadata::ApiInformation::
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8); IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
} catch (const winrt::hresult_error &) { } catch (const winrt::hresult_error&) {
return false; return false;
} catch (...) { } catch (...) {
return false; return false;
@@ -115,7 +126,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
} }
for (int i = 0; i < display_info_list_.size(); i++) { for (int i = 0; i < display_info_list_.size(); i++) {
const auto &display = display_info_list_[i]; const auto& display = display_info_list_[i];
LOG_INFO( LOG_INFO(
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - " "index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
"({}, {})", "({}, {})",
@@ -243,16 +254,16 @@ int ScreenCapturerWgc::SwitchTo(int monitor_index) {
return 0; return 0;
} }
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame, void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
int id) { int id) {
if (on_data_) { if (on_data_) {
if (!nv12_frame_) { if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2]; nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
} }
libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4, libyuv::ARGBToNV12((const uint8_t*)frame.data, frame.width * 4,
(uint8_t *)nv12_frame_, frame.width, (uint8_t*)nv12_frame_, frame.width,
(uint8_t *)(nv12_frame_ + frame.width * frame.height), (uint8_t*)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height); frame.width, frame.width, frame.height);
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width, on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
@@ -262,7 +273,7 @@ void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame,
void ScreenCapturerWgc::CleanUp() { void ScreenCapturerWgc::CleanUp() {
if (inited_) { if (inited_) {
for (auto &session : sessions_) { for (auto& session : sessions_) {
if (session.session_) { if (session.session_) {
session.session_->Stop(); session.session_->Stop();
} }
@@ -270,3 +281,4 @@ void ScreenCapturerWgc::CleanUp() {
sessions_.clear(); sessions_.clear();
} }
} }
} // namespace crossdesk

View File

@@ -11,6 +11,8 @@
#include "wgc_session.h" #include "wgc_session.h"
#include "wgc_session_impl.h" #include "wgc_session_impl.h"
namespace crossdesk {
class ScreenCapturerWgc : public ScreenCapturer, class ScreenCapturerWgc : public ScreenCapturer,
public WgcSession::wgc_session_observer { public WgcSession::wgc_session_observer {
public: public:
@@ -64,5 +66,5 @@ class ScreenCapturerWgc : public ScreenCapturer,
unsigned char* nv12_frame_ = nullptr; unsigned char* nv12_frame_ = nullptr;
unsigned char* nv12_frame_scaled_ = nullptr; unsigned char* nv12_frame_scaled_ = nullptr;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -3,6 +3,8 @@
#include <Windows.h> #include <Windows.h>
namespace crossdesk {
class WgcSession { class WgcSession {
public: public:
struct wgc_session_frame { struct wgc_session_frame {
@@ -10,13 +12,13 @@ class WgcSession {
unsigned int height; unsigned int height;
unsigned int row_pitch; unsigned int row_pitch;
const unsigned char *data; const unsigned char* data;
}; };
class wgc_session_observer { class wgc_session_observer {
public: public:
virtual ~wgc_session_observer() {} virtual ~wgc_session_observer() {}
virtual void OnFrame(const wgc_session_frame &frame, int id) = 0; virtual void OnFrame(const wgc_session_frame& frame, int id) = 0;
}; };
public: public:
@@ -25,7 +27,7 @@ class WgcSession {
virtual int Initialize(HWND hwnd) = 0; virtual int Initialize(HWND hwnd) = 0;
virtual int Initialize(HMONITOR hmonitor) = 0; virtual int Initialize(HMONITOR hmonitor) = 0;
virtual void RegisterObserver(wgc_session_observer *observer) = 0; virtual void RegisterObserver(wgc_session_observer* observer) = 0;
virtual int Start() = 0; virtual int Start() = 0;
virtual int Stop() = 0; virtual int Stop() = 0;
@@ -33,7 +35,7 @@ class WgcSession {
virtual int Pause() = 0; virtual int Pause() = 0;
virtual int Resume() = 0; virtual int Resume() = 0;
virtual ~WgcSession(){}; virtual ~WgcSession() {};
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -18,9 +18,11 @@
throw winrt::hresult_error(RO_E_CLOSED); \ throw winrt::hresult_error(RO_E_CLOSED); \
} }
namespace crossdesk {
extern "C" { extern "C" {
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice( HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); ::IDXGIDevice* dxgiDevice, ::IInspectable** graphicsDevice);
} }
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {} WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
@@ -48,7 +50,7 @@ int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
return Initialize(); return Initialize();
} }
void WgcSessionImpl::RegisterObserver(wgc_session_observer *observer) { void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
std::lock_guard locker(lock_); std::lock_guard locker(lock_);
observer_ = observer; observer_ = observer;
} }
@@ -175,7 +177,7 @@ auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
interop_factory->CreateForWindow( interop_factory->CreateForWindow(
hwnd, hwnd,
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(), winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
reinterpret_cast<void **>(winrt::put_abi(item))); reinterpret_cast<void**>(winrt::put_abi(item)));
return item; return item;
} }
@@ -187,7 +189,7 @@ auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
interop_factory->CreateForMonitor( interop_factory->CreateForMonitor(
hmonitor, hmonitor,
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(), winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
reinterpret_cast<void **>(winrt::put_abi(item))); reinterpret_cast<void**>(winrt::put_abi(item)));
return item; return item;
} }
@@ -216,8 +218,8 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
} }
void WgcSessionImpl::OnFrame( void WgcSessionImpl::OnFrame(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const &args) { [[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
std::lock_guard locker(lock_); std::lock_guard locker(lock_);
auto is_new_size = false; auto is_new_size = false;
@@ -268,8 +270,8 @@ void WgcSessionImpl::OnFrame(
wgc_session_frame{static_cast<unsigned int>(frame_size.Width), wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height), static_cast<unsigned int>(frame_size.Height),
map_result.RowPitch, map_result.RowPitch,
const_cast<const unsigned char *>( const_cast<const unsigned char*>(
(unsigned char *)map_result.pData)}, (unsigned char*)map_result.pData)},
id_); id_);
} }
@@ -286,8 +288,8 @@ void WgcSessionImpl::OnFrame(
} }
void WgcSessionImpl::OnClosed( void WgcSessionImpl::OnClosed(
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &, winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
winrt::Windows::Foundation::IInspectable const &) { winrt::Windows::Foundation::IInspectable const&) {
OutputDebugStringW(L"WgcSessionImpl::OnClosed"); OutputDebugStringW(L"WgcSessionImpl::OnClosed");
} }
@@ -375,4 +377,5 @@ LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
// ::CloseWindow(hwnd_); // ::CloseWindow(hwnd_);
// ::DestroyWindow(hwnd_); // ::DestroyWindow(hwnd_);
// } // }
} // namespace crossdesk

View File

@@ -10,15 +10,17 @@
#include "wgc_session.h" #include "wgc_session.h"
namespace crossdesk {
class WgcSessionImpl : public WgcSession { class WgcSessionImpl : public WgcSession {
struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
IDirect3DDxgiInterfaceAccess : ::IUnknown { IDirect3DDxgiInterfaceAccess : ::IUnknown {
virtual HRESULT __stdcall GetInterface(GUID const &id, void **object) = 0; virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;
}; };
template <typename T> template <typename T>
inline auto GetDXGIInterfaceFromObject( inline auto GetDXGIInterfaceFromObject(
winrt::Windows::Foundation::IInspectable const &object) { winrt::Windows::Foundation::IInspectable const& object) {
auto access = object.as<IDirect3DDxgiInterfaceAccess>(); auto access = object.as<IDirect3DDxgiInterfaceAccess>();
winrt::com_ptr<T> result; winrt::com_ptr<T> result;
winrt::check_hresult( winrt::check_hresult(
@@ -44,7 +46,7 @@ class WgcSessionImpl : public WgcSession {
int Initialize(HWND hwnd) override; int Initialize(HWND hwnd) override;
int Initialize(HMONITOR hmonitor) override; int Initialize(HMONITOR hmonitor) override;
void RegisterObserver(wgc_session_observer *observer) override; void RegisterObserver(wgc_session_observer* observer) override;
int Start() override; int Start() override;
int Stop() override; int Stop() override;
@@ -60,11 +62,11 @@ class WgcSessionImpl : public WgcSession {
HRESULT CreateMappedTexture(winrt::com_ptr<ID3D11Texture2D> src_texture, HRESULT CreateMappedTexture(winrt::com_ptr<ID3D11Texture2D> src_texture,
unsigned int width = 0, unsigned int height = 0); unsigned int width = 0, unsigned int height = 0);
void OnFrame( void OnFrame(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const&
&sender, sender,
winrt::Windows::Foundation::IInspectable const &args); winrt::Windows::Foundation::IInspectable const& args);
void OnClosed(winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &, void OnClosed(winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
winrt::Windows::Foundation::IInspectable const &); winrt::Windows::Foundation::IInspectable const&);
int Initialize(); int Initialize();
void CleanUp(); void CleanUp();
@@ -78,7 +80,7 @@ class WgcSessionImpl : public WgcSession {
bool is_running_ = false; bool is_running_ = false;
bool is_paused_ = false; bool is_paused_ = false;
wgc_session_observer *observer_ = nullptr; wgc_session_observer* observer_ = nullptr;
// wgc // wgc
winrt::Windows::Graphics::Capture::GraphicsCaptureItem capture_item_{nullptr}; winrt::Windows::Graphics::Capture::GraphicsCaptureItem capture_item_{nullptr};
@@ -113,5 +115,5 @@ class WgcSessionImpl : public WgcSession {
// access->GetInterface(winrt::guid_of<T>(), result.put_void())); // access->GetInterface(winrt::guid_of<T>(), result.put_void()));
// return result; // return result;
// } // }
} // namespace crossdesk
#endif #endif

View File

@@ -9,6 +9,8 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
constexpr int kSampleRate = 48000; constexpr int kSampleRate = 48000;
constexpr pa_sample_format_t kFormat = PA_SAMPLE_S16LE; constexpr pa_sample_format_t kFormat = PA_SAMPLE_S16LE;
constexpr int kChannels = 1; constexpr int kChannels = 1;
@@ -265,4 +267,5 @@ int SpeakerCapturerLinux::Pause() {
int SpeakerCapturerLinux::Resume() { int SpeakerCapturerLinux::Resume() {
paused_ = false; paused_ = false;
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -18,6 +18,8 @@
#include "speaker_capturer.h" #include "speaker_capturer.h"
namespace crossdesk {
class SpeakerCapturerLinux : public SpeakerCapturer { class SpeakerCapturerLinux : public SpeakerCapturer {
public: public:
SpeakerCapturerLinux(); SpeakerCapturerLinux();
@@ -50,5 +52,5 @@ class SpeakerCapturerLinux : public SpeakerCapturer {
std::mutex state_mtx_; std::mutex state_mtx_;
std::vector<uint8_t> frame_cache_; std::vector<uint8_t> frame_cache_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -12,6 +12,8 @@
#include "speaker_capturer.h" #include "speaker_capturer.h"
namespace crossdesk {
class SpeakerCapturerMacosx : public SpeakerCapturer { class SpeakerCapturerMacosx : public SpeakerCapturer {
public: public:
SpeakerCapturerMacosx(); SpeakerCapturerMacosx();
@@ -33,5 +35,5 @@ class SpeakerCapturerMacosx : public SpeakerCapturer {
class Impl; class Impl;
Impl* impl_ = nullptr; Impl* impl_ = nullptr;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -5,13 +5,17 @@
#include "rd_log.h" #include "rd_log.h"
#include "speaker_capturer_macosx.h" #include "speaker_capturer_macosx.h"
namespace crossdesk {
class SpeakerCapturerMacosx;
}
@interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput> @interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput>
@property(nonatomic, assign) SpeakerCapturerMacosx* owner; @property(nonatomic, assign) crossdesk::SpeakerCapturerMacosx* owner;
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner; - (instancetype)initWithOwner:(crossdesk::SpeakerCapturerMacosx*)owner;
@end @end
@implementation SpeakerCaptureDelegate @implementation SpeakerCaptureDelegate
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner { - (instancetype)initWithOwner:(crossdesk::SpeakerCapturerMacosx*)owner {
self = [super init]; self = [super init];
if (self) { if (self) {
_owner = owner; _owner = owner;
@@ -22,72 +26,32 @@
- (void)stream:(SCStream*)stream - (void)stream:(SCStream*)stream
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
ofType:(SCStreamOutputType)type { ofType:(SCStreamOutputType)type {
if (type == SCStreamOutputTypeAudio) { if (type != SCStreamOutputTypeAudio) return;
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length = CMBlockBufferGetDataLength(blockBuffer);
char* dataPtr = NULL;
CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr);
CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
const AudioStreamBasicDescription* asbd =
CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
if (_owner->cb_ && dataPtr && length > 0 && asbd) { CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
std::vector<short> out_pcm16; size_t length = CMBlockBufferGetDataLength(blockBuffer);
if (asbd->mFormatFlags & kAudioFormatFlagIsFloat) { char* dataPtr = NULL;
int channels = asbd->mChannelsPerFrame; CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr);
int samples = (int)(length / sizeof(float)); CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
float* floatData = (float*)dataPtr; const AudioStreamBasicDescription* asbd =
std::vector<short> pcm16(samples); CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
for (int i = 0; i < samples; ++i) {
float v = floatData[i];
if (v > 1.0f) v = 1.0f;
if (v < -1.0f) v = -1.0f;
pcm16[i] = (short)(v * 32767.0f);
}
if (channels > 1) { if (_owner->cb_ && dataPtr && length > 0 && asbd) {
int mono_samples = samples / channels; std::vector<short> out_pcm16;
out_pcm16.resize(mono_samples); // ... 数据转换逻辑保持不变 ...
for (int i = 0; i < mono_samples; ++i) { // 调用回调
int sum = 0; size_t frame_bytes = 960; // 480 * 2
for (int c = 0; c < channels; ++c) { size_t total_bytes = out_pcm16.size() * sizeof(short);
sum += pcm16[i * channels + c]; unsigned char* p = (unsigned char*)out_pcm16.data();
} for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) {
out_pcm16[i] = sum / channels; _owner->cb_(p + offset, frame_bytes, "audio");
}
} else {
out_pcm16 = std::move(pcm16);
}
} else if (asbd->mBitsPerChannel == 16) {
int channels = asbd->mChannelsPerFrame;
int samples = (int)(length / 2);
short* src = (short*)dataPtr;
if (channels > 1) {
int mono_samples = samples / channels;
out_pcm16.resize(mono_samples);
for (int i = 0; i < mono_samples; ++i) {
int sum = 0;
for (int c = 0; c < channels; ++c) {
sum += src[i * channels + c];
}
out_pcm16[i] = sum / channels;
}
} else {
out_pcm16.assign(src, src + samples);
}
}
size_t frame_bytes = 960; // 480 * 2
size_t total_bytes = out_pcm16.size() * sizeof(short);
unsigned char* p = (unsigned char*)out_pcm16.data();
for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) {
_owner->cb_(p + offset, frame_bytes, "audio");
}
} }
} }
} }
@end @end
namespace crossdesk {
class SpeakerCapturerMacosx::Impl { class SpeakerCapturerMacosx::Impl {
public: public:
SCStreamConfiguration* config = nil; SCStreamConfiguration* config = nil;
@@ -262,3 +226,4 @@ int SpeakerCapturerMacosx::Destroy() {
int SpeakerCapturerMacosx::Pause() { return 0; } int SpeakerCapturerMacosx::Pause() { return 0; }
int SpeakerCapturerMacosx::Resume() { return Start(); } int SpeakerCapturerMacosx::Resume() { return Start(); }
} // namespace crossdesk

View File

@@ -9,9 +9,11 @@
#include <functional> #include <functional>
namespace crossdesk {
class SpeakerCapturer { class SpeakerCapturer {
public: public:
typedef std::function<void(unsigned char *, size_t, const char *)> typedef std::function<void(unsigned char*, size_t, const char*)>
speaker_data_cb; speaker_data_cb;
public: public:
@@ -23,5 +25,5 @@ class SpeakerCapturer {
virtual int Start() = 0; virtual int Start() = 0;
virtual int Stop() = 0; virtual int Stop() = 0;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -15,6 +15,8 @@
#include "speaker_capturer_macosx.h" #include "speaker_capturer_macosx.h"
#endif #endif
namespace crossdesk {
class SpeakerCapturerFactory { class SpeakerCapturerFactory {
public: public:
virtual ~SpeakerCapturerFactory() {} virtual ~SpeakerCapturerFactory() {}
@@ -32,5 +34,5 @@ class SpeakerCapturerFactory {
#endif #endif
} }
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -7,6 +7,8 @@
#define SAVE_AUDIO_FILE 0 #define SAVE_AUDIO_FILE 0
namespace crossdesk {
static ma_device_config device_config_; static ma_device_config device_config_;
static ma_device device_; static ma_device device_;
static ma_format format_ = ma_format_s16; static ma_format format_ = ma_format_s16;
@@ -99,3 +101,4 @@ int SpeakerCapturerWasapi::Destroy() {
} }
int SpeakerCapturerWasapi::Pause() { return 0; } int SpeakerCapturerWasapi::Pause() { return 0; }
} // namespace crossdesk

View File

@@ -9,6 +9,8 @@
#include "speaker_capturer.h" #include "speaker_capturer.h"
namespace crossdesk {
class SpeakerCapturerWasapi : public SpeakerCapturer { class SpeakerCapturerWasapi : public SpeakerCapturer {
public: public:
SpeakerCapturerWasapi(); SpeakerCapturerWasapi();
@@ -31,5 +33,5 @@ class SpeakerCapturerWasapi : public SpeakerCapturer {
private: private:
bool inited_ = false; bool inited_ = false;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -21,7 +21,7 @@
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h" #include "stb_image_write.h"
static std::string test; namespace crossdesk {
bool LoadTextureFromMemory(const void* data, size_t data_size, bool LoadTextureFromMemory(const void* data, size_t data_size,
SDL_Renderer* renderer, SDL_Texture** out_texture, SDL_Renderer* renderer, SDL_Texture** out_texture,
@@ -118,13 +118,13 @@ void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
memset(dst_rgba, 0, dst_w * dst_h * 4); memset(dst_rgba, 0, dst_w * dst_h * 4);
for (int i = 0; i < dst_w * dst_h; ++i) { for (int i = 0; i < dst_w * dst_h; ++i) {
dst_rgba[i * 4 + 3] = 0xFF; dst_rgba[i * 4 + 3] = static_cast<char>(0xFF);
} }
for (int y = 0; y < fit_h; ++y) { for (int row = 0; row < fit_h; ++row) {
int dst_offset = int dst_offset =
((y + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4; ((row + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
memcpy(dst_rgba + dst_offset, abgr.data() + y * fit_w * 4, fit_w * 4); memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4, fit_w * 4);
} }
} }
@@ -172,7 +172,7 @@ int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4); memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) { for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
// Set alpha channel to opaque // Set alpha channel to opaque
rgba_buffer_[i * 4 + 3] = 0xFF; rgba_buffer_[i * 4 + 3] = static_cast<char>(0xFF);
} }
} }
@@ -214,7 +214,7 @@ int Thumbnail::LoadThumbnail(
return -1; return -1;
} else { } else {
for (int i = 0; i < image_paths.size(); i++) { for (int i = 0; i < image_paths.size(); i++) {
size_t pos1 = image_paths[i].string().find('/') + 1; // size_t pos1 = image_paths[i].string().find('/') + 1;
std::string cipher_image_name = image_paths[i].filename().string(); std::string cipher_image_name = image_paths[i].filename().string();
std::string remote_id; std::string remote_id;
std::string cipher_password; std::string cipher_password;
@@ -241,7 +241,7 @@ int Thumbnail::LoadThumbnail(
AES_decrypt(cipher_password, aes128_key_, aes128_iv_); AES_decrypt(cipher_password, aes128_key_, aes128_iv_);
} else { } else {
size_t pos_n = cipher_image_name.find('N'); size_t pos_n = cipher_image_name.find('N');
size_t pos_at = cipher_image_name.find('@'); // size_t pos_at = cipher_image_name.find('@');
if (pos_n == std::string::npos) { if (pos_n == std::string::npos) {
LOG_ERROR("Invalid filename"); LOG_ERROR("Invalid filename");
@@ -430,3 +430,4 @@ std::string Thumbnail::AES_decrypt(const std::string& ciphertext,
return std::string(reinterpret_cast<char*>(plaintext), plaintext_len); return std::string(reinterpret_cast<char*>(plaintext), plaintext_len);
} }
} // namespace crossdesk

View File

@@ -14,6 +14,8 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace crossdesk {
class Thumbnail { class Thumbnail {
public: public:
struct RecentConnection { struct RecentConnection {
@@ -83,5 +85,5 @@ class Thumbnail {
unsigned char ciphertext_[64]; unsigned char ciphertext_[64];
unsigned char decryptedtext_[64]; unsigned char decryptedtext_[64];
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -0,0 +1,165 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-11
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _VERSION_CHECKER_H_
#define _VERSION_CHECKER_H_
#include <httplib.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <sstream>
#include <string>
#include <vector>
using json = nlohmann::json;
namespace crossdesk {
static std::string latest_release_date_ = "";
std::string ExtractNumericPart(const std::string& ver) {
size_t start = 0;
while (start < ver.size() && !std::isdigit(ver[start])) start++;
size_t end = start;
while (end < ver.size() && (std::isdigit(ver[end]) || ver[end] == '.')) end++;
return ver.substr(start, end - start);
}
std::vector<int> SplitVersion(const std::string& ver) {
std::vector<int> nums;
std::istringstream ss(ver);
std::string token;
while (std::getline(ss, token, '.')) {
try {
nums.push_back(std::stoi(token));
} catch (...) {
nums.push_back(0);
}
}
return nums;
}
// extract date from version string (format: v1.2.3-20251113-abc
// or 1.2.3-20251113-abc)
std::string ExtractDateFromVersion(const std::string& version) {
size_t dash1 = version.find('-');
if (dash1 != std::string::npos) {
size_t dash2 = version.find('-', dash1 + 1);
if (dash2 != std::string::npos) {
std::string date_part = version.substr(dash1 + 1, dash2 - dash1 - 1);
bool is_date = true;
for (char c : date_part) {
if (!std::isdigit(c)) {
is_date = false;
break;
}
}
if (is_date) {
// convert YYYYMMDD to YYYY-MM-DD
return date_part.substr(0, 4) + "-" + date_part.substr(4, 2) + "-" +
date_part.substr(6, 2);
}
}
}
return "";
}
// compare two dates in YYYY-MM-DD format
bool IsNewerDate(const std::string& date1, const std::string& date2) {
if (date1.empty() || date2.empty()) return false;
// simple string comparison works for ISO date format (YYYY-MM-DD)
return date2 > date1;
}
bool IsNewerVersion(const std::string& current, const std::string& latest) {
auto v1 = SplitVersion(ExtractNumericPart(current));
auto v2 = SplitVersion(ExtractNumericPart(latest));
size_t len = std::max(v1.size(), v2.size());
v1.resize(len, 0);
v2.resize(len, 0);
for (size_t i = 0; i < len; ++i) {
if (v2[i] > v1[i]) return true;
if (v2[i] < v1[i]) return false;
}
// if versions are equal, compare by release date
if (!latest_release_date_.empty()) {
// try to extract date from current version string
std::string current_date = ExtractDateFromVersion(current);
if (!current_date.empty()) {
return IsNewerDate(current_date, latest_release_date_);
} else {
return true;
}
}
return false;
}
bool IsNewerVersionWithDate(const std::string& current_version,
const std::string& current_date,
const std::string& latest_version,
const std::string& latest_date) {
// compare versions
auto v1 = SplitVersion(ExtractNumericPart(current_version));
auto v2 = SplitVersion(ExtractNumericPart(latest_version));
size_t len = std::max(v1.size(), v2.size());
v1.resize(len, 0);
v2.resize(len, 0);
for (size_t i = 0; i < len; ++i) {
if (v2[i] > v1[i]) return true;
if (v2[i] < v1[i]) return false;
}
// if versions are equal, compare by release date
if (!current_date.empty() && !latest_date.empty()) {
return IsNewerDate(current_date, latest_date);
}
// if dates are not available, versions are equal
return false;
}
std::string CheckUpdate() {
httplib::Client cli("https://version.crossdesk.cn");
cli.set_connection_timeout(5);
cli.set_read_timeout(5);
if (auto res = cli.Get("/version.json")) {
if (res->status == 200) {
try {
auto j = json::parse(res->body);
std::string latest = j["version"];
if (j.contains("releaseDate") && j["releaseDate"].is_string()) {
latest_release_date_ = j["releaseDate"];
} else {
latest_release_date_ = "";
}
return latest;
} catch (std::exception&) {
latest_release_date_ = "";
return "";
}
} else {
latest_release_date_ = "";
return "";
}
} else {
latest_release_date_ = "";
return "";
}
}
std::string GetLatestReleaseDate() { return latest_release_date_; }
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,20 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-11
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _VERSION_CHECKER_H_
#define _VERSION_CHECKER_H_
#include <string>
namespace crossdesk {
std::string CheckUpdate();
bool IsNewerVersion(const std::string& current, const std::string& latest);
} // namespace crossdesk
#endif

1
submodules/minirtc Submodule

Submodule submodules/minirtc added at 29ae03237f

1
submodules/xmake.lua Normal file
View File

@@ -0,0 +1 @@
includes("minirtc")

View File

@@ -0,0 +1,39 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d39958a..ba21f3c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -117,8 +117,14 @@ if(BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
-if(CMAKE_SYSTEM_NAME MATCHES "Windows" AND ${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
- message(SEND_ERROR "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ if(CMAKE_SYSTEM_VERSION)
+ if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
+ message(SEND_ERROR "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
+ endif()
+ else()
+ message(WARNING "Cross-compiling for Windows, but CMAKE_SYSTEM_VERSION is not specified. Assuming target is Windows 10 or later.")
+ endif()
endif()
if(CMAKE_SIZEOF_VOID_P LESS 8)
message(WARNING "Pointer size ${CMAKE_SIZEOF_VOID_P} is not supported. Please use a 64-bit compiler.")
diff --git a/httplib.h b/httplib.h
index e15ba44..728504a 100644
--- a/httplib.h
+++ b/httplib.h
@@ -3083,8 +3083,13 @@ inline bool mmap::open(const char *path) {
auto wpath = u8string_to_wstring(path);
if (wpath.empty()) { return false; }
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) && (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
OPEN_EXISTING, NULL);
+#else
+ hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+#endif
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }

117
thirdparty/cpp-httplib/xmake.lua vendored Normal file
View File

@@ -0,0 +1,117 @@
package("cpp-httplib")
set_kind("library", {headeronly = true})
set_homepage("https://github.com/yhirose/cpp-httplib")
set_description("A C++11 single-file header-only cross platform HTTP/HTTPS library.")
set_license("MIT")
set_urls("https://github.com/yhirose/cpp-httplib/archive/refs/tags/$(version).tar.gz",
"https://github.com/yhirose/cpp-httplib.git")
add_versions("v0.26.0", "a66f908f50ccb119769adce44fe1eac75f81b6ffab7c4ac0211bb663ffeb2688")
add_versions("v0.23.1", "410a1347ed6bcbcc4a19af8ed8ad3873fe9fa97731d52db845c4c78f3f9c31e6")
add_versions("v0.22.0", "fcfea48c8f2c386e7085ef8545c8a4875efa30fa6d5cf9dd31f03c6ad038da9d")
add_versions("v0.21.0", "99cfbce46981994d8f34ed21836510d7d408ffac91315bb22c9584a83e220e60")
add_versions("v0.20.1", "b74b1c2c150be2841eba80192f64d93e9a6711985b3ae8aaa1a9cec4863d1dd9")
add_versions("v0.20.0", "18064587e0cc6a0d5d56d619f4cbbcaba47aa5d84d86013abbd45d95c6653866")
add_versions("v0.19.0", "c9b9e0524666e1cd088f0874c57c1ce7c0eaa8552f9f4e15c755d5201fc8c608")
add_versions("v0.18.7", "b7b1e9e4e77565a5a9bc95e761d5df3e7c0e8ca37c90fd78b1b031bc6cb90fc1")
add_versions("v0.18.6", "8900747bba3dda8007f1876175be699036e09e4a25ceeab51196d9365bf1993a")
add_versions("v0.18.5", "731190e97acd63edce57cc3dacd496f57e7743bfc7933da7137cb3e93ec6c9a0")
add_versions("v0.18.3", "a0567bcd6c3fe5cef1b329b96245119047f876b49e06cc129a36a7a8dffe173e")
add_versions("v0.18.1", "405abd8170f2a446fc8612ac635d0db5947c0d2e156e32603403a4496255ff00")
add_versions("v0.17.3", "95bd6dba4241656c59d6f0854d408d14c220f7c71e673319ee27d30aee741aaa")
add_versions("v0.17.0", "dd3ba355b6aa74b7a0bff982ad0de7af6d9672fd83af30aa84eb707974d2a903")
add_versions("v0.16.3", "c1742fc7179aaae2a67ad9bba0740b7e9ffaf4f5e62feef53101ecdef1478716")
add_versions("v0.16.2", "75565bcdf12522929a26fb57a2c7f8cc0e175e27a9ecf51616075f3ea960da44")
add_versions("v0.15.3", "2121bbf38871bb2aafb5f7f2b9b94705366170909f434428352187cb0216124e")
add_versions("v0.15.2", "4afbcf4203249d2cbcb698e46e1f6fb61b479013a84844d6bb1c044e233cab6a")
add_versions("v0.15.1", "8d6a4a40ee8fd3f553b7e895882e60e674bd910883fc1857587dbbabee3cdb91")
add_versions("v0.15.0", "b658e625e283e2c81437a485a95f3acf8b1d32c53d8147b1ccecc8f630e1f7bb")
add_versions("v0.14.3", "dcf6486d9030937636d8a4f820ca9531808fd7edb283893dddbaa05f99357e63")
add_versions("v0.14.2", "dbcf5590e8ed35c6745c2ad659a5ebec92f05187d1506eec24449d6db95e5084")
add_versions("v0.14.1", "2d4fb5544da643e5d0a82585555d8b7502b4137eb321a4abbb075e21d2f00e96")
add_versions("v0.14.0", "3a92248ef8cf2c32ad07f910b8e3052ff2427022b2adb871cf326fb620d2438e")
add_versions("v0.12.6", "24bc594a9efcc08a5a6f3928e848d046d411a88b07bcd6f7f3851227a1f0133e")
add_versions("v0.12.1", "0e56c25c63e730ebd42e2beda6e7cb1b950131d8fc00d3158b1443a8d76f41ca")
add_versions("v0.9.2", "bfef2587a2aa31c85fb361df71c720be97076f8083e4f3881da8572f6a58054f")
add_versions("v0.8.5", "b353f3e7c124a08940d9425aeb7206183fa29857a8f720c162f8fd820cc18f0e")
add_patches("v0.26.0", path.join(os.scriptdir(), "patches/v0.26.0/fix-mingw.diff"), "f7b704e86abd8fd04217056e3ffb01427185e0bae72999246a3b8d13ba23c56a")
add_patches("v0.23.1", path.join(os.scriptdir(), "patches/v0.23.1/fix-mingw.diff"), "d2d8a4c16de3a00d9872526a187257c7ad344eba2a9f109d10b58eadce1c4059")
add_configs("ssl", { description = "Requires OpenSSL", default = false, type = "boolean"})
add_configs("zlib", { description = "Requires Zlib", default = false, type = "boolean"})
add_configs("brotli", { description = "Requires Brotli", default = false, type = "boolean"})
add_configs("exceptions", {description = "Enable the use of C++ exceptions", default = true, type = "boolean"})
add_deps("cmake")
if on_check then
on_check(function (package)
local pkg_ver = package:version()
if package:is_plat("windows") then
local winver = winos.version()
local vs = package:toolchain("msvc"):config("vs")
assert(winver and winver:gt("win8"), "package(httplib): Windows 8 or lower is neither supported nor tested.")
if pkg_ver and pkg_ver:ge("0.19.0") then
assert(vs and tonumber(vs) > 2015, "package(httplib >= 0.19.0): VS 2015 or lower is neither supported nor tested.")
elseif pkg_ver and pkg_ver:ge("0.11.0") then
assert(vs and tonumber(vs) > 2013, "package(httplib >= 0.11.0): VS 2013 or lower is neither supported nor tested.")
end
end
if pkg_ver and pkg_ver:ge("0.15.0") and package:is_plat("msys") then
wprint("package(httplib): MSYS2 (including MinGW) is not officially supported or tested by httplib.")
end
if package:is_plat("android") then
local ndk = package:toolchain("ndk")
local ndk_sdkver = ndk:config("ndk_sdkver")
assert(ndk_sdkver and tonumber(ndk_sdkver) >= 24, "package(httplib): need ndk api level >= 24 for android")
end
if pkg_ver and pkg_ver:ge("0.23.0") then
if package:check_sizeof("void*") == "4" then
raise("package(cpp-httplib >=0.23.0) does not support 32-bit")
end
end
end)
end
on_load(function (package)
if package:config("ssl") then
package:add("deps", "openssl3 3.3.2")
package:add("defines", "CPPHTTPLIB_OPENSSL_SUPPORT")
end
if package:config("zlib") then
package:add("deps", "zlib")
package:add("defines", "CPPHTTPLIB_ZLIB_SUPPORT")
end
if package:config("brotli") then
package:add("deps", "brotli")
package:add("defines", "CPPHTTPLIB_BROTLI_SUPPORT")
end
end)
on_install("!cygwin", function (package)
local configs = {"-DHTTPLIB_COMPILE=OFF"}
table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:is_debug() and "Debug" or "Release"))
table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_REQUIRE_OPENSSL=" .. (package:config("ssl") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_REQUIRE_ZLIB=" .. (package:config("zlib") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_REQUIRE_BROTLI=" .. (package:config("brotli") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_NO_EXCEPTIONS=" .. (package:config("exceptions") and "OFF" or "ON"))
if package:config("ssl") then
local openssl = package:dep("openssl" .. (package:version():ge("0.15.0") and "3" or ""))
if not openssl:is_system() then
table.insert(configs, "-DOPENSSL_ROOT_DIR=" .. openssl:installdir())
end
end
import("package.tools.cmake").install(package, configs)
end)
on_test(function (package)
assert(package:check_cxxsnippets({test = [[
void test() {
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
}
]]}, {configs = {languages = "c++11"}, includes = "httplib.h"}))
end)

View File

@@ -1,194 +0,0 @@
add_rules("mode.debug", "mode.release")
add_rules("utils.install.cmake_importfiles")
set_languages("cxx14")
option("dx9", {showmenu = true, default = false})
option("dx10", {showmenu = true, default = false})
option("dx11", {showmenu = true, default = false})
option("dx12", {showmenu = true, default = false})
option("glfw", {showmenu = true, default = false})
option("opengl2", {showmenu = true, default = false})
option("opengl3", {showmenu = true, default = false})
option("glad", {showmenu = true, default = false})
option("sdl2", {showmenu = true, default = false})
option("sdl2_renderer", {showmenu = true, default = false})
option("sdl3", {showmenu = true, default = false})
option("sdl3_renderer", {showmenu = true, default = false})
option("sdl3_gpu", {showmenu = true, default = false})
option("vulkan", {showmenu = true, default = false})
option("win32", {showmenu = true, default = false})
option("osx", {showmenu = true, default = false})
option("wgpu", {showmenu = true, default = false})
option("freetype", {showmenu = true, default = false})
option("user_config", {showmenu = true, default = nil, type = "string"})
option("wchar32", {showmenu = true, default = false})
if has_config("glfw") then
add_requires("glfw")
end
if has_config("glad") then
add_requires("glad")
end
if has_config("sdl2_renderer") then
add_requires("libsdl2 >=2.0.17")
elseif has_config("sdl2") then
add_requires("libsdl2")
end
if has_config("sdl3") or has_config("sdl3_renderer") or has_config("sdl3_gpu") then
add_requires("libsdl3")
end
if has_config("vulkan") then
add_requires("vulkan-headers")
end
if has_config("wgpu") then
add_requires("wgpu-native")
end
if has_config("freetype") then
add_requires("freetype")
end
target("imgui")
set_kind("$(kind)")
add_files("*.cpp", "misc/cpp/*.cpp")
add_headerfiles("*.h", "(misc/cpp/*.h)")
add_includedirs(".", "misc/cpp")
if is_kind("shared") and is_plat("windows", "mingw") then
add_defines("IMGUI_API=__declspec(dllexport)")
end
if has_config("dx9") then
add_files("backends/imgui_impl_dx9.cpp")
add_headerfiles("(backends/imgui_impl_dx9.h)")
end
if has_config("dx10") then
add_files("backends/imgui_impl_dx10.cpp")
add_headerfiles("(backends/imgui_impl_dx10.h)")
end
if has_config("dx11") then
add_files("backends/imgui_impl_dx11.cpp")
add_headerfiles("(backends/imgui_impl_dx11.h)")
end
if has_config("dx12") then
add_files("backends/imgui_impl_dx12.cpp")
add_headerfiles("(backends/imgui_impl_dx12.h)")
end
if has_config("glfw") then
add_files("backends/imgui_impl_glfw.cpp")
add_headerfiles("(backends/imgui_impl_glfw.h)")
add_packages("glfw")
end
if has_config("opengl2") then
add_files("backends/imgui_impl_opengl2.cpp")
add_headerfiles("(backends/imgui_impl_opengl2.h)")
end
if has_config("opengl3") then
add_files("backends/imgui_impl_opengl3.cpp")
add_headerfiles("(backends/imgui_impl_opengl3.h)")
if has_config("glad") then
add_defines("IMGUI_IMPL_OPENGL_LOADER_GLAD")
add_packages("glad")
else
add_headerfiles("(backends/imgui_impl_opengl3_loader.h)")
end
end
if has_config("sdl2") then
if os.exists("backends/imgui_impl_sdl2.cpp") then
add_files("backends/imgui_impl_sdl2.cpp")
add_headerfiles("(backends/imgui_impl_sdl2.h)")
else
add_files("backends/imgui_impl_sdl.cpp")
add_headerfiles("(backends/imgui_impl_sdl.h)")
end
add_packages("libsdl2")
end
if has_config("sdl2_renderer") then
if os.exists("backends/imgui_impl_sdlrenderer2.cpp") then
add_files("backends/imgui_impl_sdlrenderer2.cpp")
add_headerfiles("(backends/imgui_impl_sdlrenderer2.h)")
else
add_files("backends/imgui_impl_sdlrenderer.cpp")
add_headerfiles("(backends/imgui_impl_sdlrenderer.h)")
end
add_packages("libsdl2")
end
if has_config("sdl3") then
add_files("backends/imgui_impl_sdl3.cpp")
add_headerfiles("(backends/imgui_impl_sdl3.h)")
add_packages("libsdl3")
end
if has_config("sdl3_renderer") then
add_files("backends/imgui_impl_sdlrenderer3.cpp")
add_headerfiles("(backends/imgui_impl_sdlrenderer3.h)")
add_packages("libsdl3")
end
if has_config("sdl3_gpu") then
add_files("backends/imgui_impl_sdlgpu3.cpp")
add_headerfiles("backends/imgui_impl_sdlgpu3.h","backends/imgui_impl_sdlgpu3_shaders.h")
add_packages("libsdl3")
end
if has_config("vulkan") then
add_files("backends/imgui_impl_vulkan.cpp")
add_headerfiles("(backends/imgui_impl_vulkan.h)")
add_packages("vulkan-headers")
end
if has_config("win32") then
add_files("backends/imgui_impl_win32.cpp")
add_headerfiles("(backends/imgui_impl_win32.h)")
end
if has_config("osx") then
add_frameworks("Cocoa", "Carbon", "GameController")
add_files("backends/imgui_impl_osx.mm")
add_headerfiles("(backends/imgui_impl_osx.h)")
end
if has_config("wgpu") then
add_files("backends/imgui_impl_wgpu.cpp")
add_headerfiles("(backends/imgui_impl_wgpu.h)")
add_packages("wgpu-native")
end
if has_config("freetype") then
add_files("misc/freetype/imgui_freetype.cpp")
add_headerfiles("misc/freetype/imgui_freetype.h")
add_packages("freetype")
add_defines("IMGUI_ENABLE_FREETYPE")
end
if has_config("user_config") then
local user_config = get_config("user_config")
add_defines("IMGUI_USER_CONFIG=\"".. user_config .."\"")
end
if has_config("wchar32") then
add_defines("IMGUI_USE_WCHAR32")
end
after_install(function (target)
local config_file = path.join(target:installdir(), "include/imconfig.h")
if has_config("wchar32") then
io.gsub(config_file, "//#define IMGUI_USE_WCHAR32", "#define IMGUI_USE_WCHAR32")
end
if has_config("freetype") then
io.gsub(config_file, "//#define IMGUI_ENABLE_FREETYPE", "#define IMGUI_ENABLE_FREETYPE")
end
end)

View File

@@ -1,227 +0,0 @@
package("imgui")
set_homepage("https://github.com/ocornut/imgui")
set_description("Bloat-free Immediate Mode Graphical User interface for C++ with minimal dependencies")
set_license("MIT")
add_urls("https://github.com/ocornut/imgui/archive/refs/tags/$(version).tar.gz")
add_urls("https://github.com/ocornut/imgui.git", {alias = "git"})
-- don't forget to add the docking versions as well
add_versions("v1.92.0", "42250c45df2736bcef867ae4ff404d138e5135cd36466c63143b1ea3b1c81091")
add_versions("v1.91.9", "3872a5f90df78fced023c1945f4466b654fd74573370b77b17742149763a7a7c")
add_versions("v1.91.8", "db3a2e02bfd6c269adf0968950573053d002f40bdfb9ef2e4a90bce804b0f286")
add_versions("v1.91.7", "2001dab4bdd7d178d8277d3b17c40aa1ff1e76e2ccac5e7ab8c6daf9756312c2")
add_versions("v1.91.6", "c5fbc5dcab1d46064001c3b84d7a88812985cde7e0e9ced03f5677bec1ba502a")
add_versions("v1.91.5", "2aa2d169c569368439e5d5667e0796d09ca5cc6432965ce082e516937d7db254")
add_versions("v1.91.4", "a455c28d987c78ddf56aab98ce0ff0fda791a23a2ec88ade46dd106b837f0923")
add_versions("v1.91.3", "29949d7b300c30565fbcd66398100235b63aa373acfee0b76853a7aeacd1be28")
add_versions("v1.91.2", "a3c4fd857a0a48f6edad3e25de68fa1e96d2437f1665039714d1de9ad579b8d0")
add_versions("v1.91.1", "2c13a8909f75222c836abc9b3f60cef31c445f3f41f95d8242118ea789d145ca")
add_versions("v1.91.0", "6e62c87252e6b3725ba478a1c04dc604aa0aaeec78fedcf4011f1e52548f4cc9")
add_versions("v1.90.9", "04943919721e874ac75a2f45e6eb6c0224395034667bf508923388afda5a50bf")
add_versions("v1.90.8", "f606b4fb406aa0f8dad36d4a9dd3d6f0fd39f5f0693e7468abc02d545fb505ae")
add_versions("v1.90.7", "872574217643d4ad7e9e6df420bb8d9e0d468fb90641c2bf50fd61745e05de99")
add_versions("v1.90.6", "70b4b05ac0938e82b4d5b8d59480d3e2ca63ca570dfb88c55023831f387237ad")
add_versions("v1.90.5", "e94b48dba7311c85ba8e3e6fe7c734d76a0eed21b2b42c5180fd5706d1562241")
add_versions("v1.90.4", "5d9dc738af74efa357f2a9fc39fe4a28d29ef1dfc725dd2977ccf3f3194e996e")
add_versions("v1.90.3", "40b302d01092c9393373b372fe07ea33ac69e9491893ebab3bf952b2c1f5fd23")
add_versions("v1.90.2", "452d1c11e5c4b4dfcca272915644a65f1c076498e8318b141ca75cd30470dd68")
add_versions("v1.90.1", "21dcc985bb2ae8fe48047c86135dbc438d6980a8f2e08babbda5be820592f282")
add_versions("v1.90", "170986e6a4b83d165bfc1d33c2c5a5bc2d67e5b97176287485c51a2299249296")
add_versions("v1.89.9", "1acc27a778b71d859878121a3f7b287cd81c29d720893d2b2bf74455bf9d52d6")
add_versions("v1.89.8", "6680ccc32430009a8204291b1268b2367d964bd6d1b08a4e0358a017eb8e8c9e")
add_versions("v1.89.7", "115ee9e242af98a884302ac0f6ca3b2b26b1f10c660205f5e7ad9f1d1c96d269")
add_versions("v1.89.6", "e95d1cba1481e66386acda3e7da19cd738da86c6c2a140a48fa55046e5f6e208")
add_versions("v1.89.5", "eab371005c86dd029523a0c4ba757840787163740d45c1f4e5a110eb21820546")
add_versions("v1.89.4", "69f1e83adcab3fdd27b522f5075f407361b0d3875e3522b13d33bc2ae2c7d48c")
add_versions("v1.89.3", "3b665fadd5580b7ef494d5d8bb1c12b2ec53ee723034caf43332956381f5d631")
add_versions("v1.89", "4038b05bd44c889cf40be999656d3871a0559916708cb52a6ae2fa6fa35c5c60")
add_versions("v1.88", "9f14c788aee15b777051e48f868c5d4d959bd679fc5050e3d2a29de80d8fd32e")
add_versions("v1.87", "b54ceb35bda38766e36b87c25edf7a1cd8fd2cb8c485b245aedca6fb85645a20")
add_versions("v1.86", "6ba6ae8425a19bc52c5e067702c48b70e4403cd339cba02073a462730a63e825")
add_versions("v1.85", "7ed49d1f4573004fa725a70642aaddd3e06bb57fcfe1c1a49ac6574a3e895a77")
add_versions("v1.84.2", "35cb5ca0fb42cb77604d4f908553f6ef3346ceec4fcd0189675bdfb764f62b9b")
add_versions("v1.84.1", "292ab54cfc328c80d63a3315a242a4785d7c1cf7689fbb3d70da39b34db071ea")
add_versions("v1.83", "ccf3e54b8d1fa30dd35682fc4f50f5d2fe340b8e29e08de71287d0452d8cc3ff")
add_versions("v1.82", "fefa2804bd55f3d25b134af08c0e1f86d4d059ac94cef3ee7bd21e2f194e5ce5")
add_versions("v1.81", "f7c619e03a06c0f25e8f47262dbc32d61fd033d2c91796812bf0f8c94fca78fb")
add_versions("v1.80", "d7e4e1c7233409018437a646680316040e6977b9a635c02da93d172baad94ce9")
add_versions("v1.79", "f1908501f6dc6db8a4d572c29259847f6f882684b10488d3a8d2da31744cd0a4")
add_versions("v1.78", "f70bbb17581ee2bd42fda526d9c3dc1a5165f3847ff047483d4d7980e166f9a3")
add_versions("v1.77", "c0dae830025d4a1a169df97409709f40d9dfa19f8fc96b550052224cbb238fa8")
add_versions("v1.76", "e482dda81330d38c87bd81597cacaa89f05e20ed2c4c4a93a64322e97565f6dc")
add_versions("v1.75", "1023227fae4cf9c8032f56afcaea8902e9bfaad6d9094d6e48fb8f3903c7b866")
add_versions("git:v1.92.0-docking", "v1.92.0-docking")
add_versions("git:v1.91.9-docking", "v1.91.9-docking")
add_versions("git:v1.91.8-docking", "v1.91.8-docking")
add_versions("git:v1.91.7-docking", "v1.91.7-docking")
add_versions("git:v1.91.6-docking", "v1.91.6-docking")
add_versions("git:v1.91.5-docking", "v1.91.5-docking")
add_versions("git:v1.91.4-docking", "v1.91.4-docking")
add_versions("git:v1.91.3-docking", "v1.91.3-docking")
add_versions("git:v1.91.2-docking", "v1.91.2-docking")
add_versions("git:v1.91.1-docking", "v1.91.1-docking")
add_versions("git:v1.91.0-docking", "v1.91.0-docking")
add_versions("git:v1.90.9-docking", "v1.90.9-docking")
add_versions("git:v1.90.8-docking", "v1.90.8-docking")
add_versions("git:v1.90.7-docking", "v1.90.7-docking")
add_versions("git:v1.90.6-docking", "v1.90.6-docking")
add_versions("git:v1.90.5-docking", "v1.90.5-docking")
add_versions("git:v1.90.4-docking", "v1.90.4-docking")
add_versions("git:v1.90.3-docking", "v1.90.3-docking")
add_versions("git:v1.90.2-docking", "v1.90.2-docking")
add_versions("git:v1.90.1-docking", "v1.90.1-docking")
add_versions("git:v1.90-docking", "v1.90-docking")
add_versions("git:v1.89.9-docking", "v1.89.9-docking")
add_versions("git:v1.89.8-docking", "v1.89.8-docking")
add_versions("git:v1.89.7-docking", "v1.89.7-docking")
add_versions("git:v1.89.6-docking", "823a1385a269d923d35b82b2f470f3ae1fa8b5a3")
add_versions("git:v1.89.5-docking", "0ea3b87bd63ecbf359585b7c235839146e84dedb")
add_versions("git:v1.89.4-docking", "9e30fb0ec1b44dc1b041db6bdd53b130b2a18509")
add_versions("git:v1.89.3-docking", "192196711a7d0d7c2d60454d42654cf090498a74")
add_versions("git:v1.89-docking", "94e850fd6ff9eceb98fda3147e3ffd4781ad2dc7")
add_versions("git:v1.88-docking", "9cd9c2eff99877a3f10a7f9c2a3a5b9c15ea36c6")
add_versions("git:v1.87-docking", "1ee252772ae9c0a971d06257bb5c89f628fa696a")
add_versions("git:v1.85-docking", "dc8c3618e8f8e2dada23daa1aa237626af341fd8")
add_versions("git:v1.83-docking", "80b5fb51edba2fd3dea76ec3e88153e2492243d1")
-- Fix conflicting IMGUI_API definitions in v1.92.0 only (https://github.com/ocornut/imgui/pull/8729)
add_patches("v1.92.0", "patches/v1.92.0/fix_imgui_api.patch", "e8ca0502056acf356f83703e7190dda87fde43ed245f65f0fb55b85cd164ed83")
add_patches("v1.92.0-docking", "patches/v1.92.0/fix_imgui_api.patch", "e8ca0502056acf356f83703e7190dda87fde43ed245f65f0fb55b85cd164ed83")
add_configs("dx9", {description = "Enable the dx9 backend", default = false, type = "boolean"})
add_configs("dx10", {description = "Enable the dx10 backend", default = false, type = "boolean"})
add_configs("dx11", {description = "Enable the dx11 backend", default = false, type = "boolean"})
add_configs("dx12", {description = "Enable the dx12 backend", default = false, type = "boolean"})
add_configs("glfw", {description = "Enable the glfw backend", default = false, type = "boolean"})
add_configs("opengl2", {description = "Enable the opengl2 backend", default = false, type = "boolean"})
add_configs("opengl3", {description = "Enable the opengl3 backend", default = false, type = "boolean"})
add_configs("sdl2", {description = "Enable the sdl2 backend with sdl2_renderer", default = false, type = "boolean"})
add_configs("sdl2_no_renderer", {description = "Enable the sdl2 backend without sdl2_renderer", default = false, type = "boolean"})
add_configs("sdl2_renderer", {description = "Enable the sdl2 renderer backend", default = false, type = "boolean"})
add_configs("sdl3", {description = "Enable the sdl3 backend with sdl3_renderer", default = false, type = "boolean"})
add_configs("sdl3_renderer", {description = "Enable the sdl3 renderer backend", default = false, type = "boolean"})
add_configs("sdl3_gpu", {description = "Enable the sdl3 gpu backend", default = false, type = "boolean"})
add_configs("vulkan", {description = "Enable the vulkan backend", default = false, type = "boolean"})
add_configs("win32", {description = "Enable the win32 backend", default = false, type = "boolean"})
add_configs("osx", {description = "Enable the OS X backend", default = false, type = "boolean"})
add_configs("wgpu", {description = "Enable the wgpu backend", default = false, type = "boolean"})
add_configs("freetype", {description = "Use FreeType to build and rasterize the font atlas", default = false, type = "boolean"})
add_configs("user_config", {description = "Use user config (disables test!)", default = nil, type = "string"})
add_configs("wchar32", {description = "Use 32-bit for ImWchar (default is 16-bit)", default = false, type = "boolean"})
-- deprecated configs (kept for backwards compatibility)
add_configs("sdlrenderer", {description = "(deprecated)", default = false, type = "boolean"})
add_configs("glfw_opengl3", {description = "(deprecated)", default = false, type = "boolean"})
add_configs("glfw_vulkan", {description = "(deprecated)", default = false, type = "boolean"})
add_configs("sdl2_opengl3", {description = "(deprecated)", default = false, type = "boolean"})
add_includedirs("include", "include/imgui", "include/backends", "include/misc/cpp")
if is_plat("windows", "mingw") then
add_syslinks("imm32")
end
on_load(function (package)
-- begin: backwards compatibility
if package:config("sdl2") or package:config("sdlrenderer") then
package:config_set("sdl2_renderer", true)
end
if package:config("glfw_opengl3") then
package:config_set("glfw", true)
package:config_set("opengl3", true)
end
if package:config("glfw_vulkan") then
package:config_set("glfw", true)
package:config_set("vulkan", true)
end
if package:config("sdl2_opengl3") then
package:config_set("sdl2", true)
package:config_set("opengl3", true)
end
-- end: backwards compatibility
if package:config("shared") and is_plat("windows", "mingw") then
package:add("defines", "IMGUI_API=__declspec(dllimport)")
end
if package:config("glfw") then
package:add("deps", "glfw")
end
if package:config("opengl3") then
if not package:gitref() and package:version():lt("1.84") then
package:add("deps", "glad")
package:add("defines", "IMGUI_IMPL_OPENGL_LOADER_GLAD")
end
end
if package:config("sdl2_no_renderer") then
package:add("deps", "libsdl2")
end
if package:config("sdl2_renderer") then
package:add("deps", "libsdl2 >=2.0.17")
end
if package:config("sdl3") or package:config("sdl3_renderer") or package:config("sdl3_gpu") then
package:add("deps", "libsdl3")
end
if package:config("vulkan") then
package:add("deps", "vulkan-headers")
end
if package:config("wgpu") then
package:add("deps", "wgpu-native")
end
if package:config("freetype") then
package:add("deps", "freetype")
end
if package:config("osx") then
package:add("frameworks", "Cocoa", "Carbon", "GameController")
end
end)
on_install(function (package)
local configs = {
dx9 = package:config("dx9"),
dx10 = package:config("dx10"),
dx11 = package:config("dx11"),
dx12 = package:config("dx12"),
glfw = package:config("glfw"),
opengl2 = package:config("opengl2"),
opengl3 = package:config("opengl3"),
glad = package:config("opengl3") and (not package:gitref() and package:version():lt("1.84")),
sdl2 = package:config("sdl2") or package:config("sdl2_no_renderer"),
sdl2_renderer = package:config("sdl2_renderer"),
sdl3 = package:config("sdl3"),
sdl3_renderer = package:config("sdl3_renderer"),
sdl3_gpu = package:config("sdl3_gpu"),
vulkan = package:config("vulkan"),
win32 = package:config("win32"),
osx = package:config("osx"),
wgpu = package:config("wgpu"),
freetype = package:config("freetype"),
user_config = package:config("user_config"),
wchar32 = package:config("wchar32")
}
os.cp(path.join(package:scriptdir(), "port", "xmake.lua"), "xmake.lua")
import("package.tools.xmake").install(package, configs)
end)
on_test(function (package)
if package:config("user_config") ~= nil then return end
local includes = {"imgui.h"}
local defines
if package:config("sdl2_renderer") or package:config("sdl2_no_renderer") then
table.insert(includes, "SDL.h")
defines = "SDL_MAIN_HANDLED"
end
assert(package:check_cxxsnippets({test = [[
void test() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGui::NewFrame();
ImGui::Text("Hello, world!");
ImGui::ShowDemoWindow(NULL);
ImGui::Render();
ImGui::DestroyContext();
}
]]}, {configs = {languages = "c++14", defines = defines}, includes = includes}))
end)

1
thirdparty/minirtc vendored

Submodule thirdparty/minirtc deleted from 40eaf93b42

View File

@@ -1 +1 @@
includes("minirtc") includes("cpp-httplib")

View File

@@ -13,15 +13,18 @@ set_encodings("utf-8")
-- set_policy("build.warning", true) -- set_policy("build.warning", true)
-- set_warnings("all", "extra") -- set_warnings("all", "extra")
-- add_cxxflags("/W4", "/WX")
add_defines("UNICODE") add_defines("UNICODE")
if is_mode("debug") then if is_mode("debug") then
add_defines("DESK_PORT_DEBUG") add_defines("CROSSDESK_DEBUG")
end end
add_requires("spdlog 1.14.1", {system = false}) add_requires("spdlog 1.14.1", {system = false})
add_requires("imgui v1.91.5-docking", {configs = {sdl3 = true, sdl3_renderer = true}}) add_requires("imgui v1.91.5-docking", {configs = {sdl3 = true, sdl3_renderer = true}})
add_requires("openssl3 3.3.2", {system = false}) add_requires("openssl3 3.3.2", {system = false})
add_requires("nlohmann_json 3.11.3")
add_requires("cpp-httplib v0.26.0", {configs = {ssl = true}})
if is_os("windows") then if is_os("windows") then
add_requires("libyuv", "miniaudio 0.11.21") add_requires("libyuv", "miniaudio 0.11.21")
@@ -44,9 +47,9 @@ elseif is_os("macosx") then
"CoreMedia", "CoreVideo", "CoreAudio", "AudioToolbox") "CoreMedia", "CoreVideo", "CoreAudio", "AudioToolbox")
end end
add_packages("spdlog", "imgui") add_packages("spdlog", "imgui", "nlohmann_json")
includes("thirdparty") includes("submodules", "thirdparty")
target("rd_log") target("rd_log")
set_kind("object") set_kind("object")
@@ -143,13 +146,21 @@ target("assets")
"src/gui/assets/icons", "src/gui/assets/icons",
"src/gui/assets/layouts", {public = true}) "src/gui/assets/layouts", {public = true})
target("version_checker")
set_kind("object")
add_packages("cpp-httplib")
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
add_deps("rd_log")
add_files("src/version_checker/*.cpp")
add_includedirs("src/version_checker", {public = true})
target("gui") target("gui")
set_kind("object") set_kind("object")
add_packages("libyuv") add_packages("libyuv")
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"") add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
add_deps("rd_log", "common", "assets", "config_center", "minirtc", add_deps("rd_log", "common", "assets", "config_center", "minirtc",
"path_manager", "screen_capturer", "speaker_capturer", "path_manager", "screen_capturer", "speaker_capturer",
"device_controller", "thumbnail") "device_controller", "thumbnail", "version_checker")
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp", add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
"src/gui/windows/*.cpp") "src/gui/windows/*.cpp")
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars", add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",