Compare commits

..

1 Commits

Author SHA1 Message Date
dijunkun
495d179fae [feat] enable custom configuration of Coturn server port 2025-10-27 15:32:20 +08:00
97 changed files with 3090 additions and 5875 deletions

View File

@@ -35,14 +35,12 @@ 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=$(TZ=Asia/Shanghai date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}" LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
else else
LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}" LEGAL_VERSION="${VERSION_NUM}-${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
@@ -55,7 +53,7 @@ jobs:
XMAKE_GLOBALDIR: /data XMAKE_GLOBALDIR: /data
run: | run: |
ls -la $XMAKE_GLOBALDIR ls -la $XMAKE_GLOBALDIR
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
xmake b -vy --root crossdesk xmake b -vy --root crossdesk
- name: Decode and save certificate - name: Decode and save certificate
@@ -101,14 +99,12 @@ 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=$(TZ=Asia/Shanghai date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}" LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
else else
LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}" LEGAL_VERSION="${VERSION_NUM}-${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
@@ -120,7 +116,7 @@ jobs:
CUDA_PATH: /usr/local/cuda CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data XMAKE_GLOBALDIR: /data
run: | run: |
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
xmake b -vy --root crossdesk xmake b -vy --root crossdesk
- name: Decode and save certificate - name: Decode and save certificate
@@ -164,17 +160,15 @@ 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)
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d) VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
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
with: with:
path: ~/.xmake/packages path: ~/.xmake/packages
key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ github.sha }} key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }}
restore-keys: | restore-keys: |
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}- ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
@@ -189,7 +183,7 @@ jobs:
- name: Build CrossDesk - name: Build CrossDesk
run: | run: |
xmake f --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y xmake f --CROSSDESK_VERSION=${VERSION_NUM} -y
xmake b -vy crossdesk xmake b -vy crossdesk
- name: Decode and save certificate - name: Decode and save certificate
@@ -229,15 +223,13 @@ 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)
$BUILD_DATE = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "China Standard Time")).ToString("yyyyMMdd") echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV
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
with: with:
path: D:\xmake_global\.xmake\packages path: D:\xmake_global\.xmake\packages
key: ${{ runner.os }}-xmake-deps-intel-${{ github.sha }} key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }}
restore-keys: | restore-keys: |
${{ runner.os }}-xmake-deps-intel- ${{ runner.os }}-xmake-deps-intel-
@@ -298,9 +290,9 @@ jobs:
- name: Build CrossDesk - name: Build CrossDesk
run: | run: |
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true -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: |
@@ -340,25 +332,17 @@ 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)
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d) VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
BUILD_DATE_ISO=$(TZ=Asia/Shanghai 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_WITH_V }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x64-${{ 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-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}.pkg
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-amd64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-amd64-${{ 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-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}.deb
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}.exe
- name: List release files - name: List release files
run: ls -lh release/ run: ls -lh release/
@@ -366,8 +350,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: ${{ steps.version.outputs.VERSION_WITH_V }} tag_name: v${{ steps.version.outputs.VERSION_NUM }}
name: Release ${{ steps.version.outputs.VERSION_WITH_V }} name: Release v${{ steps.version.outputs.VERSION_NUM }}
draft: false draft: false
prerelease: false prerelease: false
files: release/* files: release/*
@@ -401,48 +385,3 @@ 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 }}",
"releaseName": "",
"releaseNotes": "",
"tagName": "${{ steps.version.outputs.VERSION_WITH_V }}",
"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 }}

49
.github/workflows/update-pages.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
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 }}

View File

@@ -1,140 +0,0 @@
name: Update version.json from Release
on:
release:
types: [published, edited]
permissions:
contents: write
jobs:
update-version-json:
name: Update version.json with release information
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
VERSION_ONLY="${TAG_NAME#v}"
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_OUTPUT
echo "VERSION_ONLY=${VERSION_ONLY}" >> $GITHUB_OUTPUT
# Extract date from tag if available (format: v1.2.3-20251113-abc)
if [[ "${TAG_NAME}" =~ -([0-9]{8})- ]]; then
DATE_STR="${BASH_REMATCH[1]}"
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
else
# Use release published date
BUILD_DATE_ISO=$(echo "${{ github.event.release.published_at }}" | cut -d'T' -f1)
fi
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Get release information
id: release_info
run: |
# Use jq to properly escape JSON
RELEASE_BODY="${{ github.event.release.body }}"
RELEASE_NAME="${{ github.event.release.name }}"
# Handle empty values
if [ -z "$RELEASE_BODY" ]; then
RELEASE_BODY=""
fi
if [ -z "$RELEASE_NAME" ]; then
RELEASE_NAME=""
fi
# Save to temporary files for proper handling
echo -n "$RELEASE_BODY" > /tmp/release_body.txt
echo -n "$RELEASE_NAME" > /tmp/release_name.txt
# Use jq to escape JSON strings
RELEASE_BODY_JSON=$(jq -Rs . < /tmp/release_body.txt)
RELEASE_NAME_JSON=$(jq -Rs . < /tmp/release_name.txt)
echo "RELEASE_BODY=${RELEASE_BODY_JSON}" >> $GITHUB_OUTPUT
echo "RELEASE_NAME=${RELEASE_NAME_JSON}" >> $GITHUB_OUTPUT
- name: Download current version.json from server
id: download_version
continue-on-error: true
run: |
# Try to download current version.json from server
curl -f -s "https://version.crossdesk.cn/version.json" -o version.json || echo "Failed to download, will create new one"
- name: Generate or update version.json
run: |
# If version.json exists, try to preserve downloads section
if [ -f version.json ] && jq -e '.downloads' version.json > /dev/null 2>&1; then
EXISTING_DOWNLOADS=$(jq -c '.downloads' version.json)
if [ "$EXISTING_DOWNLOADS" != "null" ] && [ "$EXISTING_DOWNLOADS" != "{}" ]; then
DOWNLOADS_JSON="$EXISTING_DOWNLOADS"
else
DOWNLOADS_JSON=""
fi
else
DOWNLOADS_JSON=""
fi
# If downloads is empty, use default structure
if [ -z "$DOWNLOADS_JSON" ]; then
DOWNLOADS_JSON=$(cat << DOWNLOADS_EOF
{
"windows-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-${{ steps.version.outputs.TAG_NAME }}.exe",
"filename": "crossdesk-win-x64-${{ steps.version.outputs.TAG_NAME }}.exe"
},
"macos-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-x64-${{ steps.version.outputs.TAG_NAME }}.pkg",
"filename": "crossdesk-macos-x64-${{ steps.version.outputs.TAG_NAME }}.pkg"
},
"macos-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-arm64-${{ steps.version.outputs.TAG_NAME }}.pkg",
"filename": "crossdesk-macos-arm64-${{ steps.version.outputs.TAG_NAME }}.pkg"
},
"linux-amd64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-amd64-${{ steps.version.outputs.TAG_NAME }}.deb",
"filename": "crossdesk-linux-amd64-${{ steps.version.outputs.TAG_NAME }}.deb"
},
"linux-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-arm64-${{ steps.version.outputs.TAG_NAME }}.deb",
"filename": "crossdesk-linux-arm64-${{ steps.version.outputs.TAG_NAME }}.deb"
}
}
DOWNLOADS_EOF
)
fi
# Generate version.json using cat and heredoc
cat > version.json << EOF
{
"version": "${{ steps.version.outputs.VERSION_ONLY }}",
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
"releaseName": ${{ steps.release_info.outputs.RELEASE_NAME }},
"releaseNotes": ${{ steps.release_info.outputs.RELEASE_BODY }},
"tagName": "${{ steps.version.outputs.TAG_NAME }}",
"downloads": ${DOWNLOADS_JSON}
}
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 }}

4
.gitignore vendored
View File

@@ -1,10 +1,10 @@
# Xmake cache # Xmake cache
.xmake/ .xmake/
build/ build/
certs/
# MacOS Cache # MacOS Cache
.DS_Store .DS_Store
# VSCode cache # VSCode cache
.vscode .vscode
continuous-desk.code-workspace

4
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "submodules/minirtc"] [submodule "thirdparty/minirtc"]
path = submodules/minirtc path = thirdparty/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-brightgreen.svg)]() [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.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/web-client) [![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
[![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)]()
@@ -11,27 +11,14 @@
[ [English](README_EN.md) / 中文 ] [ [English](README_EN.md) / 中文 ]
PC 客户端
![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905) ![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905)
Web 客户端
<p align="center">
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
</p>
## 简介 ## 简介
CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备 CrossDesk 是一个轻量级的跨平台远程桌面软件。
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 及以上 ( 低版本可自行编译实现兼容 ) |
## 使用 ## 使用
@@ -46,12 +33,6 @@ 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" />
## 如何编译 ## 如何编译
依赖: 依赖:
@@ -61,7 +42,7 @@ CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频
Linux环境下需安装以下包 Linux环境下需安装以下包
``` ```
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxfixes-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
``` ```
编译 编译
@@ -76,14 +57,7 @@ git submodule update
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
编译选项
```
--USE_CUDA=true/false: 启用 CUDA 硬件编解码,默认不启用
--CROSSDESK_VERSION=xxx: 指定 CrossDesk 的版本
# 示例
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
```
运行 运行
``` ```
xmake r crossdesk xmake r crossdesk
@@ -91,14 +65,13 @@ xmake r crossdesk
### 无 CUDA 环境下的开发支持 ### 无 CUDA 环境下的开发支持
对于**未安装 CUDA 环境的 Linux 开发者,如果希望编译后的成果物拥有硬件编解码能力**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。 对于**未安装 CUDA 环境的 Linux 开发者**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
进入容器,下载工程后执行: 进入容器,下载工程后执行:
``` ```
export CUDA_PATH=/usr/local/cuda export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data export XMAKE_GLOBALDIR=/data
xmake f --USE_CUDA=true
xmake b --root -vy crossdesk xmake b --root -vy crossdesk
``` ```
@@ -120,7 +93,6 @@ set CUDA_PATH=path_to_cuda_installdir
``` ```
重新执行: 重新执行:
``` ```
xmake f --USE_CUDA=true
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
@@ -181,7 +153,7 @@ sudo docker run -d \
-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:v1.1.1 crossdesk/crossdesk-server:v1.0.0
``` ```
上述命令中,用户需注意的参数如下: 上述命令中,用户需注意的参数如下:
@@ -315,10 +287,10 @@ Generation complete. Deployment files::
Server certificate: crossdesk.cn_bundle.crt Server certificate: crossdesk.cn_bundle.crt
``` ```
### 服务端 #### 服务端
**crossdesk.cn.key****crossdesk.cn_bundle.crt** 放置到 **/path/to/your/certs** 目录下。 **crossdesk.cn.key****crossdesk.cn_bundle.crt** 放置到 **/path/to/your/certs** 目录下。
### 客户端 #### 客户端
1. 点击右上角设置进入设置页面。<br> 1. 点击右上角设置进入设置页面。<br>
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br> <img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
@@ -331,8 +303,5 @@ Generation complete. Deployment files::
7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br> 7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br> <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
### Web 客户端
详情见项目 [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
# 常见问题 # 常见问题
见 [常见问题](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-brightgreen.svg)]() [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.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/web-client) [![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
[![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)]()
@@ -11,29 +11,14 @@
[ [中文](README.md) / English ] [ [中文](README.md) / English ]
PC Client
![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687) ![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687)
Web Client
<p align="center">
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
</p>
# Intro # Intro
CrossDesk is a lightweight cross-platform remote desktop software. 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.
@@ -48,13 +33,6 @@ 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:
@@ -64,7 +42,7 @@ Requirements:
Following packages need to be installed on Linux: Following packages need to be installed on Linux:
``` ```
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxfixes-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
``` ```
Build: Build:
@@ -79,14 +57,7 @@ git submodule update
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
Build options:
```
--USE_CUDA=true/false: enable CUDA acceleration codec, default: false
--CROSSDESK_VERSION=xxx: set the version number
# example:
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
```
Run: Run:
``` ```
xmake r crossdesk xmake r crossdesk
@@ -94,7 +65,7 @@ xmake r crossdesk
#### Development Without CUDA Environment #### Development Without CUDA Environment
For **Linux developers who do not have a CUDA environment installed and want to enable hardware codec feature**, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided. For **Linux developers who do not have a CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup. This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
After entering the container, download the project and run: After entering the container, download the project and run:
@@ -102,7 +73,6 @@ After entering the container, download the project and run:
export CUDA_PATH=/usr/local/cuda export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data export XMAKE_GLOBALDIR=/data
xmake f --USE_CUDA=true
xmake b --root -vy crossdesk xmake b --root -vy crossdesk
``` ```
@@ -125,7 +95,6 @@ set CUDA_PATH=path_to_cuda_installdir:
``` ```
Then re-run: Then re-run:
``` ```
xmake f --USE_CUDA=true
xmake b -vy crossdesk xmake b -vy crossdesk
``` ```
@@ -189,7 +158,7 @@ sudo docker run -d \
-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:v1.1.1 crossdesk/crossdesk-server:v1.0.0
``` ```
The parameters you need to pay attention to are as follows: The parameters you need to pay attention to are as follows:
@@ -323,10 +292,10 @@ Generation complete. Deployment files::
Server certificate: crossdesk.cn_bundle.crt Server certificate: crossdesk.cn_bundle.crt
``` ```
### Server Side #### Server Side
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory. Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
### Client Side #### Client Side
1. Click the settings icon in the top-right corner to enter the settings page.<br> 1. Click the settings icon in the top-right corner to enter the settings page.<br>
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br> <img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
@@ -339,8 +308,5 @@ Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/y
4. Check the option to use **Self-Hosted Server Configuration**.<br><br> 4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br> <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
### Web Client
See [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
# 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) .

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -9,10 +9,7 @@ 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."
# Remove 'v' prefix from version for Debian package (Debian version must start with digit) DEB_DIR="${PKG_NAME}-${APP_VERSION}"
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"
@@ -38,7 +35,7 @@ done
cat > "$DEBIAN_DIR/control" << EOF cat > "$DEBIAN_DIR/control" << EOF
Package: $PKG_NAME Package: $PKG_NAME
Version: $DEB_VERSION Version: $APP_VERSION
Architecture: $ARCHITECTURE Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER Maintainer: $MAINTAINER
Description: $DESCRIPTION Description: $DESCRIPTION
@@ -53,7 +50,7 @@ EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
[Desktop Entry] [Desktop Entry]
Version=$DEB_VERSION Version=$APP_VERSION
Name=$APP_NAME Name=$APP_NAME
Comment=$DESCRIPTION Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME Exec=/usr/bin/$PKG_NAME

View File

@@ -9,10 +9,7 @@ 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."
# Remove 'v' prefix from version for Debian package (Debian version must start with digit) DEB_DIR="${PKG_NAME}-${APP_VERSION}"
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"
@@ -38,7 +35,7 @@ done
cat > "$DEBIAN_DIR/control" << EOF cat > "$DEBIAN_DIR/control" << EOF
Package: $PKG_NAME Package: $PKG_NAME
Version: $DEB_VERSION Version: $APP_VERSION
Architecture: $ARCHITECTURE Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER Maintainer: $MAINTAINER
Description: $DESCRIPTION Description: $DESCRIPTION
@@ -52,7 +49,7 @@ EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
[Desktop Entry] [Desktop Entry]
Version=$DEB_VERSION Version=$APP_VERSION
Name=$APP_NAME Name=$APP_NAME
Comment=$DESCRIPTION Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME Exec=/usr/bin/$PKG_NAME

49
scripts/macosx/pkg_arm64.sh Executable file → Normal file
View File

@@ -86,66 +86,29 @@ pkgbuild \
--component "${APP_BUNDLE}" \ --component "${APP_BUNDLE}" \
build_pkg_temp/${APP_NAME}-component.pkg build_pkg_temp/${APP_NAME}-component.pkg
mkdir -p build_pkg_scripts mkdir -p scripts
cat > build_pkg_scripts/postinstall <<'EOF' cat > scripts/postinstall <<'EOF'
#!/bin/bash #!/bin/bash
set -e
IDENTIFIER="cn.crossdesk.app"
# 获取当前登录用户
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
# 复制证书文件
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
mkdir -p "$DEST" mkdir -p "$DEST"
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
# 清除应用的权限授权,以便重新授权
# 使用 tccutil 重置录屏权限和辅助功能权限
if command -v tccutil >/dev/null 2>&1; then
# 重置录屏权限
tccutil reset ScreenCapture "$IDENTIFIER" 2>/dev/null || true
# 重置辅助功能权限
tccutil reset Accessibility "$IDENTIFIER" 2>/dev/null || true
# 重置摄像头权限(如果需要)
tccutil reset Camera "$IDENTIFIER" 2>/dev/null || true
# 重置麦克风权限(如果需要)
tccutil reset Microphone "$IDENTIFIER" 2>/dev/null || true
fi
# 为所有用户清除权限(可选,如果需要)
# 遍历所有用户目录并清除权限
for USER_DIR in /Users/*; do
if [ -d "$USER_DIR" ] && [ "$USER_DIR" != "/Users/Shared" ]; then
USER_NAME=$(basename "$USER_DIR")
# 跳过系统用户
if [ "$USER_NAME" != "Shared" ] && [ -d "$USER_DIR/Library" ]; then
# 删除 TCC 数据库中的相关条目(需要管理员权限)
TCC_DB="$USER_DIR/Library/Application Support/com.apple.TCC/TCC.db"
if [ -f "$TCC_DB" ]; then
# 使用 sqlite3 删除相关权限记录(如果可用)
if command -v sqlite3 >/dev/null 2>&1; then
sqlite3 "$TCC_DB" "DELETE FROM access WHERE client='$IDENTIFIER' AND service IN ('kTCCServiceScreenCapture', 'kTCCServiceAccessibility');" 2>/dev/null || true
fi
fi
fi
fi
done
exit 0 exit 0
EOF EOF
chmod +x build_pkg_scripts/postinstall chmod +x scripts/postinstall
pkgbuild \ pkgbuild \
--root "${CERTS_SOURCE}" \ --root "${CERTS_SOURCE}" \
--identifier "${IDENTIFIER}.certs" \ --identifier "${IDENTIFIER}.certs" \
--version "${APP_VERSION}" \ --version "${APP_VERSION}" \
--install-location "/Library/Application Support/CrossDesk/certs" \ --install-location "/Library/Application Support/CrossDesk/certs" \
--scripts build_pkg_scripts \ --scripts scripts \
build_pkg_temp/${APP_NAME}-certs.pkg build_pkg_temp/${APP_NAME}-certs.pkg
productbuild \ productbuild \
@@ -155,7 +118,7 @@ productbuild \
echo "PKG package created: ${PKG_NAME}" echo "PKG package created: ${PKG_NAME}"
rm -rf build_pkg_temp build_pkg_scripts ${APP_BUNDLE} rm -rf build_pkg_temp scripts ${APP_BUNDLE}
echo "PKG package created successfully." echo "PKG package created successfully."
echo "package ${APP_BUNDLE}" echo "package ${APP_BUNDLE}"

49
scripts/macosx/pkg_x64.sh Executable file → Normal file
View File

@@ -86,66 +86,29 @@ pkgbuild \
--component "${APP_BUNDLE}" \ --component "${APP_BUNDLE}" \
build_pkg_temp/${APP_NAME}-component.pkg build_pkg_temp/${APP_NAME}-component.pkg
mkdir -p build_pkg_scripts mkdir -p scripts
cat > build_pkg_scripts/postinstall <<'EOF' cat > scripts/postinstall <<'EOF'
#!/bin/bash #!/bin/bash
set -e
IDENTIFIER="cn.crossdesk.app"
# 获取当前登录用户
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
# 复制证书文件
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
mkdir -p "$DEST" mkdir -p "$DEST"
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
# 清除应用的权限授权,以便重新授权
# 使用 tccutil 重置录屏权限和辅助功能权限
if command -v tccutil >/dev/null 2>&1; then
# 重置录屏权限
tccutil reset ScreenCapture "$IDENTIFIER" 2>/dev/null || true
# 重置辅助功能权限
tccutil reset Accessibility "$IDENTIFIER" 2>/dev/null || true
# 重置摄像头权限(如果需要)
tccutil reset Camera "$IDENTIFIER" 2>/dev/null || true
# 重置麦克风权限(如果需要)
tccutil reset Microphone "$IDENTIFIER" 2>/dev/null || true
fi
# 为所有用户清除权限(可选,如果需要)
# 遍历所有用户目录并清除权限
for USER_DIR in /Users/*; do
if [ -d "$USER_DIR" ] && [ "$USER_DIR" != "/Users/Shared" ]; then
USER_NAME=$(basename "$USER_DIR")
# 跳过系统用户
if [ "$USER_NAME" != "Shared" ] && [ -d "$USER_DIR/Library" ]; then
# 删除 TCC 数据库中的相关条目(需要管理员权限)
TCC_DB="$USER_DIR/Library/Application Support/com.apple.TCC/TCC.db"
if [ -f "$TCC_DB" ]; then
# 使用 sqlite3 删除相关权限记录(如果可用)
if command -v sqlite3 >/dev/null 2>&1; then
sqlite3 "$TCC_DB" "DELETE FROM access WHERE client='$IDENTIFIER' AND service IN ('kTCCServiceScreenCapture', 'kTCCServiceAccessibility');" 2>/dev/null || true
fi
fi
fi
fi
done
exit 0 exit 0
EOF EOF
chmod +x build_pkg_scripts/postinstall chmod +x scripts/postinstall
pkgbuild \ pkgbuild \
--root "${CERTS_SOURCE}" \ --root "${CERTS_SOURCE}" \
--identifier "${IDENTIFIER}.certs" \ --identifier "${IDENTIFIER}.certs" \
--version "${APP_VERSION}" \ --version "${APP_VERSION}" \
--install-location "/Library/Application Support/CrossDesk/certs" \ --install-location "/Library/Application Support/CrossDesk/certs" \
--scripts build_pkg_scripts \ --scripts scripts \
build_pkg_temp/${APP_NAME}-certs.pkg build_pkg_temp/${APP_NAME}-certs.pkg
productbuild \ productbuild \
@@ -155,7 +118,7 @@ productbuild \
echo "PKG package created: ${PKG_NAME}" echo "PKG package created: ${PKG_NAME}"
rm -rf build_pkg_temp build_pkg_scripts ${APP_BUNDLE} rm -rf build_pkg_temp scripts ${APP_BUNDLE}
echo "PKG package created successfully." echo "PKG package created successfully."
echo "package ${APP_BUNDLE}" echo "package ${APP_BUNDLE}"

View File

@@ -1,313 +0,0 @@
#include "daemon.h"
#include <atomic>
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#ifdef _WIN32
#include <process.h>
#include <tchar.h>
#include <windows.h>
#elif __APPLE__
#include <fcntl.h>
#include <limits.h>
#include <mach-o/dyld.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstring>
#endif
#ifndef _WIN32
Daemon* Daemon::instance_ = nullptr;
#endif
// get executable file path
static std::string GetExecutablePath() {
#ifdef _WIN32
char path[32768];
DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
if (length > 0 && length < sizeof(path)) {
return std::string(path);
}
#elif __APPLE__
char path[PATH_MAX];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
char resolved_path[PATH_MAX];
if (realpath(path, resolved_path) != nullptr) {
return std::string(resolved_path);
}
return std::string(path);
}
#else
char path[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (count != -1) {
path[count] = '\0';
return std::string(path);
}
#endif
return "";
}
Daemon::Daemon(const std::string& name)
: name_(name)
#ifdef _WIN32
,
running_(false)
#else
,
running_(true)
#endif
{
}
void Daemon::stop() { running_ = false; }
bool Daemon::isRunning() const { return running_; }
bool Daemon::start(MainLoopFunc loop) {
#ifdef _WIN32
running_ = true;
return runWithRestart(loop);
#elif __APPLE__
// macOS: Use child process monitoring (like Windows) to preserve GUI
running_ = true;
return runWithRestart(loop);
#else
// linux: Daemonize first, then run with restart monitoring
instance_ = this;
// check if running from terminal before fork
bool from_terminal =
(isatty(STDIN_FILENO) != 0) || (isatty(STDOUT_FILENO) != 0);
// first fork: detach from terminal
pid_t pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon process" << std::endl;
return false;
}
if (pid > 0) _exit(0);
if (setsid() < 0) {
std::cerr << "Failed to create new session" << std::endl;
return false;
}
pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon process (second fork)" << std::endl;
return false;
}
if (pid > 0) _exit(0);
umask(0);
chdir("/");
// redirect file descriptors: keep stdout/stderr if from terminal, else
// redirect to /dev/null
int fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, STDIN_FILENO);
if (!from_terminal) {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
if (fd > 2) close(fd);
}
// set up signal handlers
signal(SIGTERM, [](int) {
if (instance_) instance_->stop();
});
signal(SIGINT, [](int) {
if (instance_) instance_->stop();
});
// ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
// set up SIGCHLD handler to reap zombie processes
struct sigaction sa_chld;
sa_chld.sa_handler = [](int) {
// reap zombie processes
while (waitpid(-1, nullptr, WNOHANG) > 0) {
// continue until no more zombie children
}
};
sigemptyset(&sa_chld.sa_mask);
sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa_chld, nullptr);
running_ = true;
return runWithRestart(loop);
#endif
}
#ifdef _WIN32
static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) {
try {
loop();
return 0; // normal exit
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
return 1; // c++ exception
} catch (...) {
std::cerr << "Unknown exception caught" << std::endl;
return 1; // other exception
}
}
static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) {
__try {
return RunLoopCatchCpp(loop);
} __except (EXCEPTION_EXECUTE_HANDLER) {
// catch system-level crashes (access violation, divide by zero, etc.)
DWORD code = GetExceptionCode();
std::cerr << "System crash detected (SEH exception code: 0x" << std::hex
<< code << std::dec << ")" << std::endl;
return 2; // System crash
}
}
#endif
// run with restart logic: parent monitors child process and restarts on crash
bool Daemon::runWithRestart(MainLoopFunc loop) {
int restart_count = 0;
std::string exe_path = GetExecutablePath();
if (exe_path.empty()) {
std::cerr
<< "Failed to get executable path, falling back to direct execution"
<< std::endl;
while (isRunning()) {
try {
loop();
break;
} catch (...) {
restart_count++;
std::cerr << "Exception caught, restarting... (attempt "
<< restart_count << ")" << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
}
}
return true;
}
while (isRunning()) {
#ifdef _WIN32
// windows: use CreateProcess to create child process
STARTUPINFOA si = {sizeof(si)};
PROCESS_INFORMATION pi = {0};
std::string cmd_line = "\"" + exe_path + "\" --child";
std::vector<char> cmd_line_buf(cmd_line.begin(), cmd_line.end());
cmd_line_buf.push_back('\0');
BOOL success = CreateProcessA(
nullptr, // executable file path (specified in command line)
cmd_line_buf.data(), // command line arguments
nullptr, // process security attributes
nullptr, // thread security attributes
FALSE, // don't inherit handles
0, // creation flags
nullptr, // environment variables (inherit from parent)
nullptr, // current directory
&si, // startup info
&pi // process information
);
if (!success) {
std::cerr << "Failed to create child process, error: " << GetLastError()
<< std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
restart_count++;
continue;
}
DWORD exit_code = 0;
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (exit_code == 0) {
break; // normal exit
}
restart_count++;
std::cerr << "Child process exited with code " << exit_code
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
#else
// linux: use fork + exec to create child process
pid_t pid = fork();
if (pid == 0) {
execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr);
_exit(1); // exec failed
} else if (pid > 0) {
int status = 0;
pid_t waited_pid = waitpid(pid, &status, 0);
if (waited_pid < 0) {
restart_count++;
std::cerr << "waitpid failed, errno: " << errno
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
continue;
}
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
if (exit_code == 0) {
break; // normal exit
}
restart_count++;
std::cerr << "Child process exited with code " << exit_code
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
} else if (WIFSIGNALED(status)) {
restart_count++;
std::cerr << "Child process crashed with signal " << WTERMSIG(status)
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
} else {
restart_count++;
std::cerr << "Child process exited with unknown status, restarting... "
"(attempt "
<< restart_count << ")" << std::endl;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
} else {
std::cerr << "Failed to fork child process" << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
restart_count++;
}
#endif
}
return true;
}

View File

@@ -1,39 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-19
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DAEMON_H_
#define _DAEMON_H_
#include <functional>
#include <string>
#define DAEMON_DEFAULT_RESTART_DELAY_MS 1000
class Daemon {
public:
using MainLoopFunc = std::function<void()>;
Daemon(const std::string& name);
bool start(MainLoopFunc loop);
void stop();
bool isRunning() const;
private:
std::string name_;
bool runWithRestart(MainLoopFunc loop);
#ifdef _WIN32
bool running_;
#else
static Daemon* instance_;
volatile bool running_;
#endif
};
#endif

View File

@@ -1,66 +1,17 @@
#ifdef _WIN32 #ifdef _WIN32
#ifdef CROSSDESK_DEBUG #ifdef DESK_PORT_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\"")
#endif #endif
#endif #endif
#include <cstring> #include "rd_log.h"
#include <memory>
#include <string>
#include "config_center.h"
#include "daemon.h"
#include "path_manager.h"
#include "render.h" #include "render.h"
int main(int argc, char* argv[]) { int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) {
// check if running as child process Render render;
bool is_child = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--child") == 0) {
is_child = true;
break;
}
}
if (is_child) {
// child process: run render directly
crossdesk::Render render;
render.Run();
return 0;
}
bool enable_daemon = false;
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
if (path_manager) {
std::string cert_path =
(path_manager->GetCertPath() / "crossdesk.cn_root.crt").string();
std::string cache_path = path_manager->GetCachePath().string();
crossdesk::ConfigCenter config_center(cache_path + "/config.ini",
cert_path);
enable_daemon = config_center.IsEnableDaemon();
}
if (enable_daemon) {
// start daemon with restart monitoring
Daemon daemon("CrossDesk");
// define main loop function: run render and stop daemon on normal exit
Daemon::MainLoopFunc main_loop = [&daemon]() {
crossdesk::Render render;
render.Run();
daemon.stop();
};
// start daemon and return result
bool success = daemon.start(main_loop);
return success ? 0 : 1;
}
// run without daemon: direct execution
crossdesk::Render render;
render.Run(); render.Run();
return 0; return 0;
} }

View File

@@ -1,302 +0,0 @@
#include "autostart.h"
#include <cstdlib>
#include <filesystem>
#include <fstream>
#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <limits.h>
#include <mach-o/dyld.h>
#include <unistd.h>
#elif defined(__linux__)
#include <linux/limits.h>
#include <unistd.h>
#endif
namespace crossdesk {
static std::string get_home_dir() {
const char* home = std::getenv("HOME");
if (!home) {
return "";
}
return std::string(home);
}
static bool file_exists(const std::string& path) {
return std::filesystem::exists(path) &&
std::filesystem::is_regular_file(path);
}
static std::string GetExecutablePath() {
#ifdef _WIN32
char path[32768];
DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
if (length > 0 && length < sizeof(path)) {
return std::string(path);
}
#elif defined(__APPLE__)
char path[1024];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
char resolved_path[PATH_MAX];
if (realpath(path, resolved_path) != nullptr) {
return std::string(resolved_path);
}
return std::string(path);
}
#elif defined(__linux__)
char path[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (count != -1) {
path[count] = '\0';
return std::string(path);
}
#endif
return "";
}
// Windows
#ifdef _WIN32
static constexpr const char* WINDOWS_RUN_KEY =
"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
static bool windows_enable(const std::string& appName,
const std::string& exePath) {
if (exePath.empty() || !std::filesystem::exists(exePath)) {
return false;
}
HKEY hKey = nullptr;
// Use KEY_WRITE to ensure we have write permission
LONG result =
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_WRITE, &hKey);
if (result != ERROR_SUCCESS) {
return false;
}
std::string regValue = exePath;
if (!exePath.empty() && exePath.find(' ') != std::string::npos) {
if (exePath.front() != '"' || exePath.back() != '"') {
regValue = "\"" + exePath + "\"";
}
}
// Ensure we close the key even if RegSetValueExA fails
result = RegSetValueExA(hKey, appName.c_str(), 0, REG_SZ,
reinterpret_cast<const BYTE*>(regValue.c_str()),
static_cast<DWORD>(regValue.size() + 1));
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
}
static bool windows_disable(const std::string& appName) {
HKEY hKey = nullptr;
LONG result =
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_WRITE, &hKey);
if (result != ERROR_SUCCESS) {
return false;
}
result = RegDeleteValueA(hKey, appName.c_str());
RegCloseKey(hKey);
// Return true even if the value doesn't exist (already disabled)
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}
static bool windows_exists(const std::string& appName) {
HKEY hKey = nullptr;
LONG result =
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS) {
return false;
}
result = RegQueryValueExA(hKey, appName.c_str(), nullptr, nullptr, nullptr,
nullptr);
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
}
#endif
// Linux
#if defined(__linux__)
static std::string linux_desktop_path(const std::string& appName) {
std::string home = get_home_dir();
if (home.empty()) {
return "";
}
return home + "/.config/autostart/" + appName + ".desktop";
}
static bool linux_enable(const std::string& appName,
const std::string& exePath) {
std::string home = get_home_dir();
if (home.empty()) {
return false;
}
std::filesystem::path dir =
std::filesystem::path(home) / ".config" / "autostart";
// Create directory if it doesn't exist
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (ec) {
return false;
}
std::string path = linux_desktop_path(appName);
if (path.empty()) {
return false;
}
std::ofstream file(path);
if (!file.is_open()) {
return false;
}
file << "[Desktop Entry]\n";
file << "Type=Application\n";
file << "Exec=" << exePath << "\n";
file << "Hidden=false\n";
file << "NoDisplay=false\n";
file << "X-GNOME-Autostart-enabled=true\n";
file << "Terminal=false\n";
file << "StartupNotify=false\n";
file << "Name=" << appName << "\n";
file.close();
return file.good();
}
static bool linux_disable(const std::string& appName) {
std::string path = linux_desktop_path(appName);
if (path.empty()) {
return false;
}
std::error_code ec;
return std::filesystem::remove(path, ec) && !ec;
}
static bool linux_exists(const std::string& appName) {
std::string path = linux_desktop_path(appName);
if (path.empty()) {
return false;
}
return file_exists(path);
}
#endif
// macOS
#ifdef __APPLE__
static std::string mac_plist_path(const std::string& appName) {
std::string home = get_home_dir();
if (home.empty()) {
return "";
}
return home + "/Library/LaunchAgents/" + appName + ".plist";
}
static bool mac_enable(const std::string& appName, const std::string& exePath) {
std::string path = mac_plist_path(appName);
if (path.empty()) {
return false;
}
// Ensure LaunchAgents directory exists
std::filesystem::path dir =
std::filesystem::path(get_home_dir()) / "Library" / "LaunchAgents";
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (ec) {
return false;
}
std::ofstream file(path);
if (!file.is_open()) {
return false;
}
file << R"(<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>)"
<< appName << R"(</string>
<key>ProgramArguments</key>
<array>
<string>)"
<< exePath << R"(</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>)";
file.close();
return file.good();
}
static bool mac_disable(const std::string& appName) {
std::string path = mac_plist_path(appName);
if (path.empty()) {
return false;
}
std::error_code ec;
return std::filesystem::remove(path, ec) && !ec;
}
static bool mac_exists(const std::string& appName) {
std::string path = mac_plist_path(appName);
if (path.empty()) {
return false;
}
return file_exists(path);
}
#endif
bool EnableAutostart(const std::string& appName) {
std::string exePath = GetExecutablePath();
if (exePath.empty()) {
return false;
}
#ifdef _WIN32
return windows_enable(appName, exePath);
#elif __APPLE__
return mac_enable(appName, exePath);
#else
return linux_enable(appName, exePath);
#endif
}
bool DisableAutostart(const std::string& appName) {
#ifdef _WIN32
return windows_disable(appName);
#elif __APPLE__
return mac_disable(appName);
#else
return linux_disable(appName);
#endif
}
bool IsAutostartEnabled(const std::string& appName) {
#ifdef _WIN32
return windows_exists(appName);
#elif __APPLE__
return mac_exists(appName);
#else
return linux_exists(appName);
#endif
}
} // namespace crossdesk

View File

@@ -1,21 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-18
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _AUTOSTART_H_
#define _AUTOSTART_H_
#include <string>
namespace crossdesk {
bool EnableAutostart(const std::string& appName);
bool DisableAutostart(const std::string& appName);
bool IsAutostartEnabled(const std::string& appName);
} // namespace crossdesk
#endif

View File

@@ -9,8 +9,6 @@
#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)
@@ -42,5 +40,5 @@ class DisplayInfo {
int width = 0; int width = 0;
int height = 0; int height = 0;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -19,8 +19,6 @@
#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;
@@ -41,21 +39,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]);
@@ -79,8 +77,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) {
@@ -124,5 +122,4 @@ std::string GetHostName() {
} }
#endif #endif
return hostname; return hostname;
} }
} // namespace crossdesk

View File

@@ -9,10 +9,7 @@
#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,10 +1,5 @@
#include "config_center.h" #include "config_center.h"
#include "autostart.h"
#include "rd_log.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),
@@ -51,9 +46,7 @@ int ConfigCenter::Load() {
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_ =
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_); ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
enable_autostart_ =
ini_.GetBoolValue(section_, "enable_autostart", enable_autostart_);
enable_daemon_ = ini_.GetBoolValue(section_, "enable_daemon", enable_daemon_);
enable_minimize_to_tray_ = ini_.GetBoolValue( enable_minimize_to_tray_ = ini_.GetBoolValue(
section_, "enable_minimize_to_tray", enable_minimize_to_tray_); section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
@@ -76,8 +69,6 @@ int ConfigCenter::Save() {
static_cast<long>(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_autostart", enable_autostart_);
ini_.SetBoolValue(section_, "enable_daemon", enable_daemon_);
ini_.SetBoolValue(section_, "enable_minimize_to_tray", ini_.SetBoolValue(section_, "enable_minimize_to_tray",
enable_minimize_to_tray_); enable_minimize_to_tray_);
@@ -188,9 +179,8 @@ int ConfigCenter::SetServerPort(int signal_server_port) {
int ConfigCenter::SetCoturnServerPort(int coturn_server_port) { int ConfigCenter::SetCoturnServerPort(int coturn_server_port) {
coturn_server_port_ = coturn_server_port; coturn_server_port_ = coturn_server_port;
ini_.SetLongValue(section_, "coturn_server_port", SI_Error rc = ini_.SetLongValue(section_, "coturn_server_port",
static_cast<long>(coturn_server_port_)); static_cast<long>(coturn_server_port_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) { if (rc < 0) {
return -1; return -1;
} }
@@ -209,7 +199,6 @@ 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;
@@ -219,47 +208,6 @@ 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;
}
int ConfigCenter::SetAutostart(bool enable_autostart) {
enable_autostart_ = enable_autostart;
bool success = false;
if (enable_autostart) {
success = EnableAutostart("CrossDesk");
} else {
success = DisableAutostart("CrossDesk");
}
ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_);
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
if (!success) {
LOG_ERROR("SetAutostart failed");
return -1;
}
return 0;
}
int ConfigCenter::SetDaemon(bool enable_daemon) {
enable_daemon_ = enable_daemon;
ini_.SetBoolValue(section_, "enable_daemon", enable_daemon_);
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0; return 0;
} }
@@ -315,9 +263,4 @@ 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_; }
bool ConfigCenter::IsEnableAutostart() const { return enable_autostart_; }
bool ConfigCenter::IsEnableDaemon() const { return enable_daemon_; }
} // namespace crossdesk

View File

@@ -11,8 +11,6 @@
#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 };
@@ -40,8 +38,6 @@ class ConfigCenter {
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);
int SetAutostart(bool enable_autostart);
int SetDaemon(bool enable_daemon);
// read config // read config
@@ -62,8 +58,6 @@ class ConfigCenter {
std::string GetDefaultCertFilePath() const; std::string GetDefaultCertFilePath() const;
bool IsSelfHosted() const; bool IsSelfHosted() const;
bool IsMinimizeToTray() const; bool IsMinimizeToTray() const;
bool IsEnableAutostart() const;
bool IsEnableDaemon() const;
int Load(); int Load();
int Save(); int Save();
@@ -76,10 +70,10 @@ 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_60; VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30;
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_ = true; bool enable_turn_ = false;
bool enable_srtp_ = false; bool enable_srtp_ = false;
std::string signal_server_host_ = "api.crossdesk.cn"; std::string signal_server_host_ = "api.crossdesk.cn";
std::string signal_server_host_default_ = "api.crossdesk.cn"; std::string signal_server_host_default_ = "api.crossdesk.cn";
@@ -90,8 +84,6 @@ class ConfigCenter {
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;
bool enable_autostart_ = false;
bool enable_daemon_ = false;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -9,13 +9,7 @@
#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,
@@ -59,7 +53,7 @@ typedef struct {
int* bottom; int* bottom;
} HostInfo; } HostInfo;
struct RemoteAction { typedef struct {
ControlType type; ControlType type;
union { union {
Mouse m; Mouse m;
@@ -68,111 +62,7 @@ struct RemoteAction {
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*);
@@ -189,5 +79,5 @@ class DeviceController {
// virtual int Hook(); // virtual int Hook();
// virtual int Unhook(); // virtual int Unhook();
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -11,8 +11,6 @@
#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 };
@@ -32,5 +30,5 @@ class DeviceControllerFactory {
} }
} }
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -3,8 +3,6 @@
#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;
@@ -51,16 +49,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
} }
int KeyboardCapturer::Unhook() { int KeyboardCapturer::Unhook() {
g_on_key_action = nullptr;
g_user_ptr = nullptr;
running_ = false; running_ = false;
if (display_) {
XSelectInput(display_, DefaultRootWindow(display_), 0);
XFlush(display_);
}
return 0; return 0;
} }
@@ -78,4 +67,3 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -13,22 +13,20 @@
#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,18 +3,12 @@
#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) {
if (!g_on_key_action) { KeyboardCapturer *keyboard_capturer = (KeyboardCapturer *)userInfo;
return event;
}
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;
@@ -97,7 +91,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;
@@ -124,25 +118,8 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
} }
int KeyboardCapturer::Unhook() { int KeyboardCapturer::Unhook() {
g_on_key_action = nullptr; CFRelease(run_loop_source_);
g_user_ptr = nullptr; CFRelease(event_tap_);
if (event_tap_) {
CGEventTapEnable(event_tap_, false);
}
if (run_loop_source_) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_,
kCFRunLoopCommonModes);
CFRelease(run_loop_source_);
run_loop_source_ = nullptr;
}
if (event_tap_) {
CFRelease(event_tap_);
event_tap_ = nullptr;
}
return 0; return 0;
} }
@@ -187,5 +164,4 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,15 +11,13 @@
#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);
@@ -35,5 +33,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,8 +2,6 @@
#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;
@@ -39,12 +37,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
} }
int KeyboardCapturer::Unhook() { int KeyboardCapturer::Unhook() {
if (keyboard_hook_) { UnhookWindowsHookEx(keyboard_hook_);
g_on_key_action = nullptr;
g_user_ptr = nullptr;
UnhookWindowsHookEx(keyboard_hook_);
keyboard_hook_ = nullptr;
}
return 0; return 0;
} }
@@ -60,5 +53,4 @@ 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,21 +11,18 @@
#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,8 +9,6 @@
#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
@@ -738,5 +736,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,8 +4,6 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {} MouseController::MouseController() {}
MouseController::~MouseController() { Destroy(); } MouseController::~MouseController() { Destroy(); }
@@ -123,5 +121,4 @@ 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,8 +15,6 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController { class MouseController : public DeviceController {
public: public:
MouseController(); MouseController();
@@ -39,5 +37,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,8 +4,6 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {} MouseController::MouseController() {}
MouseController::~MouseController() {} MouseController::~MouseController() {}
@@ -100,5 +98,4 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,8 +11,6 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController { class MouseController : public DeviceController {
public: public:
MouseController(); MouseController();
@@ -28,5 +26,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,8 +2,6 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {} MouseController::MouseController() {}
MouseController::~MouseController() {} MouseController::~MouseController() {}
@@ -71,5 +69,4 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -11,8 +11,6 @@
#include "device_controller.h" #include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController { class MouseController : public DeviceController {
public: public:
MouseController(); MouseController();
@@ -26,5 +24,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

@@ -20,22 +20,12 @@
#define INPUT_WINDOW_PADDING_EN 96 #define INPUT_WINDOW_PADDING_EN 96
#define SETTINGS_WINDOW_WIDTH_CN 202 #define SETTINGS_WINDOW_WIDTH_CN 202
#define SETTINGS_WINDOW_WIDTH_EN 248 #define SETTINGS_WINDOW_WIDTH_EN 248
#if USE_CUDA
#if _WIN32 #if _WIN32
#define SETTINGS_WINDOW_HEIGHT_CN 405
#define SETTINGS_WINDOW_HEIGHT_EN 405
#else
#define SETTINGS_WINDOW_HEIGHT_CN 375
#define SETTINGS_WINDOW_HEIGHT_EN 375
#endif
#else
#if _WIN32
#define SETTINGS_WINDOW_HEIGHT_CN 375
#define SETTINGS_WINDOW_HEIGHT_EN 375
#else
#define SETTINGS_WINDOW_HEIGHT_CN 345 #define SETTINGS_WINDOW_HEIGHT_CN 345
#define SETTINGS_WINDOW_HEIGHT_EN 345 #define SETTINGS_WINDOW_HEIGHT_EN 345
#endif #else
#define SETTINGS_WINDOW_HEIGHT_CN 315
#define SETTINGS_WINDOW_HEIGHT_EN 315
#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
@@ -57,10 +47,6 @@
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218 #define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171 #define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218 #define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
#define ENABLE_AUTOSTART_PADDING_CN 171
#define ENABLE_AUTOSTART_PADDING_EN 218
#define ENABLE_DAEMON_PADDING_CN 171
#define ENABLE_DAEMON_PADDING_EN 218
#define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171 #define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171
#define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218 #define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90 #define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
@@ -73,19 +59,5 @@
#define SETTINGS_OK_BUTTON_PADDING_EN 83 #define SETTINGS_OK_BUTTON_PADDING_EN 83
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78 #define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91 #define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91
#define UPDATE_NOTIFICATION_OK_BUTTON_PADDING_CN 162
#define UPDATE_NOTIFICATION_OK_BUTTON_PADDING_EN 146
#ifdef _WIN32
#define UPDATE_NOTIFICATION_RESERVED_HEIGHT 130
#elif __APPLE__
#define UPDATE_NOTIFICATION_RESERVED_HEIGHT 100
#else
#define UPDATE_NOTIFICATION_RESERVED_HEIGHT 100
#endif
#define REQUEST_PERMISSION_WINDOW_WIDTH_CN 130
#define REQUEST_PERMISSION_WINDOW_HEIGHT_CN 125
#define REQUEST_PERMISSION_WINDOW_WIDTH_EN 260
#define REQUEST_PERMISSION_WINDOW_HEIGHT_EN 125
#define REQUEST_PERMISSION_WINDOW_CHECKBOX_PADDING_CN 90
#define REQUEST_PERMISSION_WINDOW_CHECKBOX_PADDING_EN 210
#endif #endif

View File

@@ -12,9 +12,6 @@
#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 = {
@@ -109,9 +106,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"信令服务端口:"), "Signal Service Port:"}; reinterpret_cast<const char*>(u8"服务端口:"), "Server Port:"};
static std::vector<std::string> self_hosted_server_coturn_server_port = { static std::vector<std::string> self_hosted_server_coturn_server_port = {
reinterpret_cast<const char*>(u8"中继服务端口:"), "Relay Service Port:"}; reinterpret_cast<const char*>(u8"中继服务端口:"), "Relay Server 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 = {
@@ -158,49 +155,18 @@ 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> notification = {
reinterpret_cast<const char*>(u8"通知"), "Notification"};
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> release_date = {
reinterpret_cast<const char*>(u8"发布日期: "), "Release Date: "};
static std::vector<std::string> access_website = {
reinterpret_cast<const char*>(u8"访问官网: "), "Access Website: "};
static std::vector<std::string> update = {
reinterpret_cast<const char*>(u8"更新"), "Update"};
static std::vector<std::string> confirm_delete_connection = { static std::vector<std::string> confirm_delete_connection = {
reinterpret_cast<const char*>(u8"确认删除此连接"), reinterpret_cast<const char*>(u8"确认删除此连接"),
"Confirm to delete this connection"}; "Confirm to delete this connection"};
static std::vector<std::string> enable_autostart = {
reinterpret_cast<const char*>(u8"开机自启:"), "Auto Start:"};
static std::vector<std::string> enable_daemon = {
reinterpret_cast<const char*>(u8"启用守护进程:"), "Enable Daemon:"};
static std::vector<std::string> takes_effect_after_restart = {
reinterpret_cast<const char*>(u8"重启后生效"),
"Takes effect after restart"};
#if _WIN32 #if _WIN32
static std::vector<std::string> minimize_to_tray = { static std::vector<std::string> minimize_to_tray = {
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"), reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
"Minimize to system tray when exit:"}; "Minimize to system tray when exit:"};
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"}; static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
#endif #endif
#ifdef __APPLE__
static std::vector<std::string> request_permissions = {
reinterpret_cast<const char*>(u8"权限请求"), "Request Permissions"};
static std::vector<std::string> screen_recording_permission = {
reinterpret_cast<const char*>(u8"屏幕录制权限"),
"Screen Recording Permission"};
static std::vector<std::string> accessibility_permission = {
reinterpret_cast<const char*>(u8"辅助功能权限"),
"Accessibility Permission"};
static std::vector<std::string> permission_required_message = {
reinterpret_cast<const char*>(u8"该应用需要授权以下权限:"),
"The application requires the following permissions:"};
#endif
} // namespace localization } // namespace localization
} // namespace crossdesk
#endif #endif

View File

@@ -5,8 +5,6 @@
#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);
@@ -289,5 +287,4 @@ int Render::LocalWindow() {
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -2,8 +2,6 @@
#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),
@@ -286,4 +284,3 @@ int Render::ConfirmDeleteConnection() {
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,9 +3,7 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk { static int InputTextCallback(ImGuiInputTextCallbackData *data);
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_),
@@ -79,10 +77,9 @@ 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;
std::shared_lock lock(client_properties_mutex_);
if (client_properties_.find(remote_id) != if (client_properties_.find(remote_id) !=
client_properties_.end()) { client_properties_.end()) {
if (!client_properties_[remote_id]->connection_established_) { if (!client_properties_[remote_id]->connection_established_) {
@@ -102,22 +99,12 @@ int Render::RemoteWindow() {
} }
} }
// check every 1 second for rejoin
if (need_to_rejoin_) { if (need_to_rejoin_) {
auto now = std::chrono::steady_clock::now(); need_to_rejoin_ = false;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( for (const auto &[_, props] : client_properties_) {
now - last_rejoin_check_time_) if (props->rejoin_) {
.count(); ConnectTo(props->remote_id_, props->remote_password_,
props->remember_password_);
if (elapsed >= 1000) {
last_rejoin_check_time_ = now;
need_to_rejoin_ = false;
std::shared_lock lock(client_properties_mutex_);
for (const auto& [_, props] : client_properties_) {
if (props->rejoin_) {
ConnectTo(props->remote_id_, props->remote_password_,
props->remember_password_);
}
} }
} }
} }
@@ -130,7 +117,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, " ");
} }
@@ -142,54 +129,34 @@ 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;
std::shared_lock shared_lock(client_properties_mutex_); if (client_properties_.find(remote_id) == client_properties_.end()) {
bool exists = client_properties_[remote_id] =
(client_properties_.find(remote_id) != client_properties_.end()); std::make_shared<SubStreamWindowProperties>();
shared_lock.unlock(); auto props = client_properties_[remote_id];
props->local_id_ = "C-" + std::string(client_id_);
props->remote_id_ = remote_id;
memcpy(&props->params_, &params_, sizeof(Params));
props->params_.user_id = props->local_id_.c_str();
props->peer_ = CreatePeer(&props->params_);
AddAudioStream(props->peer_, props->audio_label_.c_str());
AddDataStream(props->peer_, props->data_label_.c_str());
if (!exists) { if (props->peer_) {
std::unique_lock unique_lock(client_properties_mutex_); LOG_INFO("[{}] Create peer instance successful", props->local_id_);
if (client_properties_.find(remote_id) == client_properties_.end()) { Init(props->peer_);
client_properties_[remote_id] = LOG_INFO("[{}] Peer init finish", props->local_id_);
std::make_shared<SubStreamWindowProperties>(); } else {
auto props = client_properties_[remote_id]; LOG_INFO("Create peer [{}] instance failed", props->local_id_);
props->local_id_ = "C-" + std::string(client_id_);
props->remote_id_ = remote_id;
memcpy(&props->params_, &params_, sizeof(Params));
props->params_.user_id = props->local_id_.c_str();
props->peer_ = CreatePeer(&props->params_);
if (!props->peer_) {
LOG_INFO("Create peer [{}] instance failed", props->local_id_);
return -1;
}
for (auto& display_info : display_info_list_) {
AddVideoStream(props->peer_, display_info.name.c_str());
}
AddAudioStream(props->peer_, props->audio_label_.c_str());
AddDataStream(props->peer_, props->data_label_.c_str());
if (props->peer_) {
LOG_INFO("[{}] Create peer instance successful", props->local_id_);
Init(props->peer_);
LOG_INFO("[{}] Peer init finish", props->local_id_);
} else {
LOG_INFO("Create peer [{}] instance failed", props->local_id_);
}
props->connection_status_ = ConnectionStatus::Connecting;
} }
unique_lock.unlock();
}
props->connection_status_ = ConnectionStatus::Connecting;
}
int ret = -1; int ret = -1;
std::shared_lock read_lock(client_properties_mutex_);
auto props = client_properties_[remote_id]; auto props = client_properties_[remote_id];
if (!props->connection_established_) { if (!props->connection_established_) {
props->remember_password_ = remember_password; props->remember_password_ = remember_password;
@@ -201,18 +168,14 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
} }
std::string remote_id_with_pwd = remote_id + "@" + password; std::string remote_id_with_pwd = remote_id + "@" + password;
if (props->peer_) { ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str());
ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str()); if (0 == ret) {
if (0 == ret) { props->rejoin_ = false;
props->rejoin_ = false; } else {
} else { props->rejoin_ = true;
props->rejoin_ = true; need_to_rejoin_ = true;
need_to_rejoin_ = true;
}
} }
} }
read_lock.unlock();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -16,14 +16,11 @@
#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));
@@ -168,7 +165,7 @@ SDL_HitTestResult Render::HitTestCallback(SDL_Window* window,
return SDL_HITTEST_NORMAL; return SDL_HITTEST_NORMAL;
} }
Render::Render() : last_rejoin_check_time_(std::chrono::steady_clock::now()) {} Render::Render() {}
Render::~Render() {} Render::~Render() {}
@@ -259,10 +256,6 @@ 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();
enable_autostart_ = config_center_->IsEnableAutostart();
enable_daemon_ = config_center_->IsEnableDaemon();
enable_minimize_to_tray_ = config_center_->IsMinimizeToTray();
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_;
@@ -270,9 +263,6 @@ 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_;
enable_autostart_last_ = enable_autostart_;
enable_minimize_to_tray_last_ = enable_minimize_to_tray_;
LOG_INFO("Load settings from cache file"); LOG_INFO("Load settings from cache file");
@@ -330,9 +320,8 @@ int Render::ScreenCapturerInit() {
int Render::StartScreenCapturer() { int Render::StartScreenCapturer() {
if (screen_capturer_) { if (screen_capturer_) {
LOG_INFO("Start screen capturer, show cursor: {}", show_cursor_); LOG_INFO("Start screen capturer");
screen_capturer_->Start();
screen_capturer_->Start(show_cursor_);
} }
return 0; return 0;
@@ -502,8 +491,6 @@ 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;
@@ -575,14 +562,6 @@ 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;
@@ -736,51 +715,6 @@ int Render::SetupFontAndStyle() {
static const ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; static const ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
io.Fonts->AddFontFromMemoryTTF(fa_solid_900_ttf, fa_solid_900_ttf_len, 30.0f, io.Fonts->AddFontFromMemoryTTF(fa_solid_900_ttf, fa_solid_900_ttf_len, 30.0f,
&config, icon_ranges); &config, icon_ranges);
// Load system Chinese font as fallback
config.MergeMode = false;
config.FontDataOwnedByAtlas = false;
system_chinese_font_ = nullptr;
#if defined(_WIN32)
// Windows: Try Microsoft YaHei (微软雅黑) first, then SimSun (宋体)
const char* font_paths[] = {"C:/Windows/Fonts/msyh.ttc",
"C:/Windows/Fonts/msyhbd.ttc",
"C:/Windows/Fonts/simsun.ttc", nullptr};
#elif defined(__APPLE__)
// macOS: Try PingFang SC first, then STHeiti
const char* font_paths[] = {"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/STHeiti Light.ttc",
"/System/Library/Fonts/STHeiti Medium.ttc",
nullptr};
#else
// Linux: Try common Chinese fonts
const char* font_paths[] = {
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
"/usr/share/fonts/truetype/arphic/uming.ttc",
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", nullptr};
#endif
for (int i = 0; font_paths[i] != nullptr; i++) {
std::ifstream font_file(font_paths[i], std::ios::binary);
if (font_file.good()) {
font_file.close();
system_chinese_font_ = io.Fonts->AddFontFromFileTTF(
font_paths[i], 32.0f, &config, io.Fonts->GetGlyphRangesChineseFull());
if (system_chinese_font_ != nullptr) {
LOG_INFO("Loaded system Chinese font: {}", font_paths[i]);
break;
}
}
}
// If no system font found, use default font
if (system_chinese_font_ == nullptr) {
system_chinese_font_ = io.Fonts->AddFontDefault(&config);
LOG_WARN("System Chinese font not found, using default font");
}
io.Fonts->Build(); io.Fonts->Build();
ImGui::StyleColorsLight(); ImGui::StyleColorsLight();
@@ -893,14 +827,6 @@ int Render::DrawMainWindow() {
MainWindow(); MainWindow();
UpdateNotificationWindow();
#ifdef __APPLE__
if (show_request_permission_window_) {
RequestPermissionWindow();
}
#endif
ImGui::End(); ImGui::End();
// Rendering // Rendering
@@ -947,7 +873,6 @@ int Render::DrawStreamWindow() {
ImGui::Render(); ImGui::Render();
SDL_RenderClear(stream_renderer_); SDL_RenderClear(stream_renderer_);
std::shared_lock lock(client_properties_mutex_);
for (auto& it : client_properties_) { for (auto& it : client_properties_) {
auto props = it.second; auto props = it.second;
if (props->tab_selected_) { if (props->tab_selected_) {
@@ -967,26 +892,6 @@ int Render::DrawStreamWindow() {
} }
int Render::Run() { int Render::Run() {
latest_version_info_ = CheckUpdate();
if (!latest_version_info_.empty() &&
latest_version_info_.contains("version") &&
latest_version_info_["version"].is_string()) {
latest_version_ = latest_version_info_["version"];
if (latest_version_info_.contains("releaseNotes") &&
latest_version_info_["releaseNotes"].is_string()) {
release_notes_ = latest_version_info_["releaseNotes"];
} else {
release_notes_ = "";
}
update_available_ = IsNewerVersion(CROSSDESK_VERSION, latest_version_);
if (update_available_) {
show_update_notification_window_ = true;
}
} else {
latest_version_ = "";
update_available_ = false;
}
path_manager_ = std::make_unique<PathManager>("CrossDesk"); path_manager_ = std::make_unique<PathManager>("CrossDesk");
if (path_manager_) { if (path_manager_) {
cert_path_ = cert_path_ =
@@ -1012,8 +917,6 @@ int Render::Run() {
} }
InitializeLogger(); InitializeLogger();
LOG_INFO("CrossDesk version: {}", CROSSDESK_VERSION);
InitializeSettings(); InitializeSettings();
InitializeSDL(); InitializeSDL();
InitializeModules(); InitializeModules();
@@ -1148,9 +1051,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::string msg = remote_action.to_json(); std::vector<char> serialized = SerializeRemoteAction(remote_action);
int ret = int ret = SendDataFrame(peer_, serialized.data(), serialized.size(),
SendDataFrame(peer_, msg.data(), msg.size(), data_label_.c_str()); 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;
@@ -1276,26 +1179,16 @@ 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_);
} }
{ for (auto& it : client_properties_) {
std::shared_lock lock(client_properties_mutex_); auto props = it.second;
for (auto& it : client_properties_) { CleanupPeer(props);
auto props = it.second;
CleanupPeer(props);
}
} }
{ client_properties_.clear();
std::unique_lock lock(client_properties_mutex_);
client_properties_.clear();
}
} }
void Render::CleanSubStreamWindowProperties( void Render::CleanSubStreamWindowProperties(
@@ -1312,7 +1205,6 @@ void Render::CleanSubStreamWindowProperties(
} }
void Render::UpdateRenderRect() { void Render::UpdateRenderRect() {
std::shared_lock lock(client_properties_mutex_);
for (auto& [_, props] : client_properties_) { for (auto& [_, props] : client_properties_) {
if (!props->reset_control_bar_pos_) { if (!props->reset_control_bar_pos_) {
props->mouse_diff_control_bar_pos_x_ = 0; props->mouse_diff_control_bar_pos_x_ = 0;
@@ -1387,47 +1279,39 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
DestroyStreamWindow(); DestroyStreamWindow();
DestroyStreamWindowContext(); DestroyStreamWindowContext();
{ for (auto& [host_name, props] : client_properties_) {
std::shared_lock lock(client_properties_mutex_); thumbnail_->SaveToThumbnail(
for (auto& [host_name, props] : client_properties_) { (char*)props->dst_buffer_, props->video_width_,
thumbnail_->SaveToThumbnail( props->video_height_, host_name, props->remote_host_name_,
(char*)props->dst_buffer_, props->video_width_, props->remember_password_ ? props->remote_password_ : "");
props->video_height_, host_name, props->remote_host_name_,
props->remember_password_ ? props->remote_password_ : "");
if (props->peer_) { if (props->peer_) {
std::string client_id = (host_name == client_id_) std::string client_id = (host_name == client_id_)
? "C-" + std::string(client_id_) ? "C-" + std::string(client_id_)
: client_id_; : client_id_;
LOG_INFO("[{}] Leave connection [{}]", client_id, host_name); LOG_INFO("[{}] Leave connection [{}]", client_id, host_name);
LeaveConnection(props->peer_, host_name.c_str()); LeaveConnection(props->peer_, host_name.c_str());
LOG_INFO("Destroy peer [{}]", client_id); LOG_INFO("Destroy peer [{}]", client_id);
DestroyPeer(&props->peer_); DestroyPeer(&props->peer_);
}
props->streaming_ = false;
props->remember_password_ = false;
props->connection_established_ = false;
props->audio_capture_button_pressed_ = false;
memset(&props->net_traffic_stats_, 0,
sizeof(props->net_traffic_stats_));
SDL_SetWindowFullscreen(main_window_, false);
SDL_FlushEvents(STREAM_REFRESH_EVENT, STREAM_REFRESH_EVENT);
memset(audio_buffer_, 0, 720);
} }
}
{ props->streaming_ = false;
std::unique_lock lock(client_properties_mutex_); props->remember_password_ = false;
client_properties_.clear(); props->connection_established_ = false;
props->audio_capture_button_pressed_ = false;
memset(&props->net_traffic_stats_, 0,
sizeof(props->net_traffic_stats_));
SDL_SetWindowFullscreen(main_window_, false);
SDL_FlushEvents(STREAM_REFRESH_EVENT, STREAM_REFRESH_EVENT);
memset(audio_buffer_, 0, 720);
} }
client_properties_.clear();
rejoin_ = false; rejoin_ = false;
is_client_mode_ = false; is_client_mode_ = false;
reload_recent_connections_ = true; reload_recent_connections_ = true;
fullscreen_button_pressed_ = false; fullscreen_button_pressed_ = false;
start_keyboard_capturer_ = false;
just_created_ = false; just_created_ = false;
recent_connection_image_save_time_ = SDL_GetTicks(); recent_connection_image_save_time_ = SDL_GetTicks();
} else { } else {
@@ -1547,5 +1431,4 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
} }
break; break;
} }
} }
} // namespace crossdesk

View File

@@ -13,9 +13,7 @@
#include <chrono> #include <chrono>
#include <fstream> #include <fstream>
#include <mutex> #include <mutex>
#include <nlohmann/json.hpp>
#include <optional> #include <optional>
#include <shared_mutex>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -31,12 +29,10 @@
#include "screen_capturer_factory.h" #include "screen_capturer_factory.h"
#include "speaker_capturer_factory.h" #include "speaker_capturer_factory.h"
#include "thumbnail.h" #include "thumbnail.h"
#if _WIN32 #if _WIN32
#include "win_tray.h" #include "win_tray.h"
#endif #endif
namespace crossdesk {
class Render { class Render {
public: public:
struct SubStreamWindowProperties { struct SubStreamWindowProperties {
@@ -52,10 +48,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_ = true; bool mouse_control_button_pressed_ = false;
bool mouse_controller_is_started_ = false; bool mouse_controller_is_started_ = false;
bool audio_capture_button_pressed_ = true; bool audio_capture_button_pressed_ = false;
bool control_mouse_ = true; bool control_mouse_ = false;
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;
@@ -154,7 +150,6 @@ class Render {
int CreateStreamRenderWindow(); int CreateStreamRenderWindow();
int TitleBar(bool main_window); int TitleBar(bool main_window);
int MainWindow(); int MainWindow();
int UpdateNotificationWindow();
int StreamWindow(); int StreamWindow();
int LocalWindow(); int LocalWindow();
int RemoteWindow(); int RemoteWindow();
@@ -169,8 +164,6 @@ class Render {
bool ConnectionStatusWindow( bool ConnectionStatusWindow(
std::shared_ptr<SubStreamWindowProperties>& props); std::shared_ptr<SubStreamWindowProperties>& props);
int ShowRecentConnections(); int ShowRecentConnections();
void Hyperlink(const std::string& label, const std::string& url,
const float window_width);
private: private:
int ConnectTo(const std::string& remote_id, const char* password, int ConnectTo(const std::string& remote_id, const char* password,
@@ -190,14 +183,6 @@ class Render {
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props); int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
void DrawConnectionStatusText( void DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties>& props); std::shared_ptr<SubStreamWindowProperties>& props);
#ifdef __APPLE__
int RequestPermissionWindow();
bool CheckScreenRecordingPermission();
bool CheckAccessibilityPermission();
void OpenScreenRecordingPreferences();
void OpenAccessibilityPreferences();
bool DrawToggleSwitch(const char* id, bool active, bool enabled);
#endif
public: public:
static void OnReceiveVideoBufferCb(const XVideoFrame* video_frame, static void OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
@@ -314,7 +299,6 @@ class Render {
SDL_Window* main_window_ = nullptr; SDL_Window* main_window_ = nullptr;
SDL_Renderer* main_renderer_ = nullptr; SDL_Renderer* main_renderer_ = nullptr;
ImGuiContext* main_ctx_ = nullptr; ImGuiContext* main_ctx_ = nullptr;
ImFont* system_chinese_font_ = nullptr; // System Chinese font for fallback
bool exit_ = false; bool exit_ = false;
const int sdl_refresh_ms_ = 16; // ~60 FPS const int sdl_refresh_ms_ = 16; // ~60 FPS
#if _WIN32 #if _WIN32
@@ -322,18 +306,11 @@ class Render {
#endif #endif
// main window properties // main window properties
nlohmann::json latest_version_info_ = nlohmann::json{};
bool update_available_ = false;
std::string latest_version_ = "";
std::string release_notes_ = "";
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_speaker_capturer_ = false; bool start_keyboard_capturer_ = false;
bool speaker_capturer_is_started_ = false;
bool start_keyboard_capturer_ = true;
bool show_cursor_ = false;
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;
@@ -366,8 +343,6 @@ class Render {
float notification_window_height_ = 80; float notification_window_height_ = 80;
float about_window_width_ = 300; float about_window_width_ = 300;
float about_window_height_ = 170; float about_window_height_ = 170;
float update_notification_window_width_ = 400;
float update_notification_window_height_ = 320;
int screen_width_ = 1280; int screen_width_ = 1280;
int screen_height_ = 720; int screen_height_ = 720;
int selected_display_ = 0; int selected_display_ = 0;
@@ -381,7 +356,6 @@ class Render {
int audio_len_ = 0; int audio_len_ = 0;
bool audio_buffer_fresh_ = false; bool audio_buffer_fresh_ = false;
bool need_to_rejoin_ = false; bool need_to_rejoin_ = false;
std::chrono::steady_clock::time_point last_rejoin_check_time_;
bool just_created_ = false; bool just_created_ = false;
std::string controlled_remote_id_ = ""; std::string controlled_remote_id_ = "";
std::string focused_remote_id_ = ""; std::string focused_remote_id_ = "";
@@ -424,7 +398,6 @@ class Render {
bool show_about_window_ = false; bool show_about_window_ = false;
bool show_connection_status_window_ = false; bool show_connection_status_window_ = false;
bool show_reset_password_window_ = false; bool show_reset_password_window_ = false;
bool show_update_notification_window_ = false;
bool fullscreen_button_pressed_ = false; bool fullscreen_button_pressed_ = false;
bool focus_on_input_widget_ = true; bool focus_on_input_widget_ = true;
bool is_client_mode_ = false; bool is_client_mode_ = false;
@@ -456,41 +429,28 @@ 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;
#ifdef __APPLE__
bool show_request_permission_window_ = true;
#endif
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_ = 1; int video_frame_rate_button_value_ = 0;
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_ = true; 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 coturn_server_port_[6] = "3478";
char cert_file_path_[256] = ""; char cert_file_path_[256] = "";
bool enable_self_hosted_ = false; bool enable_self_hosted_server_ = 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_ = true; bool enable_turn_last_ = false;
bool enable_srtp_last_ = false; bool enable_srtp_last_ = false;
bool enable_self_hosted_last_ = false;
bool enable_autostart_ = false;
bool enable_autostart_last_ = false;
bool enable_daemon_ = false;
bool enable_daemon_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_self_[256] = ""; char signal_server_ip_self_[256] = "";
@@ -506,12 +466,8 @@ class Render {
/* ------ sub stream window property start ------ */ /* ------ sub stream window property start ------ */
std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>> std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>>
client_properties_; client_properties_;
std::shared_mutex client_properties_mutex_;
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

@@ -1,5 +1,3 @@
#include <cmath>
#include "device_controller.h" #include "device_controller.h"
#include "localization.h" #include "localization.h"
#include "platform.h" #include "platform.h"
@@ -8,7 +6,10 @@
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
namespace crossdesk { #ifdef DESK_PORT_DEBUG
#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;
@@ -21,16 +22,12 @@ int Render::SendKeyCommand(int key_code, bool is_down) {
remote_action.k.key_value = key_code; remote_action.k.key_value = key_code;
if (!controlled_remote_id_.empty()) { if (!controlled_remote_id_.empty()) {
std::shared_lock lock(client_properties_mutex_);
if (client_properties_.find(controlled_remote_id_) != if (client_properties_.find(controlled_remote_id_) !=
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) {
std::string msg = remote_action.to_json(); SendDataFrame(props->peer_, (const char*)&remote_action,
if (props->peer_) { sizeof(remote_action), props->data_label_.c_str());
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
} }
} }
} }
@@ -45,7 +42,6 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
float ratio_x, ratio_y = 0; float ratio_x, ratio_y = 0;
RemoteAction remote_action; RemoteAction remote_action;
std::shared_lock lock(client_properties_mutex_);
for (auto& it : client_properties_) { for (auto& it : client_properties_) {
auto props = it.second; auto props = it.second;
if (!props->control_mouse_) { if (!props->control_mouse_) {
@@ -96,12 +92,8 @@ 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,
std::string msg = remote_action.to_json(); sizeof(remote_action), props->data_label_.c_str());
if (props->peer_) {
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 +
@@ -109,46 +101,32 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
last_mouse_event.button.y >= props->stream_render_rect_.y && last_mouse_event.button.y >= props->stream_render_rect_.y &&
last_mouse_event.button.y <= props->stream_render_rect_.y + last_mouse_event.button.y <= props->stream_render_rect_.y +
props->stream_render_rect_.h) { props->stream_render_rect_.h) {
float scroll_x = event.wheel.x; int scroll_x = event.wheel.x;
float scroll_y = event.wheel.y; int scroll_y = event.wheel.y;
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
scroll_x = -scroll_x; scroll_x = -scroll_x;
scroll_y = -scroll_y; scroll_y = -scroll_y;
} }
remote_action.type = ControlType::mouse; remote_action.type = ControlType::mouse;
if (scroll_x == 0) {
auto roundUp = [](float value) -> int {
if (value > 0) {
return static_cast<int>(std::ceil(value));
} else if (value < 0) {
return static_cast<int>(std::floor(value));
}
return 0;
};
if (std::abs(scroll_y) >= std::abs(scroll_x)) {
remote_action.m.flag = MouseFlag::wheel_vertical; remote_action.m.flag = MouseFlag::wheel_vertical;
remote_action.m.s = roundUp(scroll_y); remote_action.m.s = scroll_y;
} else { } else if (scroll_y == 0) {
remote_action.m.flag = MouseFlag::wheel_horizontal; remote_action.m.flag = MouseFlag::wheel_horizontal;
remote_action.m.s = roundUp(scroll_x); remote_action.m.s = scroll_x;
} }
render_width = props->stream_render_rect_.w; render_width = props->stream_render_rect_.w;
render_height = props->stream_render_rect_.h; render_height = props->stream_render_rect_.h;
remote_action.m.x = remote_action.m.x =
(float)(last_mouse_event.button.x - props->stream_render_rect_.x) / (float)(event.button.x - props->stream_render_rect_.x) / render_width;
render_width;
remote_action.m.y = remote_action.m.y =
(float)(last_mouse_event.button.y - props->stream_render_rect_.y) / (float)(event.button.y - props->stream_render_rect_.y) /
render_height; render_height;
std::string msg = remote_action.to_json(); SendDataFrame(props->peer_, (const char*)&remote_action,
if (props->peer_) { sizeof(remote_action), props->data_label_.c_str());
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
} }
} }
@@ -162,14 +140,11 @@ void Render::SdlCaptureAudioIn(void* userdata, Uint8* stream, int len) {
} }
if (1) { if (1) {
std::shared_lock lock(render->client_properties_mutex_); for (auto it : render->client_properties_) {
for (const auto& it : render->client_properties_) {
auto props = it.second; auto props = it.second;
if (props->connection_status_ == ConnectionStatus::Connected) { if (props->connection_status_ == ConnectionStatus::Connected) {
if (props->peer_) { SendAudioFrame(props->peer_, (const char*)stream, len,
SendAudioFrame(props->peer_, (const char*)stream, len, render->audio_label_.c_str());
render->audio_label_.c_str());
}
} }
} }
@@ -218,7 +193,6 @@ void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
} }
std::string remote_id(user_id, user_id_size); std::string remote_id(user_id, user_id_size);
std::shared_lock lock(render->client_properties_mutex_);
if (render->client_properties_.find(remote_id) == if (render->client_properties_.find(remote_id) ==
render->client_properties_.end()) { render->client_properties_.end()) {
return; return;
@@ -303,55 +277,64 @@ 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);
std::shared_lock lock(render->client_properties_mutex_);
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;
if (remote_action.type == ControlType::host_infomation && RemoteAction host_info;
props->remote_host_name_.empty()) { if (DeserializeRemoteAction(data, size, host_info)) {
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(remote_action); FreeRemoteAction(host_info);
} else { } else {
// remote // remote
if (remote_action.type == ControlType::mouse && render->mouse_controller_) { if (ControlType::mouse == remote_action.type && render->mouse_controller_) {
render->mouse_controller_->SendMouseCommand(remote_action, render->mouse_controller_->SendMouseCommand(remote_action,
render->selected_display_); render->selected_display_);
} else if (remote_action.type == ControlType::audio_capture) { } else if (ControlType::audio_capture == remote_action.type) {
if (remote_action.a && !render->start_speaker_capturer_) if (remote_action.a) {
render->StartSpeakerCapturer(); render->StartSpeakerCapturer();
else if (!remote_action.a && render->start_speaker_capturer_) render->audio_capture_ = true;
} else {
render->StopSpeakerCapturer(); render->StopSpeakerCapturer();
} else if (remote_action.type == ControlType::keyboard && render->audio_capture_ = false;
}
} 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 (remote_action.type == ControlType::display_id && } else if (ControlType::display_id == remote_action.type) {
render->screen_capturer_) { if (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);
}
} }
} }
} }
@@ -386,7 +369,6 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
} }
std::string remote_id(client_id.begin() + 2, client_id.end()); std::string remote_id(client_id.begin() + 2, client_id.end());
std::shared_lock lock(render->client_properties_mutex_);
if (render->client_properties_.find(remote_id) == if (render->client_properties_.find(remote_id) ==
render->client_properties_.end()) { render->client_properties_.end()) {
return; return;
@@ -416,7 +398,6 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
if (!render) return; if (!render) return;
std::string remote_id(user_id, user_id_size); std::string remote_id(user_id, user_id_size);
std::shared_lock lock(render->client_properties_mutex_);
auto it = render->client_properties_.find(remote_id); auto it = render->client_properties_.find(remote_id);
auto props = (it != render->client_properties_.end()) ? it->second : nullptr; auto props = (it != render->client_properties_.end()) ? it->second : nullptr;
@@ -426,7 +407,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;
@@ -437,22 +418,24 @@ 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_ && props->stream_texture_) { if (props->dst_buffer_) {
memset(props->dst_buffer_, 0, props->dst_buffer_capacity_); memset(props->dst_buffer_, 0, props->dst_buffer_capacity_);
SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_, SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_,
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_) {
@@ -462,73 +445,37 @@ 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_speaker_capturer_ = true;
// #ifdef CROSSDESK_DEBUG
// render->start_mouse_controller_ = false;
// render->start_keyboard_capturer_ = false;
// #else
render->start_mouse_controller_ = true; render->start_mouse_controller_ = true;
// #endif
if (std::all_of(render->connection_status_.begin(),
render->connection_status_.end(), [](const auto& kv) {
return kv.first.find("web") != std::string::npos;
})) {
render->show_cursor_ = true;
}
break; break;
} case ConnectionStatus::Closed:
case ConnectionStatus::Closed: { render->start_screen_capturer_ = false;
if (std::all_of(render->connection_status_.begin(), render->start_mouse_controller_ = false;
render->connection_status_.end(), [](const auto& kv) { render->start_keyboard_capturer_ = false;
return kv.second == ConnectionStatus::Closed || render->need_to_send_host_info_ = false;
kv.second == ConnectionStatus::Failed || if (props) props->connection_established_ = false;
kv.second == ConnectionStatus::Disconnected; if (render->audio_capture_) {
})) { render->StopSpeakerCapturer();
render->start_screen_capturer_ = false; render->audio_capture_ = 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);
} }
if (std::all_of(render->connection_status_.begin(),
render->connection_status_.end(), [](const auto& kv) {
return kv.first.find("web") == std::string::npos;
})) {
render->show_cursor_ = false;
}
break; break;
}
default: default:
break; break;
} }
@@ -577,7 +524,6 @@ void Render::NetStatusReport(const char* client_id, size_t client_id_size,
} }
std::string remote_id(user_id, user_id_size); std::string remote_id(user_id, user_id_size);
std::shared_lock lock(render->client_properties_mutex_);
if (render->client_properties_.find(remote_id) == if (render->client_properties_.find(remote_id) ==
render->client_properties_.end()) { render->client_properties_.end()) {
return; return;
@@ -596,5 +542,4 @@ 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,8 +3,6 @@
#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;
@@ -68,9 +66,8 @@ 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) {
std::string msg = remote_action.to_json(); SendDataFrame(props->peer_, (const char*)&remote_action,
SendDataFrame(props->peer_, msg.c_str(), msg.size(), sizeof(remote_action), props->data_label_.c_str());
props->data_label_.c_str());
} }
} }
props->display_selectable_hovered_ = ImGui::IsWindowHovered(); props->display_selectable_hovered_ = ImGui::IsWindowHovered();
@@ -143,9 +140,8 @@ 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_;
std::string msg = remote_action.to_json(); SendDataFrame(props->peer_, (const char*)&remote_action,
SendDataFrame(props->peer_, msg.c_str(), msg.size(), sizeof(remote_action), props->data_label_.c_str());
props->data_label_.c_str());
} }
} }
if (!props->audio_capture_button_pressed_) { if (!props->audio_capture_button_pressed_) {
@@ -328,4 +324,3 @@ int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -1,8 +1,6 @@
#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;
@@ -37,5 +35,4 @@ int Render::StatusBar() {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::EndChild(); ImGui::EndChild();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,9 +3,6 @@
#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));
@@ -40,59 +37,13 @@ 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(
show_new_version_icon_in_menu_ = false; localization::about[localization_language_index_].c_str())) {
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),
@@ -104,33 +55,6 @@ 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();
@@ -247,5 +171,4 @@ int Render::TitleBar(bool main_window) {
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleColor(); ImGui::PopStyleColor();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -4,8 +4,6 @@
#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) {
@@ -111,5 +109,4 @@ bool WinTray::HandleTrayMessage(MSG* msg) {
} }
} }
return true; return true;
} }
} // namespace crossdesk

View File

@@ -14,8 +14,6 @@
#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,
@@ -34,5 +32,5 @@ class WinTray {
int language_index_; int language_index_;
NOTIFYICONDATA nid_; NOTIFYICONDATA nid_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -1,53 +1,18 @@
#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 Render::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);
@@ -69,30 +34,17 @@ int Render::AboutWindow() {
#endif #endif
std::string text = localization::version[localization_language_index_] + std::string text = localization::version[localization_language_index_] +
": CrossDesk " + version; ": CrossDesk v" + 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;
@@ -106,5 +58,4 @@ int Render::AboutWindow() {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,11 +3,9 @@
#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_) /
@@ -170,5 +168,4 @@ bool Render::ConnectionStatusWindow(
ImGui::PopStyleVar(); ImGui::PopStyleVar();
return ret_flag; return ret_flag;
} }
} // namespace crossdesk

View File

@@ -1,9 +1,7 @@
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
namespace crossdesk { int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) {
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_) {
@@ -222,5 +220,4 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -3,8 +3,6 @@
#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_) {
@@ -82,11 +80,11 @@ int Render::SettingWindow() {
{ {
const char* video_quality_items[] = { const char* video_quality_items[] = {
localization::video_quality_low[localization_language_index_] localization::video_quality_high[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_high[localization_language_index_] localization::video_quality_low[localization_language_index_]
.c_str()}; .c_str()};
settings_items_offset += settings_items_padding; settings_items_offset += settings_items_padding;
@@ -158,7 +156,6 @@ int Render::SettingWindow() {
video_encode_format_items, IM_ARRAYSIZE(video_encode_format_items)); video_encode_format_items, IM_ARRAYSIZE(video_encode_format_items));
} }
#if USE_CUDA && !defined(__aarch64__) && !defined(__arm__)
ImGui::Separator(); ImGui::Separator();
{ {
@@ -177,7 +174,6 @@ int Render::SettingWindow() {
ImGui::Checkbox("##enable_hardware_video_codec", ImGui::Checkbox("##enable_hardware_video_codec",
&enable_hardware_video_codec_); &enable_hardware_video_codec_);
} }
#endif
ImGui::Separator(); ImGui::Separator();
@@ -233,54 +229,8 @@ 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", &enable_self_hosted_); ImGui::Checkbox("##enable_self_hosted_server",
} &enable_self_hosted_server_);
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text("%s",
localization::enable_autostart[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(ENABLE_AUTOSTART_PADDING_CN);
} else {
ImGui::SetCursorPosX(ENABLE_AUTOSTART_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_autostart_", &enable_autostart_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text(
"%s",
localization::enable_daemon[localization_language_index_].c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(ENABLE_DAEMON_PADDING_CN);
} else {
ImGui::SetCursorPosX(ENABLE_DAEMON_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_daemon_", &enable_daemon_);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text("%s", localization::takes_effect_after_restart
[localization_language_index_]
.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
} }
#if _WIN32 #if _WIN32
ImGui::Separator(); ImGui::Separator();
@@ -337,23 +287,14 @@ 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::LOW); config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
} 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::HIGH); config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
} }
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(
@@ -389,35 +330,11 @@ int Render::SettingWindow() {
} }
enable_srtp_last_ = enable_srtp_; enable_srtp_last_ = enable_srtp_;
if (enable_self_hosted_) { if (enable_self_hosted_server_) {
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_;
if (enable_autostart_) {
config_center_->SetAutostart(true);
} else {
config_center_->SetAutostart(false);
}
enable_autostart_last_ = enable_autostart_;
if (enable_daemon_) {
config_center_->SetDaemon(true);
} else {
config_center_->SetDaemon(false);
}
enable_daemon_last_ = enable_daemon_;
#if _WIN32
if (enable_minimize_to_tray_) {
config_center_->SetMinimizeToTray(true);
} else {
config_center_->SetMinimizeToTray(false);
}
enable_minimize_to_tray_last_ = enable_minimize_to_tray_;
#endif
settings_window_pos_reset_ = true; settings_window_pos_reset_ = true;
@@ -447,11 +364,6 @@ 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_ =
@@ -478,5 +390,4 @@ int Render::SettingWindow() {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -2,8 +2,6 @@
#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));
@@ -32,7 +30,6 @@ int Render::MainWindow() {
StatusBar(); StatusBar();
if (show_connection_status_window_) { if (show_connection_status_window_) {
std::unique_lock lock(client_properties_mutex_);
for (auto it = client_properties_.begin(); for (auto it = client_properties_.begin();
it != client_properties_.end();) { it != client_properties_.end();) {
auto& props = it->second; auto& props = it->second;
@@ -50,4 +47,3 @@ int Render::MainWindow() {
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -1,205 +0,0 @@
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
#include <ApplicationServices/ApplicationServices.h>
#include <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#include <unistd.h>
#include <cstdlib>
namespace crossdesk {
bool Render::DrawToggleSwitch(const char* id, bool active, bool enabled) {
const float TRACK_HEIGHT = ImGui::GetFrameHeight();
const float TRACK_WIDTH = TRACK_HEIGHT * 1.8f;
const float TRACK_RADIUS = TRACK_HEIGHT * 0.5f;
const float KNOB_PADDING = 2.0f;
const float KNOB_HEIGHT = TRACK_HEIGHT - 4.0f;
const float KNOB_WIDTH = KNOB_HEIGHT * 1.2f;
const float KNOB_RADIUS = KNOB_HEIGHT * 0.5f;
const float DISABLED_ALPHA = 0.6f;
const float KNOB_ALPHA_DISABLED = 0.9f;
const ImVec4 COLOR_ACTIVE = ImVec4(0.0f, 0.0f, 1.0f, 1.0f);
const ImVec4 COLOR_ACTIVE_HOVER = ImVec4(0.26f, 0.59f, 0.98f, 1.0f);
const ImVec4 COLOR_INACTIVE = ImVec4(0.60f, 0.60f, 0.60f, 1.0f);
const ImVec4 COLOR_INACTIVE_HOVER = ImVec4(0.70f, 0.70f, 0.70f, 1.0f);
const ImVec4 COLOR_KNOB = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 track_pos = ImGui::GetCursorScreenPos();
ImGui::InvisibleButton(id, ImVec2(TRACK_WIDTH, TRACK_HEIGHT));
bool hovered = ImGui::IsItemHovered();
bool clicked = ImGui::IsItemClicked() && enabled;
ImVec4 track_color = active ? (hovered && enabled ? COLOR_ACTIVE_HOVER : COLOR_ACTIVE)
: (hovered && enabled ? COLOR_INACTIVE_HOVER : COLOR_INACTIVE);
if (!enabled) {
track_color.w *= DISABLED_ALPHA;
}
ImVec2 track_min = ImVec2(track_pos.x, track_pos.y + 0.5f);
ImVec2 track_max = ImVec2(track_pos.x + TRACK_WIDTH, track_pos.y + TRACK_HEIGHT - 0.5f);
draw_list->AddRectFilled(track_min, track_max, ImGui::GetColorU32(track_color), TRACK_RADIUS);
float knob_position = active ? 1.0f : 0.0f;
float knob_min_x = track_pos.x + KNOB_PADDING;
float knob_max_x = track_pos.x + TRACK_WIDTH - KNOB_WIDTH - KNOB_PADDING;
float knob_x = knob_min_x + knob_position * (knob_max_x - knob_min_x);
float knob_y = track_pos.y + (TRACK_HEIGHT - KNOB_HEIGHT) * 0.5f;
ImVec4 knob_color = COLOR_KNOB;
if (!enabled) {
knob_color.w = KNOB_ALPHA_DISABLED;
}
ImVec2 knob_min = ImVec2(knob_x, knob_y);
ImVec2 knob_max = ImVec2(knob_x + KNOB_WIDTH, knob_y + KNOB_HEIGHT);
draw_list->AddRectFilled(knob_min, knob_max, ImGui::GetColorU32(knob_color), KNOB_RADIUS);
return clicked;
}
bool Render::CheckScreenRecordingPermission() {
// CGPreflightScreenCaptureAccess is available on macOS 10.15+
if (@available(macOS 10.15, *)) {
bool granted = CGPreflightScreenCaptureAccess();
return granted;
}
// for older macOS versions, assume permission is granted
return true;
}
bool Render::CheckAccessibilityPermission() {
NSDictionary* options = @{(__bridge id)kAXTrustedCheckOptionPrompt : @NO};
bool trusted = AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options);
return trusted;
}
void Render::OpenAccessibilityPreferences() {
NSDictionary* options = @{(__bridge id)kAXTrustedCheckOptionPrompt : @YES};
AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options);
system("open "
"\"x-apple.systempreferences:com.apple.preference.security?Privacy_"
"Accessibility\"");
}
void Render::OpenScreenRecordingPreferences() {
if (@available(macOS 10.15, *)) {
CGRequestScreenCaptureAccess();
}
system("open "
"\"x-apple.systempreferences:com.apple.preference.security?Privacy_"
"ScreenCapture\"");
}
int Render::RequestPermissionWindow() {
bool screen_recording_granted = CheckScreenRecordingPermission();
bool accessibility_granted = CheckAccessibilityPermission();
show_request_permission_window_ = !screen_recording_granted || !accessibility_granted;
if (!show_request_permission_window_) {
return 0;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
float window_width = localization_language_index_ == 0 ? REQUEST_PERMISSION_WINDOW_WIDTH_CN
: REQUEST_PERMISSION_WINDOW_WIDTH_EN;
float window_height = localization_language_index_ == 0 ? REQUEST_PERMISSION_WINDOW_HEIGHT_CN
: REQUEST_PERMISSION_WINDOW_HEIGHT_EN;
float checkbox_padding = localization_language_index_ == 0
? REQUEST_PERMISSION_WINDOW_CHECKBOX_PADDING_CN
: REQUEST_PERMISSION_WINDOW_CHECKBOX_PADDING_EN;
ImVec2 center_pos = ImVec2((viewport->WorkSize.x - window_width) * 0.5f + viewport->WorkPos.x,
(viewport->WorkSize.y - window_height) * 0.5f + viewport->WorkPos.y);
ImGui::SetNextWindowPos(center_pos, ImGuiCond_Once);
ImGui::SetNextWindowSize(ImVec2(window_width, window_height), ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin(
localization::request_permissions[localization_language_index_].c_str(), nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(0.3f);
// use system font
if (system_chinese_font_ != nullptr) {
ImGui::PushFont(system_chinese_font_);
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetTextLineHeight() + 5.0f);
ImGui::SetCursorPosX(10.0f);
ImGui::TextWrapped(
"%s", localization::permission_required_message[localization_language_index_].c_str());
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
// accessibility permission
ImGui::SetCursorPosX(10.0f);
ImGui::AlignTextToFramePadding();
ImGui::Text("1. %s:",
localization::accessibility_permission[localization_language_index_].c_str());
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
ImGui::SetCursorPosX(checkbox_padding);
if (accessibility_granted) {
DrawToggleSwitch("accessibility_toggle_on", true, false);
} else {
if (DrawToggleSwitch("accessibility_toggle", accessibility_granted, !accessibility_granted)) {
OpenAccessibilityPreferences();
}
}
ImGui::Spacing();
// screen recording permission
ImGui::SetCursorPosX(10.0f);
ImGui::AlignTextToFramePadding();
ImGui::Text("2. %s:",
localization::screen_recording_permission[localization_language_index_].c_str());
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
ImGui::SetCursorPosX(checkbox_padding);
if (screen_recording_granted) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.0f);
DrawToggleSwitch("screen_recording_toggle_on", true, false);
} else {
if (DrawToggleSwitch("screen_recording_toggle", screen_recording_granted,
!screen_recording_granted)) {
OpenScreenRecordingPreferences();
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.45f);
// pop system font
if (system_chinese_font_ != nullptr) {
ImGui::PopFont();
}
ImGui::End();
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleVar(4);
ImGui::PopStyleColor();
return 0;
}
} // namespace crossdesk

View File

@@ -10,8 +10,6 @@
#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
@@ -320,5 +318,4 @@ int Render::SelfHostedServerWindow() {
} }
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -2,8 +2,6 @@
#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;
@@ -32,15 +30,12 @@ void Render::DrawConnectionStatusText(
} }
void Render::CloseTab(decltype(client_properties_)::iterator& it) { void Render::CloseTab(decltype(client_properties_)::iterator& it) {
std::unique_lock lock(client_properties_mutex_); CleanupPeer(it->second);
if (it != client_properties_.end()) { it = client_properties_.erase(it);
CleanupPeer(it->second); if (client_properties_.empty()) {
it = client_properties_.erase(it); SDL_Event event;
if (client_properties_.empty()) { event.type = SDL_EVENT_QUIT;
SDL_Event event; SDL_PushEvent(&event);
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
} }
} }
@@ -82,22 +77,11 @@ int Render::StreamWindow() {
ImGuiTabBarFlags_AutoSelectNewTabs)) { ImGuiTabBarFlags_AutoSelectNewTabs)) {
is_tab_bar_hovered_ = ImGui::IsWindowHovered(); is_tab_bar_hovered_ = ImGui::IsWindowHovered();
std::shared_lock lock(client_properties_mutex_);
for (auto it = client_properties_.begin(); for (auto it = client_properties_.begin();
it != client_properties_.end();) { it != client_properties_.end();) {
auto& props = it->second; auto& props = it->second;
if (!props->tab_opened_) { if (!props->tab_opened_) {
std::string remote_id_to_close = props->remote_id_; CloseTab(it);
lock.unlock();
{
std::unique_lock unique_lock(client_properties_mutex_);
auto close_it = client_properties_.find(remote_id_to_close);
if (close_it != client_properties_.end()) {
CloseTab(close_it);
}
}
lock.lock();
it = client_properties_.begin();
continue; continue;
} }
@@ -136,23 +120,12 @@ int Render::StreamWindow() {
focused_remote_id_ = props->remote_id_; focused_remote_id_ = props->remote_id_;
if (!props->peer_) { if (!props->peer_) {
std::string remote_id_to_erase = props->remote_id_; it = client_properties_.erase(it);
lock.unlock(); if (client_properties_.empty()) {
{ SDL_Event event;
std::unique_lock unique_lock(client_properties_mutex_); event.type = SDL_EVENT_QUIT;
auto erase_it = client_properties_.find(remote_id_to_erase); SDL_PushEvent(&event);
if (erase_it != client_properties_.end()) {
erase_it = client_properties_.erase(erase_it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
}
} }
lock.lock();
it = client_properties_.begin();
continue;
} else { } else {
DrawConnectionStatusText(props); DrawConnectionStatusText(props);
++it; ++it;
@@ -172,22 +145,11 @@ int Render::StreamWindow() {
ImGui::End(); // End TabBar ImGui::End(); // End TabBar
} else { } else {
std::shared_lock lock(client_properties_mutex_);
for (auto it = client_properties_.begin(); for (auto it = client_properties_.begin();
it != client_properties_.end();) { it != client_properties_.end();) {
auto& props = it->second; auto& props = it->second;
if (!props->tab_opened_) { if (!props->tab_opened_) {
std::string remote_id_to_close = props->remote_id_; CloseTab(it);
lock.unlock();
{
std::unique_lock unique_lock(client_properties_mutex_);
auto close_it = client_properties_.find(remote_id_to_close);
if (close_it != client_properties_.end()) {
CloseTab(close_it);
}
}
lock.lock();
it = client_properties_.begin();
continue; continue;
} }
@@ -217,23 +179,12 @@ int Render::StreamWindow() {
if (!props->peer_) { if (!props->peer_) {
fullscreen_button_pressed_ = false; fullscreen_button_pressed_ = false;
SDL_SetWindowFullscreen(stream_window_, false); SDL_SetWindowFullscreen(stream_window_, false);
std::string remote_id_to_erase = props->remote_id_; it = client_properties_.erase(it);
lock.unlock(); if (client_properties_.empty()) {
{ SDL_Event event;
std::unique_lock unique_lock(client_properties_mutex_); event.type = SDL_EVENT_QUIT;
auto erase_it = client_properties_.find(remote_id_to_erase); SDL_PushEvent(&event);
if (erase_it != client_properties_.end()) {
client_properties_.erase(erase_it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
}
} }
lock.lock();
it = client_properties_.begin();
continue;
} else { } else {
DrawConnectionStatusText(props); DrawConnectionStatusText(props);
++it; ++it;
@@ -248,5 +199,4 @@ int Render::StreamWindow() {
ImGui::End(); // End VideoBg ImGui::End(); // End VideoBg
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -1,209 +0,0 @@
#include <algorithm>
#include <cstdlib>
#include <string>
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
std::string CleanMarkdown(const std::string& markdown) {
std::string result = markdown;
// remove # title mark
size_t pos = 0;
while (pos < result.length()) {
if (result[pos] == '\n' || pos == 0) {
size_t line_start = (result[pos] == '\n') ? pos + 1 : pos;
if (line_start < result.length() && result[line_start] == '#') {
size_t hash_end = line_start;
while (hash_end < result.length() &&
(result[hash_end] == '#' || result[hash_end] == ' ')) {
hash_end++;
}
result.erase(line_start, hash_end - line_start);
pos = line_start;
continue;
}
}
pos++;
}
// remove ** bold mark
pos = 0;
while ((pos = result.find("**", pos)) != std::string::npos) {
result.erase(pos, 2);
}
// remove all spaces
result.erase(std::remove(result.begin(), result.end(), ' '), result.end());
// replace . with 、
pos = 0;
while ((pos = result.find('.', pos)) != std::string::npos) {
result.replace(pos, 1, "");
pos += 1; // Move to next position after the replacement
}
return result;
}
int Render::UpdateNotificationWindow() {
if (show_update_notification_window_ && update_available_) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
float window_width = update_notification_window_width_;
float window_height = update_notification_window_height_;
#ifdef __APPLE__
float font_scale = 0.3f;
#else
float font_scale = 0.5f;
#endif
ImGui::SetNextWindowPos(
ImVec2(
(viewport->WorkSize.x - viewport->WorkPos.x - window_width) / 2,
(viewport->WorkSize.y - viewport->WorkPos.y - window_height) / 2),
ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(window_width, window_height));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::Begin(
localization::notification[localization_language_index_].c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar);
if (system_chinese_font_ != nullptr) {
ImGui::PushFont(system_chinese_font_);
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetTextLineHeight() +
5.0f);
// title: new version available
ImGui::SetCursorPosX(window_width * 0.1f);
ImGui::SetWindowFontScale(font_scale + 0.2f);
std::string title =
localization::new_version_available[localization_language_index_] +
": v" + latest_version_;
ImGui::Text("%s", title.c_str());
ImGui::SetWindowFontScale(font_scale);
ImGui::Spacing();
// website link
std::string download_text =
localization::access_website[localization_language_index_] +
"https://crossdesk.cn";
Hyperlink(download_text, "https://crossdesk.cn", window_width);
ImGui::Spacing();
float scrollable_height =
window_height - UPDATE_NOTIFICATION_RESERVED_HEIGHT;
// scrollable content area
ImGui::SetCursorPosX(window_width * 0.05f);
ImGui::BeginChild("ScrollableContent",
ImVec2(window_width * 0.9f, scrollable_height),
ImGuiChildFlags_Border, ImGuiWindowFlags_None);
// set text wrap position to current available width (accounts for
// scrollbar)
float wrap_pos = ImGui::GetContentRegionAvail().x;
ImGui::PushTextWrapPos(wrap_pos);
// release name
if (latest_version_info_.contains("releaseName") &&
latest_version_info_["releaseName"].is_string() &&
!latest_version_info_["releaseName"].empty()) {
ImGui::SetCursorPosX(window_width * 0.05f);
std::string release_name =
latest_version_info_["releaseName"].get<std::string>();
ImGui::TextWrapped("%s", release_name.c_str());
ImGui::Spacing();
}
// release notes
if (!release_notes_.empty()) {
ImGui::SetCursorPosX(window_width * 0.05f);
std::string cleaned_notes = CleanMarkdown(release_notes_);
ImGui::TextWrapped("%s", cleaned_notes.c_str());
ImGui::Spacing();
}
// release date
if (latest_version_info_.contains("releaseDate") &&
latest_version_info_["releaseDate"].is_string() &&
!latest_version_info_["releaseDate"].empty()) {
ImGui::SetCursorPosX(window_width * 0.05f);
std::string date_label =
localization::release_date[localization_language_index_];
std::string release_date = latest_version_info_["releaseDate"];
std::string date_text = date_label + release_date;
ImGui::Text("%s", date_text.c_str());
ImGui::Spacing();
}
// pop text wrap position
ImGui::PopTextWrapPos();
ImGui::EndChild();
ImGui::Spacing();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(UPDATE_NOTIFICATION_OK_BUTTON_PADDING_CN);
} else {
ImGui::SetCursorPosX(UPDATE_NOTIFICATION_OK_BUTTON_PADDING_EN);
}
// update button
if (ImGui::Button(
localization::update[localization_language_index_].c_str())) {
// open download page
std::string url = "https://crossdesk.cn";
#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());
show_update_notification_window_ = false;
}
ImGui::SameLine();
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str())) {
show_update_notification_window_ = false;
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(font_scale);
// pop system font
if (system_chinese_font_ != nullptr) {
ImGui::PopFont();
}
ImGui::End();
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
}
return 0;
}
} // namespace crossdesk

View File

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

View File

@@ -25,8 +25,6 @@
#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);
@@ -37,5 +35,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,8 +2,6 @@
#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() {
@@ -18,11 +16,7 @@ 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
@@ -36,7 +30,7 @@ std::filesystem::path PathManager::GetLogPath() {
#elif __APPLE__ #elif __APPLE__
return GetHome() + "/Library/Logs/" + app_name_; return GetHome() + "/Library/Logs/" + app_name_;
#else #else
return GetCachePath() / "logs"; return GetCachePath() / app_name_ / "logs";
#endif #endif
} }
@@ -76,35 +70,22 @@ 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,8 +14,6 @@
#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);
@@ -42,5 +40,5 @@ class PathManager {
private: private:
std::string app_name_; std::string app_name_;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -1,15 +1,11 @@
#include "screen_capturer_x11.h" #include "screen_capturer_x11.h"
#include <X11/extensions/Xfixes.h>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#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(); }
@@ -38,21 +34,9 @@ int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
XRRCrtcInfo* crtc_info = XRRCrtcInfo* crtc_info =
XRRGetCrtcInfo(display_, screen_res_, output_info->crtc); XRRGetCrtcInfo(display_, screen_res_, output_info->crtc);
std::string name(output_info->name); display_info_list_.push_back(
DisplayInfo((void*)display_, output_info->name, true, crtc_info->x,
if (name.empty()) { crtc_info->y, crtc_info->width, crtc_info->height));
name = "Display" + std::to_string(i + 1);
}
// clean display name, remove non-alphanumeric characters
name.erase(
std::remove_if(name.begin(), name.end(),
[](unsigned char c) { return !std::isalnum(c); }),
name.end());
display_info_list_.push_back(DisplayInfo(
(void*)display_, name, true, crtc_info->x, crtc_info->y,
crtc_info->x + crtc_info->width, crtc_info->y + crtc_info->height));
XRRFreeCrtcInfo(crtc_info); XRRFreeCrtcInfo(crtc_info);
} }
@@ -100,9 +84,8 @@ int ScreenCapturerX11::Destroy() {
return 0; return 0;
} }
int ScreenCapturerX11::Start(bool show_cursor) { int ScreenCapturerX11::Start() {
if (running_) return 0; if (running_) return 0;
show_cursor_ = show_cursor;
running_ = true; running_ = true;
paused_ = false; paused_ = false;
thread_ = std::thread([this]() { thread_ = std::thread([this]() {
@@ -159,20 +142,6 @@ void ScreenCapturerX11::OnFrame() {
AllPlanes, ZPixmap); AllPlanes, ZPixmap);
if (!image) return; if (!image) return;
// if enable show cursor, draw cursor
if (show_cursor_) {
Window root_return, child_return;
int root_x, root_y, win_x, win_y;
unsigned int mask;
if (XQueryPointer(display_, root_, &root_return, &child_return, &root_x,
&root_y, &win_x, &win_y, &mask)) {
if (root_x >= left_ && root_x < left_ + width_ && root_y >= top_ &&
root_y < top_ + height_) {
DrawCursor(image, root_x - left_, root_y - top_);
}
}
}
bool needs_copy = image->bytes_per_line != width_ * 4; bool needs_copy = image->bytes_per_line != width_ * 4;
std::vector<uint8_t> argb_buf; std::vector<uint8_t> argb_buf;
uint8_t* src_argb = nullptr; uint8_t* src_argb = nullptr;
@@ -202,83 +171,4 @@ void ScreenCapturerX11::OnFrame() {
} }
XDestroyImage(image); XDestroyImage(image);
} }
void ScreenCapturerX11::DrawCursor(XImage* image, int x, int y) {
if (!display_ || !image) {
return;
}
// check XFixes extension
int event_base, error_base;
if (!XFixesQueryExtension(display_, &event_base, &error_base)) {
return;
}
XFixesCursorImage* cursor_image = XFixesGetCursorImage(display_);
if (!cursor_image) {
return;
}
int cursor_width = cursor_image->width;
int cursor_height = cursor_image->height;
int draw_x = x - cursor_image->xhot;
int draw_y = y - cursor_image->yhot;
// draw cursor on image
for (int cy = 0; cy < cursor_height; ++cy) {
for (int cx = 0; cx < cursor_width; ++cx) {
int img_x = draw_x + cx;
int img_y = draw_y + cy;
if (img_x < 0 || img_x >= image->width || img_y < 0 ||
img_y >= image->height) {
continue;
}
unsigned long cursor_pixel = cursor_image->pixels[cy * cursor_width + cx];
unsigned char a = (cursor_pixel >> 24) & 0xFF;
// if alpha is 0, skip
if (a == 0) {
continue;
}
unsigned long img_pixel = XGetPixel(image, img_x, img_y);
unsigned char img_r = (img_pixel >> 16) & 0xFF;
unsigned char img_g = (img_pixel >> 8) & 0xFF;
unsigned char img_b = img_pixel & 0xFF;
unsigned char cursor_r = (cursor_pixel >> 16) & 0xFF;
unsigned char cursor_g = (cursor_pixel >> 8) & 0xFF;
unsigned char cursor_b = cursor_pixel & 0xFF;
// alpha mix
unsigned char final_r, final_g, final_b;
if (a == 255) {
// if alpha is 255, use cursor color
final_r = cursor_r;
final_g = cursor_g;
final_b = cursor_b;
} else {
float alpha = a / 255.0f;
float inv_alpha = 1.0f - alpha;
final_r =
static_cast<unsigned char>(cursor_r * alpha + img_r * inv_alpha);
final_g =
static_cast<unsigned char>(cursor_g * alpha + img_g * inv_alpha);
final_b =
static_cast<unsigned char>(cursor_b * alpha + img_b * inv_alpha);
}
// set pixel
unsigned long new_pixel = (final_r << 16) | (final_g << 8) | final_b;
XPutPixel(image, img_x, img_y, new_pixel);
}
}
XFree(cursor_image);
}
} // namespace crossdesk

View File

@@ -10,7 +10,6 @@
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xfixes.h>
#include <atomic> #include <atomic>
#include <cstring> #include <cstring>
@@ -21,8 +20,6 @@
#include "screen_capturer.h" #include "screen_capturer.h"
namespace crossdesk {
class ScreenCapturerX11 : public ScreenCapturer { class ScreenCapturerX11 : public ScreenCapturer {
public: public:
ScreenCapturerX11(); ScreenCapturerX11();
@@ -31,7 +28,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
public: public:
int Init(const int fps, cb_desktop_data cb) override; int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override; int Destroy() override;
int Start(bool show_cursor) override; int Start() override;
int Stop() override; int Stop() override;
int Pause(int monitor_index) override; int Pause(int monitor_index) override;
@@ -55,7 +52,6 @@ class ScreenCapturerX11 : public ScreenCapturer {
std::atomic<bool> running_{false}; std::atomic<bool> running_{false};
std::atomic<bool> paused_{false}; std::atomic<bool> paused_{false};
std::atomic<int> monitor_index_{0}; std::atomic<int> monitor_index_{0};
std::atomic<bool> show_cursor_{true};
int fps_ = 60; int fps_ = 60;
cb_desktop_data callback_; cb_desktop_data callback_;
std::vector<DisplayInfo> display_info_list_; std::vector<DisplayInfo> display_info_list_;
@@ -63,9 +59,6 @@ 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_;
// 鼠标光标相关
void DrawCursor(XImage* image, int x, int y);
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -2,8 +2,6 @@
#include "rd_log.h" #include "rd_log.h"
namespace crossdesk {
ScreenCapturerSck::ScreenCapturerSck() {} ScreenCapturerSck::ScreenCapturerSck() {}
ScreenCapturerSck::~ScreenCapturerSck() {} ScreenCapturerSck::~ScreenCapturerSck() {}
@@ -28,8 +26,8 @@ int ScreenCapturerSck::Destroy() {
return 0; return 0;
} }
int ScreenCapturerSck::Start(bool show_cursor) { int ScreenCapturerSck::Start() {
screen_capturer_sck_impl_->Start(show_cursor); screen_capturer_sck_impl_->Start();
return 0; return 0;
} }
@@ -72,5 +70,4 @@ std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
void ScreenCapturerSck::OnFrame() {} void ScreenCapturerSck::OnFrame() {}
void ScreenCapturerSck::CleanUp() {} void ScreenCapturerSck::CleanUp() {}
} // namespace crossdesk

View File

@@ -16,8 +16,6 @@
#include "screen_capturer.h" #include "screen_capturer.h"
namespace crossdesk {
class ScreenCapturerSck : public ScreenCapturer { class ScreenCapturerSck : public ScreenCapturer {
public: public:
ScreenCapturerSck(); ScreenCapturerSck();
@@ -26,7 +24,7 @@ class ScreenCapturerSck : public ScreenCapturer {
public: public:
int Init(const int fps, cb_desktop_data cb) override; int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override; int Destroy() override;
int Start(bool show_cursor) override; int Start() override;
int Stop() override; int Stop() override;
int Pause(int monitor_index) override; int Pause(int monitor_index) override;
@@ -57,5 +55,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,11 +22,8 @@
#include "display_info.h" #include "display_info.h"
#include "rd_log.h" #include "rd_log.h"
using namespace crossdesk;
class ScreenCapturerSckImpl;
static const int kFullDesktopScreenId = -1; static const int kFullDesktopScreenId = -1;
class ScreenCapturerSckImpl;
// 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.
@@ -57,7 +54,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
public: public:
int Init(const int fps, cb_desktop_data cb) override; int Init(const int fps, cb_desktop_data cb) override;
int Start(bool show_cursor) override; int Start() override;
int SwitchTo(int monitor_index) override; int SwitchTo(int monitor_index) override;
@@ -80,7 +77,6 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
int width_ = 0; int width_ = 0;
int height_ = 0; int height_ = 0;
int fps_ = 60; int fps_ = 60;
bool show_cursor_ = false;
public: public:
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be // Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
@@ -196,26 +192,14 @@ ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) { int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
_on_data = cb; _on_data = cb;
fps_ = fps;
if (@available(macOS 10.15, *)) {
bool has_permission = CGPreflightScreenCaptureAccess();
if (!has_permission) {
LOG_ERROR("Screen recording permission not granted");
return -1;
}
}
dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block SCShareableContent *content = nil; __block SCShareableContent *content = nil;
__block NSError *capture_error = nil;
[SCShareableContent [SCShareableContent
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) { getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
if (error) { if (error) {
capture_error = error; NSLog(@"Failed to get shareable content: %@", error);
LOG_ERROR("Failed to get shareable content: {}",
std::string([error.localizedDescription UTF8String]));
} else { } else {
content = result; content = result;
} }
@@ -223,10 +207,9 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
}]; }];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (capture_error || !content || content.displays.count == 0) { if (!content || content.displays.count == 0) {
LOG_ERROR("Failed to get display info, error: {}", LOG_ERROR("Failed to get display info");
std::string([capture_error.localizedDescription UTF8String])); return 0;
return -1;
} }
CGDirectDisplayID displays[10]; CGDirectDisplayID displays[10];
@@ -239,17 +222,13 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
CGRect bounds = CGDisplayBounds(display_id); CGRect bounds = CGDisplayBounds(display_id);
bool is_primary = CGDisplayIsMain(display_id); bool is_primary = CGDisplayIsMain(display_id);
std::string name = GetDisplayName(display_id); std::string name;
name = GetDisplayName(display_id);
if (name.empty()) { if (name.empty()) {
name = "Display" + std::to_string(unnamed_count++); name = "Display " + std::to_string(unnamed_count++);
} }
// clean display name, remove non-alphanumeric characters
name.erase(
std::remove_if(name.begin(), name.end(), [](unsigned char c) { return !std::isalnum(c); }),
name.end());
DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary, DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary,
static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y), static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y),
static_cast<int>(bounds.origin.x + bounds.size.width), static_cast<int>(bounds.origin.x + bounds.size.width),
@@ -264,18 +243,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
return 0; return 0;
} }
int ScreenCapturerSckImpl::Start(bool show_cursor) { int ScreenCapturerSckImpl::Start() {
if (permanent_error_) {
LOG_ERROR("Cannot start capturer: permanent error occurred");
return -1;
}
if (display_info_list_.empty()) {
LOG_ERROR("Cannot start capturer: display info not initialized");
return -1;
}
show_cursor_ = show_cursor;
StartOrReconfigureCapturer(); StartOrReconfigureCapturer();
return 0; return 0;
} }
@@ -330,17 +298,17 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
return; return;
} }
if (!content.displays || content.displays.count == 0) { if (!content.displays.count) {
LOG_ERROR("getShareableContent returned no displays"); LOG_ERROR("getShareableContent returned no displays");
permanent_error_ = true; permanent_error_ = true;
return; return;
} }
SCDisplay *captured_display = nil; SCDisplay *captured_display;
{ {
std::lock_guard<std::mutex> lock(lock_); std::lock_guard<std::mutex> lock(lock_);
for (SCDisplay *display in content.displays) { for (SCDisplay *display in content.displays) {
if (current_display_ != 0 && current_display_ == display.displayID) { if (current_display_ == display.displayID) {
LOG_WARN("current display: {}, name: {}", current_display_, LOG_WARN("current display: {}, name: {}", current_display_,
display_id_name_map_[current_display_]); display_id_name_map_[current_display_]);
captured_display = display; captured_display = display;
@@ -349,35 +317,15 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
} }
if (!captured_display) { if (!captured_display) {
captured_display = content.displays.firstObject; captured_display = content.displays.firstObject;
if (captured_display) { current_display_ = captured_display.displayID;
current_display_ = captured_display.displayID;
}
} }
} }
if (!captured_display) {
LOG_ERROR("Failed to find valid display");
permanent_error_ = true;
return;
}
SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display
excludingWindows:@[]]; excludingWindows:@[]];
if (!filter) {
LOG_ERROR("Failed to create SCContentFilter");
permanent_error_ = true;
return;
}
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init]; SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
if (!config) {
LOG_ERROR("Failed to create SCStreamConfiguration");
permanent_error_ = true;
return;
}
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
config.showsCursor = show_cursor_; config.showsCursor = false;
config.width = filter.contentRect.size.width * filter.pointPixelScale; config.width = filter.contentRect.size.width * filter.pointPixelScale;
config.height = filter.contentRect.size.height * filter.pointPixelScale; config.height = filter.contentRect.size.height * filter.pointPixelScale;
config.captureResolution = SCCaptureResolutionAutomatic; config.captureResolution = SCCaptureResolutionAutomatic;
@@ -466,33 +414,20 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
} }
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() { void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
if (permanent_error_) { // The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_`
LOG_ERROR("Cannot reconfigure capturer: permanent error occurred"); // inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is
return; // deleted before the block is executed.
}
if (@available(macOS 10.15, *)) {
bool has_permission = CGPreflightScreenCaptureAccess();
if (!has_permission) {
LOG_ERROR("Screen recording permission not granted");
permanent_error_ = true;
return;
}
}
SckHelper *local_helper = helper_; SckHelper *local_helper = helper_;
auto handler = ^(SCShareableContent *content, NSError *error) { auto handler = ^(SCShareableContent *content, NSError *error) {
if (error) {
LOG_ERROR("getShareableContent failed: {}",
std::string([error.localizedDescription UTF8String]));
[local_helper onShareableContentCreated:nil];
return;
}
[local_helper onShareableContentCreated:content]; [local_helper onShareableContentCreated:content];
}; };
[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.
@@ -550,8 +485,4 @@ void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
_capturer = nullptr; _capturer = nullptr;
} }
@end @end
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
return std::make_unique<ScreenCapturerSckImpl>();
}

View File

@@ -11,8 +11,6 @@
#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*)>
@@ -24,7 +22,7 @@ class ScreenCapturer {
public: public:
virtual int Init(const int fps, cb_desktop_data cb) = 0; virtual int Init(const int fps, cb_desktop_data cb) = 0;
virtual int Destroy() = 0; virtual int Destroy() = 0;
virtual int Start(bool show_cursor) = 0; virtual int Start() = 0;
virtual int Stop() = 0; virtual int Stop() = 0;
virtual int Pause(int monitor_index) = 0; virtual int Pause(int monitor_index) = 0;
virtual int Resume(int monitor_index) = 0; virtual int Resume(int monitor_index) = 0;
@@ -32,5 +30,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,8 +16,6 @@
#include "screen_capturer_sck.h" #include "screen_capturer_sck.h"
#endif #endif
namespace crossdesk {
class ScreenCapturerFactory { class ScreenCapturerFactory {
public: public:
virtual ~ScreenCapturerFactory() {} virtual ~ScreenCapturerFactory() {}
@@ -36,5 +34,5 @@ class ScreenCapturerFactory {
#endif #endif
} }
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -10,46 +10,35 @@
#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 std::wstring& wstr) { std::string WideToUtf8(const wchar_t *wideStr) {
if (wstr.empty()) return {}; int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
int size_needed = WideCharToMultiByte( nullptr, nullptr);
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, wstr.data(), (int)wstr.size(), result.data(), WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr,
size_needed, nullptr, 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, display_name, {(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom}); monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR*)data = hmonitor; *(HMONITOR *)data = hmonitor;
} else { } else {
gs_display_list.push_back(DisplayInfo( gs_display_list.push_back(DisplayInfo(
(void*)hmonitor, display_name, (void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom)); monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
@@ -92,7 +81,7 @@ bool ScreenCapturerWgc::IsWgcSupported() {
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */ /* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
return winrt::Windows::Foundation::Metadata::ApiInformation:: return winrt::Windows::Foundation::Metadata::ApiInformation::
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8); IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
} catch (const winrt::hresult_error&) { } catch (const winrt::hresult_error &) {
return false; return false;
} catch (...) { } catch (...) {
return false; return false;
@@ -126,7 +115,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
} }
for (int i = 0; i < display_info_list_.size(); i++) { for (int i = 0; i < display_info_list_.size(); i++) {
const auto& display = display_info_list_[i]; const auto &display = display_info_list_[i];
LOG_INFO( LOG_INFO(
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - " "index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
"({}, {})", "({}, {})",
@@ -152,7 +141,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
int ScreenCapturerWgc::Destroy() { return 0; } int ScreenCapturerWgc::Destroy() { return 0; }
int ScreenCapturerWgc::Start(bool show_cursor) { int ScreenCapturerWgc::Start() {
if (running_ == true) { if (running_ == true) {
LOG_ERROR("Screen capturer already running"); LOG_ERROR("Screen capturer already running");
return 0; return 0;
@@ -172,7 +161,7 @@ int ScreenCapturerWgc::Start(bool show_cursor) {
if (sessions_[i].running_) { if (sessions_[i].running_) {
LOG_ERROR("Session {} is already running", i); LOG_ERROR("Session {} is already running", i);
} else { } else {
sessions_[i].session_->Start(show_cursor); sessions_[i].session_->Start();
if (i != 0) { if (i != 0) {
sessions_[i].session_->Pause(); sessions_[i].session_->Pause();
@@ -254,16 +243,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,
@@ -273,7 +262,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();
} }
@@ -281,4 +270,3 @@ void ScreenCapturerWgc::CleanUp() {
sessions_.clear(); sessions_.clear();
} }
} }
} // namespace crossdesk

View File

@@ -11,8 +11,6 @@
#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:
@@ -24,7 +22,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
int Init(const int fps, cb_desktop_data cb) override; int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override; int Destroy() override;
int Start(bool show_cursor) override; int Start() override;
int Stop() override; int Stop() override;
int Pause(int monitor_index) override; int Pause(int monitor_index) override;
@@ -66,5 +64,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,8 +3,6 @@
#include <Windows.h> #include <Windows.h>
namespace crossdesk {
class WgcSession { class WgcSession {
public: public:
struct wgc_session_frame { struct wgc_session_frame {
@@ -12,13 +10,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:
@@ -27,15 +25,15 @@ 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(bool show_cursor) = 0; virtual int Start() = 0;
virtual int Stop() = 0; virtual int Stop() = 0;
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,11 +18,9 @@
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) {}
@@ -50,12 +48,12 @@ 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;
} }
int WgcSessionImpl::Start(bool show_cursor) { int WgcSessionImpl::Start() {
std::lock_guard locker(lock_); std::lock_guard locker(lock_);
if (is_running_) return 0; if (is_running_) return 0;
@@ -91,7 +89,7 @@ int WgcSessionImpl::Start(bool show_cursor) {
capture_session_.StartCapture(); capture_session_.StartCapture();
capture_session_.IsCursorCaptureEnabled(show_cursor); capture_session_.IsCursorCaptureEnabled(false);
error = 0; error = 0;
} catch (winrt::hresult_error) { } catch (winrt::hresult_error) {
@@ -177,7 +175,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;
} }
@@ -189,7 +187,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;
} }
@@ -218,8 +216,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;
@@ -270,8 +268,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_);
} }
@@ -288,8 +286,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");
} }
@@ -377,5 +375,4 @@ LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
// ::CloseWindow(hwnd_); // ::CloseWindow(hwnd_);
// ::DestroyWindow(hwnd_); // ::DestroyWindow(hwnd_);
// } // }
} // namespace crossdesk

View File

@@ -10,17 +10,15 @@
#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(
@@ -46,9 +44,9 @@ 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(bool show_cursor) override; int Start() override;
int Stop() override; int Stop() override;
int Pause() override; int Pause() override;
@@ -62,11 +60,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();
@@ -80,7 +78,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};
@@ -115,5 +113,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,8 +9,6 @@
#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;
@@ -267,5 +265,4 @@ int SpeakerCapturerLinux::Pause() {
int SpeakerCapturerLinux::Resume() { int SpeakerCapturerLinux::Resume() {
paused_ = false; paused_ = false;
return 0; return 0;
} }
} // namespace crossdesk

View File

@@ -18,8 +18,6 @@
#include "speaker_capturer.h" #include "speaker_capturer.h"
namespace crossdesk {
class SpeakerCapturerLinux : public SpeakerCapturer { class SpeakerCapturerLinux : public SpeakerCapturer {
public: public:
SpeakerCapturerLinux(); SpeakerCapturerLinux();
@@ -52,5 +50,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,8 +12,6 @@
#include "speaker_capturer.h" #include "speaker_capturer.h"
namespace crossdesk {
class SpeakerCapturerMacosx : public SpeakerCapturer { class SpeakerCapturerMacosx : public SpeakerCapturer {
public: public:
SpeakerCapturerMacosx(); SpeakerCapturerMacosx();
@@ -35,5 +33,5 @@ class SpeakerCapturerMacosx : public SpeakerCapturer {
class Impl; class Impl;
Impl* impl_ = nullptr; Impl* impl_ = nullptr;
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -5,17 +5,13 @@
#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) crossdesk::SpeakerCapturerMacosx* owner; @property(nonatomic, assign) SpeakerCapturerMacosx* owner;
- (instancetype)initWithOwner:(crossdesk::SpeakerCapturerMacosx*)owner; - (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner;
@end @end
@implementation SpeakerCaptureDelegate @implementation SpeakerCaptureDelegate
- (instancetype)initWithOwner:(crossdesk::SpeakerCapturerMacosx*)owner { - (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner {
self = [super init]; self = [super init];
if (self) { if (self) {
_owner = owner; _owner = owner;
@@ -26,32 +22,72 @@ class SpeakerCapturerMacosx;
- (void)stream:(SCStream*)stream - (void)stream:(SCStream*)stream
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
ofType:(SCStreamOutputType)type { ofType:(SCStreamOutputType)type {
if (type != SCStreamOutputTypeAudio) return; if (type == SCStreamOutputTypeAudio) {
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);
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); if (_owner->cb_ && dataPtr && length > 0 && asbd) {
size_t length = CMBlockBufferGetDataLength(blockBuffer); std::vector<short> out_pcm16;
char* dataPtr = NULL; if (asbd->mFormatFlags & kAudioFormatFlagIsFloat) {
CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr); int channels = asbd->mChannelsPerFrame;
CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer); int samples = (int)(length / sizeof(float));
const AudioStreamBasicDescription* asbd = float* floatData = (float*)dataPtr;
CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc); std::vector<short> pcm16(samples);
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 (_owner->cb_ && dataPtr && length > 0 && asbd) { if (channels > 1) {
std::vector<short> out_pcm16; int mono_samples = samples / channels;
// ... 数据转换逻辑保持不变 ... out_pcm16.resize(mono_samples);
// 调用回调 for (int i = 0; i < mono_samples; ++i) {
size_t frame_bytes = 960; // 480 * 2 int sum = 0;
size_t total_bytes = out_pcm16.size() * sizeof(short); for (int c = 0; c < channels; ++c) {
unsigned char* p = (unsigned char*)out_pcm16.data(); sum += pcm16[i * channels + c];
for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) { }
_owner->cb_(p + offset, frame_bytes, "audio"); out_pcm16[i] = sum / channels;
}
} 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;
@@ -226,4 +262,3 @@ 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,11 +9,9 @@
#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:
@@ -25,5 +23,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,8 +15,6 @@
#include "speaker_capturer_macosx.h" #include "speaker_capturer_macosx.h"
#endif #endif
namespace crossdesk {
class SpeakerCapturerFactory { class SpeakerCapturerFactory {
public: public:
virtual ~SpeakerCapturerFactory() {} virtual ~SpeakerCapturerFactory() {}
@@ -34,5 +32,5 @@ class SpeakerCapturerFactory {
#endif #endif
} }
}; };
} // namespace crossdesk
#endif #endif

View File

@@ -7,8 +7,6 @@
#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;
@@ -101,4 +99,3 @@ int SpeakerCapturerWasapi::Destroy() {
} }
int SpeakerCapturerWasapi::Pause() { return 0; } int SpeakerCapturerWasapi::Pause() { return 0; }
} // namespace crossdesk

View File

@@ -9,8 +9,6 @@
#include "speaker_capturer.h" #include "speaker_capturer.h"
namespace crossdesk {
class SpeakerCapturerWasapi : public SpeakerCapturer { class SpeakerCapturerWasapi : public SpeakerCapturer {
public: public:
SpeakerCapturerWasapi(); SpeakerCapturerWasapi();
@@ -33,5 +31,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"
namespace crossdesk { static std::string test;
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] = static_cast<char>(0xFF); dst_rgba[i * 4 + 3] = 0xFF;
} }
for (int row = 0; row < fit_h; ++row) { for (int y = 0; y < fit_h; ++y) {
int dst_offset = int dst_offset =
((row + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4; ((y + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4, fit_w * 4); memcpy(dst_rgba + dst_offset, abgr.data() + y * 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] = static_cast<char>(0xFF); rgba_buffer_[i * 4 + 3] = 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,4 +430,3 @@ 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,8 +14,6 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace crossdesk {
class Thumbnail { class Thumbnail {
public: public:
struct RecentConnection { struct RecentConnection {
@@ -85,5 +83,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

@@ -1,158 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-11
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#include "version_checker.h"
#include <httplib.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
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;
}
nlohmann::json 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 = nlohmann::json::parse(res->body);
if (j.contains("releaseDate") && j["releaseDate"].is_string()) {
latest_release_date_ = j["releaseDate"];
} else {
latest_release_date_ = "";
}
return j;
} catch (std::exception&) {
latest_release_date_ = "";
return nlohmann::json{};
}
} else {
latest_release_date_ = "";
return nlohmann::json{};
}
} else {
latest_release_date_ = "";
return nlohmann::json{};
}
}
} // namespace crossdesk

View File

@@ -1,21 +0,0 @@
/*
* @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 <nlohmann/json.hpp>
#include <string>
namespace crossdesk {
nlohmann::json CheckUpdate();
bool IsNewerVersion(const std::string& current, const std::string& latest);
} // namespace crossdesk
#endif

View File

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

View File

@@ -1,39 +0,0 @@
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; }

View File

@@ -1,117 +0,0 @@
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)

194
thirdparty/imgui/port/xmake.lua vendored Normal file
View File

@@ -0,0 +1,194 @@
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)

227
thirdparty/imgui/xmake.lua vendored Normal file
View File

@@ -0,0 +1,227 @@
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

Submodule thirdparty/minirtc added at 40eaf93b42

View File

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

View File

@@ -7,32 +7,21 @@ option("CROSSDESK_VERSION")
set_description("Set CROSSDESK_VERSION for build") set_description("Set CROSSDESK_VERSION for build")
option_end() option_end()
option("USE_CUDA")
set_default(false)
set_showmenu(true)
set_description("Use CUDA for hardware codec acceleration")
option_end()
add_rules("mode.release", "mode.debug") add_rules("mode.release", "mode.debug")
set_languages("c++17") set_languages("c++17")
set_encodings("utf-8") 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")
add_defines("USE_CUDA=" .. (is_config("USE_CUDA", true) and "1" or "0"))
if is_mode("debug") then if is_mode("debug") then
add_defines("CROSSDESK_DEBUG") add_defines("DESK_PORT_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")
@@ -45,7 +34,7 @@ elseif is_os("linux") then
add_links("pulse-simple", "pulse") add_links("pulse-simple", "pulse")
add_requires("libyuv") add_requires("libyuv")
add_syslinks("pthread", "dl") add_syslinks("pthread", "dl")
add_links("SDL3", "asound", "X11", "Xtst", "Xrandr", "Xfixes") add_links("SDL3", "asound", "X11", "Xtst", "Xrandr")
add_cxflags("-Wno-unused-variable") add_cxflags("-Wno-unused-variable")
elseif is_os("macosx") then elseif is_os("macosx") then
add_links("SDL3") add_links("SDL3")
@@ -55,9 +44,9 @@ elseif is_os("macosx") then
"CoreMedia", "CoreVideo", "CoreAudio", "AudioToolbox") "CoreMedia", "CoreVideo", "CoreAudio", "AudioToolbox")
end end
add_packages("spdlog", "imgui", "nlohmann_json") add_packages("spdlog", "imgui")
includes("submodules", "thirdparty") includes("thirdparty")
target("rd_log") target("rd_log")
set_kind("object") set_kind("object")
@@ -141,15 +130,9 @@ target("thumbnail")
add_files("src/thumbnail/*.cpp") add_files("src/thumbnail/*.cpp")
add_includedirs("src/thumbnail", {public = true}) add_includedirs("src/thumbnail", {public = true})
target("autostart")
set_kind("object")
add_deps("rd_log")
add_files("src/autostart/*.cpp")
add_includedirs("src/autostart", {public = true})
target("config_center") target("config_center")
set_kind("object") set_kind("object")
add_deps("rd_log", "autostart") add_deps("rd_log")
add_files("src/config_center/*.cpp") add_files("src/config_center/*.cpp")
add_includedirs("src/config_center", {public = true}) add_includedirs("src/config_center", {public = true})
@@ -160,21 +143,13 @@ 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", "version_checker") "device_controller", "thumbnail")
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp", add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
"src/gui/windows/*.cpp") "src/gui/windows/*.cpp")
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars", add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",
@@ -182,12 +157,9 @@ target("gui")
if is_os("windows") then if is_os("windows") then
add_files("src/gui/tray/*.cpp") add_files("src/gui/tray/*.cpp")
add_includedirs("src/gui/tray", {public = true}) add_includedirs("src/gui/tray", {public = true})
elseif is_os("macosx") then
add_files("src/gui/windows/*.mm")
end end
target("crossdesk") target("crossdesk")
set_kind("binary") set_kind("binary")
add_deps("rd_log", "common", "gui") add_deps("rd_log", "common", "gui")
add_files("src/app/*.cpp") add_files("src/app/main.cpp")
add_includedirs("src/app", {public = true})