Compare commits
216 Commits
new-ui
...
v1.0.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bd27d0b17 | ||
|
|
ee5612da8b | ||
|
|
c7a2023c88 | ||
|
|
c031a8c145 | ||
|
|
0bf83f07ad | ||
|
|
3638b712bd | ||
|
|
17f9536476 | ||
|
|
0ef51e3faf | ||
|
|
cccf5dadb2 | ||
|
|
2f0b0ffc22 | ||
|
|
c7411b59f1 | ||
|
|
8222782522 | ||
|
|
5fed09c1aa | ||
|
|
8e499772f9 | ||
|
|
0da812e7e9 | ||
|
|
3d67b6e9d6 | ||
|
|
506b2893c6 | ||
|
|
56abe9e690 | ||
|
|
ab13fa582d | ||
|
|
b9e8192eee | ||
|
|
5590aaeb1e | ||
|
|
adfe14809f | ||
|
|
a21dbc8d69 | ||
|
|
9d45e497f4 | ||
|
|
e5891eb397 | ||
|
|
b9c70f54d3 | ||
|
|
9cd617d078 | ||
|
|
92fd7f2e89 | ||
|
|
b8535fff6f | ||
|
|
09f34a81ad | ||
|
|
22cc552e85 | ||
|
|
69a4dfcbb9 | ||
|
|
e1390ca2d3 | ||
|
|
dccdcd1b6f | ||
|
|
276c3a336f | ||
|
|
08518a9409 | ||
|
|
f6b0767bb1 | ||
|
|
641fc84430 | ||
|
|
4ce79b87a3 | ||
|
|
477b204913 | ||
|
|
98f349ea2f | ||
|
|
47b1e15eef | ||
|
|
590bf50924 | ||
|
|
097633e47d | ||
|
|
d9ba88107d | ||
|
|
f6a6ef0b08 | ||
|
|
a93ee0c19e | ||
|
|
7e73553aca | ||
|
|
b858bd78c0 | ||
|
|
f10947edf9 | ||
|
|
c723cca8f9 | ||
|
|
b8f8b07ebe | ||
|
|
f9c6e5a6ef | ||
|
|
15bf8ef8c0 | ||
|
|
6565816c0e | ||
|
|
2aa67ccd57 | ||
|
|
88c75f94e4 | ||
|
|
aea9505c4c | ||
|
|
646740aab6 | ||
|
|
a458836e6e | ||
|
|
5fe7df8ea8 | ||
|
|
fda3743f86 | ||
|
|
9e4170b3a8 | ||
|
|
43338aaf02 | ||
|
|
274b7fcedc | ||
|
|
01a5660984 | ||
|
|
a45118f785 | ||
|
|
b6631c3db0 | ||
|
|
84d164c3af | ||
|
|
32815ce25a | ||
|
|
7c2b3f8c8d | ||
|
|
309d9d6ed6 | ||
|
|
91ab21c5f2 | ||
|
|
b8369c4f0a | ||
|
|
a068a32303 | ||
|
|
b6fe0da581 | ||
|
|
6e7e2697b5 | ||
|
|
22084072b0 | ||
|
|
d5457373f4 | ||
|
|
01ae299cde | ||
|
|
fbcd4c21cf | ||
|
|
cf9dc0a9a5 | ||
|
|
1b4f41af46 | ||
|
|
7f3425519b | ||
|
|
434c92deb6 | ||
|
|
e5eae1feb4 | ||
|
|
37e37d7d20 | ||
|
|
062568dc96 | ||
|
|
d60fdf9050 | ||
|
|
9912a88a13 | ||
|
|
8a8f2cd5d7 | ||
|
|
e77e16d9c2 | ||
|
|
1616d0ec33 | ||
|
|
374453775f | ||
|
|
6f8fd6a030 | ||
|
|
c7166975b3 | ||
|
|
2ae5e5a969 | ||
|
|
85cdc995c5 | ||
|
|
b051c8a059 | ||
|
|
508b7dc7a1 | ||
|
|
f203566b81 | ||
|
|
8360c1725f | ||
|
|
3b00fdef71 | ||
|
|
f2664daaec | ||
|
|
1a1bbe3e4d | ||
|
|
4c898c5f07 | ||
|
|
5d704edbc8 | ||
|
|
b23038bac6 | ||
|
|
147b9b2ddc | ||
|
|
1327b995f2 | ||
|
|
e8d7ec8daf | ||
|
|
3bf68a396f | ||
|
|
29077fad5e | ||
|
|
69367bdadf | ||
|
|
f650b5a6ef | ||
|
|
75021b74ef | ||
|
|
205421621b | ||
|
|
4bb5607702 | ||
|
|
78c54136e2 | ||
|
|
8fe8f4fd7e | ||
|
|
c13e2613b6 | ||
|
|
e52ec6cde2 | ||
|
|
ad6a24b230 | ||
|
|
ad60815d83 | ||
|
|
c47a90bf9c | ||
|
|
3901e26c37 | ||
|
|
41c27139a0 | ||
|
|
a31ca21ef4 | ||
|
|
57939ccd00 | ||
|
|
fc14f7b9c6 | ||
|
|
cfcf6dd9cb | ||
|
|
854c3a72c7 | ||
|
|
8004d25ca3 | ||
|
|
b04dfd188a | ||
|
|
383ace2493 | ||
|
|
094204361c | ||
|
|
d72c6d9df7 | ||
|
|
05d73ebe9a | ||
|
|
a8b5e934b8 | ||
|
|
920677a433 | ||
|
|
b86d3d42ee | ||
|
|
818dab764f | ||
|
|
7ea26854af | ||
|
|
f34b55b88a | ||
|
|
67168f7735 | ||
|
|
6e69c37056 | ||
|
|
8f5dd21e75 | ||
|
|
e2dfeb1186 | ||
|
|
96c7d3174b | ||
|
|
97f6c18296 | ||
|
|
9138ca771f | ||
|
|
abc6b17a3b | ||
|
|
8e0524bf60 | ||
|
|
6ebf583a13 | ||
|
|
eca4f614c8 | ||
|
|
11358e0b60 | ||
|
|
0a1014dded | ||
|
|
a6beb48b1f | ||
|
|
5eff530ab9 | ||
|
|
e6e237279c | ||
|
|
6fe46f6181 | ||
|
|
9b5023645c | ||
|
|
c2da4ebcb7 | ||
|
|
dc05f2405f | ||
|
|
e2a469ed65 | ||
|
|
e89d385f99 | ||
|
|
0a61dcc2be | ||
|
|
250fd49406 | ||
|
|
93bd5b2660 | ||
|
|
d05ff9f905 | ||
|
|
f3451a5fa1 | ||
|
|
a9084ba98d | ||
|
|
893051f9b3 | ||
|
|
dfbd4317b7 | ||
|
|
532ad0eb51 | ||
|
|
184983d857 | ||
|
|
c33e4bbe0e | ||
|
|
4b9e86c424 | ||
|
|
f7f8ddd925 | ||
|
|
188b1758f2 | ||
|
|
9705374b9a | ||
|
|
22aed6ea53 | ||
|
|
44c5fde086 | ||
|
|
2cd5a9dd76 | ||
|
|
fa73447f88 | ||
|
|
536fe17055 | ||
|
|
662cbbc3cc | ||
|
|
69a8503ee1 | ||
|
|
b906bfcafd | ||
|
|
f891a047d6 | ||
|
|
dce32672a6 | ||
|
|
2774991107 | ||
|
|
b7ce8b6299 | ||
|
|
700fb2ec14 | ||
|
|
62d14587cd | ||
|
|
824d9243cc | ||
|
|
5351b4da0e | ||
|
|
fcd0488624 | ||
|
|
193e905b28 | ||
|
|
f48085c69a | ||
|
|
cf078be53f | ||
|
|
badcd6a05c | ||
|
|
d828bd736d | ||
|
|
1e014bdae3 | ||
|
|
334ab182db | ||
|
|
f52de76bfc | ||
|
|
6c7d0e8cab | ||
|
|
7229ae856e | ||
|
|
597d760d24 | ||
|
|
bcf61e1ada | ||
|
|
08e596714b | ||
|
|
bff577ba34 | ||
|
|
4df935b9d2 | ||
|
|
9bb560314b | ||
|
|
1a0c5e8b42 | ||
|
|
011919d0e7 |
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.h linguist-language=C++
|
||||
*.cpp linguist-language=C++
|
||||
*.lua linguist-language=Xmake
|
||||
387
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
# Linux amd64
|
||||
build-linux-amd64:
|
||||
name: Build on Ubuntu 22.04 amd64
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: crossdesk/ubuntu20.04:latest
|
||||
options: --user root
|
||||
steps:
|
||||
- name: Extract version number
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
VERSION_NUM="${VERSION#v}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set legal Debian version
|
||||
shell: bash
|
||||
id: set_deb_version
|
||||
run: |
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
||||
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
|
||||
else
|
||||
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
|
||||
fi
|
||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Build CrossDesk
|
||||
env:
|
||||
CUDA_PATH: /usr/local/cuda
|
||||
XMAKE_GLOBALDIR: /data
|
||||
run: |
|
||||
ls -la $XMAKE_GLOBALDIR
|
||||
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
|
||||
xmake b -vy --root crossdesk
|
||||
|
||||
- name: Decode and save certificate
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p certs
|
||||
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
chmod +x ./scripts/linux/pkg_amd64.sh
|
||||
./scripts/linux/pkg_amd64.sh ${LEGAL_VERSION}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crossdesk-linux-amd64-${{ env.LEGAL_VERSION }}
|
||||
path: ${{ github.workspace }}/crossdesk-linux-amd64-${{ env.LEGAL_VERSION }}.deb
|
||||
|
||||
# Linux arm64
|
||||
build-linux-arm64:
|
||||
name: Build on Ubuntu 22.04 arm64
|
||||
runs-on: ubuntu-22.04-arm
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64
|
||||
image: crossdesk/ubuntu20.04-arm64v8:latest
|
||||
package_script: ./scripts/linux/pkg_arm64.sh
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
options: --user root
|
||||
steps:
|
||||
- name: Extract version number
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
VERSION_NUM="${VERSION#v}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set legal Debian version
|
||||
shell: bash
|
||||
id: set_deb_version
|
||||
run: |
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
|
||||
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
|
||||
else
|
||||
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
|
||||
fi
|
||||
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Build CrossDesk
|
||||
env:
|
||||
CUDA_PATH: /usr/local/cuda
|
||||
XMAKE_GLOBALDIR: /data
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
|
||||
xmake b -vy --root crossdesk
|
||||
|
||||
- name: Decode and save certificate
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p certs
|
||||
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
chmod +x ${{ matrix.package_script }}
|
||||
${{ matrix.package_script }} ${LEGAL_VERSION}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}
|
||||
path: ${{ github.workspace }}/crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}.deb
|
||||
|
||||
# macOS
|
||||
build-macos:
|
||||
name: Build on macOS
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
runner: macos-15-intel
|
||||
cache-key: intel
|
||||
out-dir: ./build/macosx/x86_64/release/crossdesk
|
||||
package_script: ./scripts/macosx/pkg_x64.sh
|
||||
- arch: arm64
|
||||
runner: macos-14
|
||||
cache-key: arm
|
||||
out-dir: ./build/macosx/arm64/release/crossdesk
|
||||
package_script: ./scripts/macosx/pkg_arm64.sh
|
||||
|
||||
steps:
|
||||
- name: Extract version number
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
|
||||
echo "VERSION_NUM=${VERSION_NUM}"
|
||||
|
||||
- name: Cache xmake dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.xmake/packages
|
||||
key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
|
||||
|
||||
- name: Install xmake
|
||||
run: brew install xmake
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Build CrossDesk
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${VERSION_NUM} -y
|
||||
xmake b -vy crossdesk
|
||||
|
||||
- name: Decode and save certificate
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p certs
|
||||
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
|
||||
|
||||
- name: Package CrossDesk app
|
||||
run: |
|
||||
chmod +x ${{ matrix.package_script }}
|
||||
${{ matrix.package_script }} ${VERSION_NUM}
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
|
||||
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
|
||||
|
||||
- name: Move files to release dir
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
|
||||
|
||||
# Windows
|
||||
build-windows-x64:
|
||||
name: Build on Windows x64
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
XMAKE_GLOBALDIR: D:\xmake_global
|
||||
steps:
|
||||
- name: Extract version number
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ref = $env:GITHUB_REF
|
||||
$version = $ref -replace '^refs/(tags|heads)/', ''
|
||||
$version = $version -replace '^v', ''
|
||||
$version = $version -replace '/', '-'
|
||||
$SHORT_SHA = $env:GITHUB_SHA.Substring(0,7)
|
||||
echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Cache xmake dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: D:\xmake_global\.xmake\packages
|
||||
key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-xmake-deps-intel-
|
||||
|
||||
- name: Install xmake
|
||||
run: |
|
||||
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
|
||||
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
xmake create cuda
|
||||
Set-Location cuda
|
||||
xmake g --theme=plain
|
||||
$cudaPath = ""
|
||||
$packagesPath = "D:\xmake_global\.xmake\packages"
|
||||
|
||||
if (Test-Path $packagesPath) {
|
||||
Write-Host "Packages directory exists: $packagesPath"
|
||||
try {
|
||||
$info = xmake require --info "cuda 12.6.3" 2>$null
|
||||
if ($null -ne $info -and $info -ne "") {
|
||||
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
|
||||
}
|
||||
} catch {}
|
||||
} else {
|
||||
Write-Host "Packages directory not found: $packagesPath"
|
||||
Write-Host "Installing CUDA package..."
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
$info = xmake require --info "cuda 12.6.3"
|
||||
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
|
||||
}
|
||||
|
||||
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
|
||||
Write-Host "Resolved CUDA_PATH = $cudaPath"
|
||||
Pop-Location
|
||||
|
||||
- name: Download INetC plugin
|
||||
shell: powershell
|
||||
run: |
|
||||
$url = "https://github.com/DigitalMediaServer/NSIS-INetC-plugin/releases/download/v1.0.5.7/InetC.zip"
|
||||
$out = "$env:RUNNER_TEMP\InetC.zip"
|
||||
Invoke-WebRequest -Uri $url -OutFile $out
|
||||
Expand-Archive $out -DestinationPath "$env:RUNNER_TEMP\InetC" -Force
|
||||
|
||||
$source = "$env:RUNNER_TEMP\InetC\x86-unicode\INetC.dll"
|
||||
$target = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\INetC.dll"
|
||||
|
||||
Write-Host "Copying $source to $target"
|
||||
Copy-Item $source $target -Force
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Copy nsProcess plugin to NSIS folder
|
||||
run: |
|
||||
$nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode"
|
||||
copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir
|
||||
|
||||
- name: Build CrossDesk
|
||||
run: |
|
||||
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y
|
||||
xmake b -vy crossdesk
|
||||
|
||||
- name: Decode and save certificate
|
||||
shell: powershell
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path certs
|
||||
[System.IO.File]::WriteAllBytes('certs\crossdesk.cn_root.crt', [Convert]::FromBase64String('${{ secrets.CROSSDESK_CERT_BASE64 }}'))
|
||||
|
||||
- name: Package
|
||||
shell: pwsh
|
||||
run: |
|
||||
cd "${{ github.workspace }}\scripts\windows"
|
||||
makensis /DVERSION=$env:VERSION_NUM nsis_script.nsi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crossdesk-win-x64-${{ env.VERSION_NUM }}
|
||||
path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs:
|
||||
[build-linux-amd64, build-linux-arm64, build-macos, build-windows-x64]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Extract version number
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF##*/}"
|
||||
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
|
||||
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
|
||||
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Rename artifacts
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}.pkg
|
||||
cp artifacts/crossdesk-macos-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_NUM }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}.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_NUM }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}.exe
|
||||
|
||||
- name: List release files
|
||||
run: ls -lh release/
|
||||
|
||||
- name: Upload to Versioned GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ steps.version.outputs.VERSION_NUM }}
|
||||
name: Release v${{ steps.version.outputs.VERSION_NUM }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: release/*
|
||||
generate_release_notes: false
|
||||
body: |
|
||||
Binary release only. Source code is not included.
|
||||
|
||||
- name: Create or update 'latest' tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -f latest
|
||||
git push origin latest --force
|
||||
|
||||
- name: Upload to GitHub Release (latest)
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: latest
|
||||
name: Latest Release
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: release/*
|
||||
generate_release_notes: false
|
||||
|
||||
- name: Upload artifacts to server
|
||||
uses: burnett01/rsync-deployments@5.2
|
||||
with:
|
||||
switches: -avzr --progress --delete
|
||||
path: release/*
|
||||
remote_path: /var/www/html/downloads/
|
||||
remote_host: ${{ secrets.SERVER_HOST }}
|
||||
remote_user: ${{ secrets.SERVER_USER }}
|
||||
remote_key: ${{ secrets.SERVER_KEY }}
|
||||
65
.github/workflows/close-issue.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Close Inactive Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run every day at midnight
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
close_inactive_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check inactive issues and close them
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const now = new Date().getTime();
|
||||
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||
|
||||
for (const issue of issues) {
|
||||
// skip pull requests (they are also returned by listForRepo)
|
||||
if (issue.pull_request) continue;
|
||||
|
||||
// skip labeled issues
|
||||
if (issue.labels.length > 0) {
|
||||
console.log(`Skipping issue #${issue.number} (Has labels).`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// fetch comments for this issue
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
// determine the "last activity" time
|
||||
let lastActivityTime;
|
||||
if (comments.length > 0) {
|
||||
const lastComment = comments[comments.length - 1];
|
||||
lastActivityTime = new Date(lastComment.updated_at).getTime();
|
||||
} else {
|
||||
lastActivityTime = new Date(issue.created_at).getTime();
|
||||
}
|
||||
|
||||
// check inactivity
|
||||
if (now - lastActivityTime > inactivePeriod) {
|
||||
console.log(`Closing inactive issue: #${issue.number} (No recent replies for 7 days)`);
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed',
|
||||
});
|
||||
} else {
|
||||
console.log(`Skipping issue #${issue.number} (Active within 7 days).`);
|
||||
}
|
||||
}
|
||||
49
.github/workflows/update-pages.yml
vendored
Normal 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 }}
|
||||
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "thirdparty/projectx"]
|
||||
path = thirdparty/projectx
|
||||
url = https://github.com/dijunkun/projectx.git
|
||||
[submodule "thirdparty/minirtc"]
|
||||
path = thirdparty/minirtc
|
||||
url = https://github.com/kunkundi/minirtc.git
|
||||
|
||||
39
Info.plist
@@ -1,39 +0,0 @@
|
||||
<?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>
|
||||
<!-- 应用的Bundle identifier,通常使用反向域名标记 -->
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.yourcompany.yourappname</string>
|
||||
|
||||
<!-- 应用的显示名称 -->
|
||||
<key>CFBundleName</key>
|
||||
<string>Your App Name</string>
|
||||
|
||||
<!-- 应用的版本号 -->
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
|
||||
<!-- 应用的构建版本号 -->
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
|
||||
<!-- 请求麦克风访问权限 -->
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>App requires access to the microphone for audio recording.</string>
|
||||
|
||||
<!-- 请求相机访问权限 -->
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>App requires access to the camera for video recording.</string>
|
||||
|
||||
<!-- 请求使用连续相机设备 -->
|
||||
<key>NSCameraUseContinuityCameraDeviceType</key>
|
||||
<string>Your usage description here</string>
|
||||
|
||||
<!-- High DPI -->>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
|
||||
<!-- 其他权限和配置可以在这里添加 -->
|
||||
</dict>
|
||||
</plist>
|
||||
177
LICENSE
@@ -1,20 +1,165 @@
|
||||
The MIT License (MIT)
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (c) 2023 The Continuous Desk Authors
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
301
README.md
@@ -1,67 +1,300 @@
|
||||
# Continuous Desk
|
||||
# CrossDesk
|
||||
|
||||
#### More than remote desktop
|
||||
[]()
|
||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
||||
[](https://github.com/kunkundi/crossdesk/actions)
|
||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
----
|
||||
[中文](README_CN.md) / [English](README.md)
|
||||
[ [English](README_EN.md) / 中文 ]
|
||||
|
||||

|
||||

|
||||
|
||||
# Intro
|
||||
## 简介
|
||||
|
||||
Continuous Desk is a lightweight cross-platform remote desktop. It allows multiple users to remotely control the same computer at the same time. In addition to desktop image transmission, it also supports end-to-end voice transmission, providing collaboration capabilities on the basis of remote desktop.
|
||||
CrossDesk 是一个轻量级的跨平台远程桌面软件。
|
||||
|
||||
Continuous Desk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)).
|
||||
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))等基础能力。
|
||||
|
||||
## Usage
|
||||
|
||||
Enter the remote desktop ID in the 'REMOTE ID' field on the menu bar, and click 'Connect' button to initiate the remote connection.
|
||||
## 使用
|
||||
|
||||

|
||||
在菜单栏“对端ID”处输入远端桌面的ID,点击“→”即可发起远程连接。
|
||||
|
||||
If the remote desktop is set with a connection password, the local end needs to enter the correct password to initiate the remote connection. If the password is incorrect, an "Incorrect password" alert will appear in the status bar.
|
||||

|
||||
|
||||

|
||||
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。
|
||||
|
||||
After connection successfully established, the status bar will display the message "ClientConnected."
|
||||

|
||||
|
||||

|
||||
发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。
|
||||

|
||||
|
||||
## How to build
|
||||
## 如何编译
|
||||
|
||||
Requirements:
|
||||
依赖:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
- [vcpkg](https://vcpkg.io/en/getting-started)
|
||||
|
||||
Following packages need to be installed on Linux:
|
||||
Linux环境下需安装以下包:
|
||||
|
||||
```
|
||||
sudo apt-get install -y nvidia-cuda-toolkit libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev libxv-dev libasound2-dev libsndio-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
|
||||
```
|
||||
|
||||
Commands:
|
||||
编译
|
||||
```
|
||||
git clone https://github.com/dijunkun/continuous-desk
|
||||
git clone https://github.com/kunkundi/crossdesk.git
|
||||
|
||||
cd continuous-desk
|
||||
cd crossdesk
|
||||
|
||||
git submodule init
|
||||
|
||||
git submodule update
|
||||
|
||||
xmake b remote_desk
|
||||
```
|
||||
Run:
|
||||
```
|
||||
# Windows/MacOS
|
||||
xmake r remote_desk
|
||||
|
||||
# root privileges are required on Linux
|
||||
./remote_desk
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
运行
|
||||
```
|
||||
xmake r crossdesk
|
||||
```
|
||||
|
||||
Continuous Desk is licenced under MIT, and some third-party libraries are distributed under their licenses.
|
||||
### 无 CUDA 环境下的开发支持
|
||||
|
||||
对于**未安装 CUDA 环境的 Linux 开发者**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
|
||||
|
||||
进入容器,下载工程后执行:
|
||||
```
|
||||
export CUDA_PATH=/usr/local/cuda
|
||||
export XMAKE_GLOBALDIR=/data
|
||||
|
||||
xmake b --root -vy crossdesk
|
||||
```
|
||||
|
||||
对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 CUDA 编译环境:
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
安装完成后执行:
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
输出如下:
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入:
|
||||
```
|
||||
set CUDA_PATH=path_to_cuda_installdir
|
||||
```
|
||||
重新执行:
|
||||
```
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### 注意
|
||||
运行时如果客户端状态栏显示 **未连接服务器**,请先在 [CrossDesk 官方网站](https://www.crossdesk.cn/) 安装客户端,以便在环境中安装所需的证书文件。
|
||||
|
||||
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
|
||||
|
||||
## 关于 Xmake
|
||||
|
||||
#### 安装 Xmake
|
||||
使用 curl:
|
||||
```
|
||||
curl -fsSL https://xmake.io/shget.text | bash
|
||||
```
|
||||
使用 wget:
|
||||
```
|
||||
wget https://xmake.io/shget.text -O - | bash
|
||||
```
|
||||
使用 powershell:
|
||||
```
|
||||
irm https://xmake.io/psget.text | iex
|
||||
```
|
||||
|
||||
#### 编译选项
|
||||
```
|
||||
# 切换编译模式
|
||||
xmake f -m debug/release
|
||||
|
||||
# 可选编译参数
|
||||
-r :重新构建目标
|
||||
-v :显示详细的构建日志
|
||||
-y :自动确认提示
|
||||
|
||||
# 示例
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### 运行选项
|
||||
```
|
||||
# 使用调试模式运行
|
||||
xmake r -d crossdesk
|
||||
```
|
||||
更多使用方法可参考 [Xmake官方文档](https://xmake.io/guide/quick-start.html) 。
|
||||
|
||||
## 自托管服务器
|
||||
推荐使用Docker部署CrossDesk Server。
|
||||
```
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e CROSSDESK_SERVER_PORT=9099 \
|
||||
-v /path/to/your/certs:/crossdesk-server/certs \
|
||||
-v /path/to/your/db:/crossdesk-server/db \
|
||||
-v /path/to/your/logs:/crossdesk-server/logs \
|
||||
crossdesk/crossdesk-server:latest
|
||||
```
|
||||
|
||||
上述命令中,用户需注意的参数如下:
|
||||
|
||||
- EXTERNAL_IP:服务器公网 IP , 对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器地址**
|
||||
|
||||
- INTERNAL_IP:服务器内网 IP
|
||||
|
||||
- CROSSDESK_SERVER_PORT:自托管服务使用的端口,对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口**
|
||||
|
||||
- /path/to/your/certs:证书文件目录
|
||||
|
||||
- /path/to/your/db:CrossDesk Server 设备管理数据库
|
||||
|
||||
- /path/to/your/logs:日志目录
|
||||
|
||||
**注意**:
|
||||
- **/path/to/your/ 是示例路径,请替换为你自己的实际路径。挂载的目录必须事先创建好,否则容器会报错。**
|
||||
- **服务器需开放端口:3478/udp,3478/tcp,30000-60000/udp,CROSSDESK_SERVER_PORT/tcp,443/tcp。**
|
||||
|
||||
## 证书文件
|
||||
客户端需加载根证书文件,服务端需加载服务器私钥和服务器证书文件。
|
||||
|
||||
如果已有SSL证书的用户,可以忽略下面的证书生成步骤。
|
||||
|
||||
对于无证书的用户,可使用下面的脚本自行生成证书文件:
|
||||
```
|
||||
# 创建证书生成脚本
|
||||
vim generate_certs.sh
|
||||
```
|
||||
拷贝到脚本中
|
||||
```
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 检查参数
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <SERVER_IP>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVER_IP="$1"
|
||||
|
||||
# 文件名
|
||||
ROOT_KEY="crossdesk.cn_root.key"
|
||||
ROOT_CERT="crossdesk.cn_root.crt"
|
||||
SERVER_KEY="crossdesk.cn.key"
|
||||
SERVER_CSR="crossdesk.cn.csr"
|
||||
SERVER_CERT="crossdesk.cn_bundle.crt"
|
||||
FULLCHAIN_CERT="crossdesk.cn_fullchain.crt"
|
||||
|
||||
# 证书主题
|
||||
SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP"
|
||||
|
||||
# 1. 生成根证书
|
||||
echo "Generating root private key..."
|
||||
openssl genrsa -out "$ROOT_KEY" 4096
|
||||
|
||||
echo "Generating self-signed root certificate..."
|
||||
openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ"
|
||||
|
||||
# 2. 生成服务器私钥
|
||||
echo "Generating server private key..."
|
||||
openssl genrsa -out "$SERVER_KEY" 2048
|
||||
|
||||
# 3. 生成服务器 CSR
|
||||
echo "Generating server CSR..."
|
||||
openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ"
|
||||
|
||||
# 4. 生成临时 OpenSSL 配置文件,加入 SAN
|
||||
SAN_CONF="san.cnf"
|
||||
cat > $SAN_CONF <<EOL
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
prompt = no
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = CN
|
||||
ST = Zhejiang
|
||||
L = Hangzhou
|
||||
O = CrossDesk
|
||||
OU = CrossDesk
|
||||
CN = $SERVER_IP
|
||||
|
||||
[ req_ext ]
|
||||
subjectAltName = IP:$SERVER_IP
|
||||
EOL
|
||||
|
||||
# 5. 用根证书签发服务器证书(包含 SAN)
|
||||
echo "Signing server certificate with root certificate..."
|
||||
openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \
|
||||
-out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext
|
||||
|
||||
# 6. 生成完整链证书
|
||||
cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT"
|
||||
|
||||
# 7. 清理中间文件
|
||||
rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT"
|
||||
|
||||
echo "Generation complete. Deployment files:"
|
||||
echo " Client root certificate: $ROOT_CERT"
|
||||
echo " Server private key: $SERVER_KEY"
|
||||
echo " Server certificate: $SERVER_CERT"
|
||||
```
|
||||
执行
|
||||
```
|
||||
chmod +x generate_certs.sh
|
||||
./generate_certs.sh 服务器公网IP
|
||||
|
||||
# 例如 ./generate_certs.sh 111.111.111.111
|
||||
```
|
||||
输出如下:
|
||||
```
|
||||
Generating root private key...
|
||||
Generating self-signed root certificate...
|
||||
Generating server private key...
|
||||
Generating server CSR...
|
||||
Signing server certificate with root certificate...
|
||||
Certificate request self-signature ok
|
||||
subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx
|
||||
cleaning up intermediate files...
|
||||
Generation complete. Deployment files::
|
||||
Client root certificate:: crossdesk.cn_root.crt
|
||||
Server private key: crossdesk.cn.key
|
||||
Server certificate: crossdesk.cn_bundle.crt
|
||||
```
|
||||
|
||||
#### 服务端
|
||||
将 **crossdesk.cn.key** 和 **crossdesk.cn_bundle.crt** 放置到 **/path/to/your/certs** 目录下。
|
||||
|
||||
#### 客户端
|
||||
1. 点击右上角设置进入设置页面。<br>
|
||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
||||
|
||||
3. 点击点击**自托管服务器配置**。<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
||||
|
||||
5. 在**证书文件路径**选择框中找到 **crossdesk.cn_root.crt** 的存放路径,选中 **crossdesk.cn_root.crt**,点击确认。<br><br>
|
||||
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
|
||||
|
||||
7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||
|
||||
# 常见问题
|
||||
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。
|
||||
67
README_CN.md
@@ -1,67 +0,0 @@
|
||||
# Continuous Desk
|
||||
|
||||
#### 不止远程桌面
|
||||
|
||||
----
|
||||
[English](README.md) / [中文](README_CN.md)
|
||||
|
||||

|
||||
|
||||
## 简介
|
||||
|
||||
Continuous Desk 是一个轻量级的跨平台远程桌面软件。它允许多个用户在同一时间远程操控同一台电脑。除桌面图像传输外,它还支持端到端的语音传输,在远程桌面基础上提供额外的协作能力。
|
||||
|
||||
Continuous Desk 是 [Projectx](https://github.com/dijunkun/projectx) 实时音视频传输库的实验性应用。Projectx 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)),视频软硬编解码(H264),音频编解码([Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制([TCP over UDP](https://libnice.freedesktop.org/))等基础能力。
|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
在菜单栏“REMOTE ID”处输入远端桌面的ID,点击“Connect”即可发起远程连接。
|
||||
|
||||

|
||||
|
||||
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。密码错误时,状态栏会出现“Incorrect password”告警提示。
|
||||
|
||||

|
||||
|
||||
连接成功建立后,状态栏会有“ClientConnected”相关字样。
|
||||
|
||||

|
||||
|
||||
## 编译
|
||||
|
||||
依赖:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
- [vcpkg](https://vcpkg.io/en/getting-started)
|
||||
|
||||
Linux环境下需安装以下包:
|
||||
|
||||
```
|
||||
sudo apt-get install -y nvidia-cuda-toolkit libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev libxv-dev libasound2-dev libsndio-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
编译命令
|
||||
```
|
||||
git clone https://github.com/dijunkun/continuous-desk
|
||||
|
||||
cd continuous-desk
|
||||
|
||||
git submodule init
|
||||
|
||||
git submodule update
|
||||
|
||||
xmake b remote_desk
|
||||
```
|
||||
运行
|
||||
```
|
||||
# Windows/MacOS
|
||||
xmake r remote_desk
|
||||
|
||||
# Linux下需使用root权限运行
|
||||
./remote_desk
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
Continuous Desk 使用 MIT 许可证,其中使用到的第三方库根据自身许可证进行分发。
|
||||
305
README_EN.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# CrossDesk
|
||||
|
||||
[]()
|
||||
[](https://www.gnu.org/licenses/lgpl-3.0)
|
||||
[](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
|
||||
[](https://github.com/kunkundi/crossdesk/actions)
|
||||
[](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
[ [中文](README.md) / English ]
|
||||
|
||||

|
||||
|
||||
# Intro
|
||||
|
||||
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)).
|
||||
|
||||
## Usage
|
||||
|
||||
Enter the remote desktop ID in the menu bar’s “Remote ID” field and click “→” to initiate a remote connection.
|
||||
|
||||

|
||||
|
||||
If the remote desktop requires a connection password, you must enter the correct password on your side to successfully establish the connection.
|
||||
|
||||

|
||||
|
||||
Before connecting, you can customize configuration options in the settings, such as language and video encoding format.
|
||||
|
||||

|
||||
|
||||
## How to build
|
||||
|
||||
Requirements:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
|
||||
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 libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
Build:
|
||||
```
|
||||
git clone https://github.com/kunkundi/crossdesk.git
|
||||
|
||||
cd crossdesk
|
||||
|
||||
git submodule init
|
||||
|
||||
git submodule update
|
||||
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
Run:
|
||||
```
|
||||
xmake r crossdesk
|
||||
```
|
||||
|
||||
#### Development Without CUDA Environment
|
||||
|
||||
For **Linux developers who do not have a CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
|
||||
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
|
||||
|
||||
After entering the container, download the project and run:
|
||||
```
|
||||
export CUDA_PATH=/usr/local/cuda
|
||||
export XMAKE_GLOBALDIR=/data
|
||||
|
||||
xmake b --root -vy crossdesk
|
||||
```
|
||||
|
||||
For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment:
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
After the installation is complete, execute:
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
The output will look like this:
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
From the output above, locate the CUDA installation directory — this is the path pointed to by installdir.
|
||||
Add this path to your system environment variable CUDA_PATH, or set it in the terminal using:
|
||||
```
|
||||
set CUDA_PATH=path_to_cuda_installdir:
|
||||
```
|
||||
Then re-run:
|
||||
```
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### Notice
|
||||
If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment.
|
||||
|
||||
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
|
||||
|
||||
## About Xmake
|
||||
#### Installing Xmake
|
||||
|
||||
You can install Xmake using one of the following methods:
|
||||
|
||||
Using curl:
|
||||
```
|
||||
curl -fsSL https://xmake.io/shget.text | bash
|
||||
```
|
||||
Using wget:
|
||||
```
|
||||
wget https://xmake.io/shget.text -O - | bash
|
||||
```
|
||||
Using powershell:
|
||||
```
|
||||
irm https://xmake.io/psget.text | iex
|
||||
```
|
||||
|
||||
#### Build Options
|
||||
```
|
||||
# Switch build mode
|
||||
xmake f -m debug/release
|
||||
|
||||
# Optional build parameters
|
||||
-r : Rebuild the target
|
||||
-v : Show detailed build logs
|
||||
-y : Automatically confirm prompts
|
||||
|
||||
# Example
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### Run Options
|
||||
```
|
||||
# Run in debug mode
|
||||
xmake r -d crossdesk
|
||||
```
|
||||
|
||||
For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) .
|
||||
|
||||
## Self-Hosted Server
|
||||
It is recommended to deploy CrossDesk Server using Docker.
|
||||
```
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=150.158.81.30 \
|
||||
-e INTERNAL_IP=10.0.4.3 \
|
||||
-e CROSSDESK_SERVER_PORT=9099 \
|
||||
-v /path/to/your/certs:/crossdesk-server/certs \
|
||||
-v /path/to/your/db:/crossdesk-server/db \
|
||||
-v /path/to/your/logs:/crossdesk-server/logs \
|
||||
crossdesk/crossdesk-server:latest
|
||||
```
|
||||
|
||||
The parameters you need to pay attention to are as follows:
|
||||
|
||||
- **EXTERNAL_IP**: The server's public IP, corresponding to the **Server Address** in the CrossDesk client **Self-Hosted Server Configuration**.
|
||||
|
||||
- **INTERNAL_IP**: The server's internal IP.
|
||||
|
||||
- **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
|
||||
|
||||
- **/path/to/your/certs**: Directory for certificate files.
|
||||
|
||||
- **/path/to/your/db**: CrossDesk Server device management database.
|
||||
|
||||
- **/path/to/your/logs**: Log directory.
|
||||
|
||||
**Note**:
|
||||
- **/path/to/your/ is an example path; please replace it with your actual path. The mounted directories must be created in advance, otherwise the container will fail.**
|
||||
- **The server must open the following ports: 3478/udp, 3478/tcp, 30000-60000/udp, CROSSDESK_SERVER_PORT/tcp, 443/tcp.**
|
||||
|
||||
## Certificate Files
|
||||
The client needs to load the root certificate, and the server needs to load the server private key and server certificate.
|
||||
|
||||
If you already have an SSL certificate, you can skip the following certificate generation steps.
|
||||
|
||||
For users without a certificate, you can use the script below to generate the certificate files:
|
||||
```
|
||||
# Create certificate generation script
|
||||
vim generate_certs.sh
|
||||
```
|
||||
Copy the following into the script:
|
||||
```
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Check arguments
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <SERVER_IP>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVER_IP="$1"
|
||||
|
||||
# Filenames
|
||||
ROOT_KEY="crossdesk.cn_root.key"
|
||||
ROOT_CERT="crossdesk.cn_root.crt"
|
||||
SERVER_KEY="crossdesk.cn.key"
|
||||
SERVER_CSR="crossdesk.cn.csr"
|
||||
SERVER_CERT="crossdesk.cn_bundle.crt"
|
||||
FULLCHAIN_CERT="crossdesk.cn_fullchain.crt"
|
||||
|
||||
# Certificate subject
|
||||
SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP"
|
||||
|
||||
# 1. Generate root certificate
|
||||
echo "Generating root private key..."
|
||||
openssl genrsa -out "$ROOT_KEY" 4096
|
||||
|
||||
echo "Generating self-signed root certificate..."
|
||||
openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ"
|
||||
|
||||
# 2. Generate server private key
|
||||
echo "Generating server private key..."
|
||||
openssl genrsa -out "$SERVER_KEY" 2048
|
||||
|
||||
# 3. Generate server CSR
|
||||
echo "Generating server CSR..."
|
||||
openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ"
|
||||
|
||||
# 4. Create temporary OpenSSL config file with SAN
|
||||
SAN_CONF="san.cnf"
|
||||
cat > $SAN_CONF <<EOL
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
prompt = no
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = CN
|
||||
ST = Zhejiang
|
||||
L = Hangzhou
|
||||
O = CrossDesk
|
||||
OU = CrossDesk
|
||||
CN = $SERVER_IP
|
||||
|
||||
[ req_ext ]
|
||||
subjectAltName = IP:$SERVER_IP
|
||||
EOL
|
||||
|
||||
# 5. Sign server certificate with root certificate (including SAN)
|
||||
echo "Signing server certificate with root certificate..."
|
||||
openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \
|
||||
-out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext
|
||||
|
||||
# 6. Generate full chain certificate
|
||||
cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT"
|
||||
|
||||
# 7. Clean up intermediate files
|
||||
rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT"
|
||||
|
||||
echo "Generation complete. Deployment files:"
|
||||
echo " Client root certificate: $ROOT_CERT"
|
||||
echo " Server private key: $SERVER_KEY"
|
||||
echo " Server certificate: $SERVER_CERT"
|
||||
```
|
||||
Execute:
|
||||
```
|
||||
chmod +x generate_certs.sh
|
||||
./generate_certs.sh EXTERNAL_IP
|
||||
|
||||
# example ./generate_certs.sh 111.111.111.111
|
||||
```
|
||||
Expected output:
|
||||
```
|
||||
Generating root private key...
|
||||
Generating self-signed root certificate...
|
||||
Generating server private key...
|
||||
Generating server CSR...
|
||||
Signing server certificate with root certificate...
|
||||
Certificate request self-signature ok
|
||||
subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx
|
||||
cleaning up intermediate files...
|
||||
Generation complete. Deployment files::
|
||||
Client root certificate:: crossdesk.cn_root.crt
|
||||
Server private key: crossdesk.cn.key
|
||||
Server certificate: crossdesk.cn_bundle.crt
|
||||
```
|
||||
|
||||
#### Server Side
|
||||
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
|
||||
|
||||
#### Client Side
|
||||
1. Click the settings icon in the top-right corner to enter the settings page.<br>
|
||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
||||
|
||||
2. Click **Self-Hosted Server Configuration**.<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
||||
|
||||
3. In the **Certificate File Path** selection, locate and select the **crossdesk.cn_root.crt** file.<br><br>
|
||||
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
|
||||
|
||||
4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||
|
||||
# FAQ
|
||||
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
|
||||
@@ -1,19 +0,0 @@
|
||||
[signal server]
|
||||
ip = 150.158.81.30
|
||||
port = 9099
|
||||
|
||||
[stun server]
|
||||
ip = 150.158.81.30
|
||||
port = 3478
|
||||
|
||||
[turn server]
|
||||
ip = 150.158.81.30
|
||||
port = 3478
|
||||
username = dijunkun
|
||||
password = dijunkunpw
|
||||
|
||||
[hardware acceleration]
|
||||
turn_on = false
|
||||
|
||||
[av1 encoding]
|
||||
turn_on = true
|
||||
33
docs/FAQ.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 常见问题(FAQ)
|
||||
|
||||
欢迎来到 **CrossDesk 常见问题** 页面!
|
||||
这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。
|
||||
|
||||
---
|
||||
|
||||
### Q1. 对等连接失败
|
||||
**A:**
|
||||
打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。
|
||||
|
||||
<img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" />
|
||||
|
||||
由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8)
|
||||
|
||||
### Q2. Windows 无 CUDA 环境下编译
|
||||
**A:**
|
||||
运行下面的命令安装 CUDA 编译环境。
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
安装完成后执行
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
输出如下
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir,重新执行 xmake b -vy crossdesk 即可。
|
||||
[Issue #6](https://github.com/kunkundi/crossdesk/issues/6)
|
||||
|
||||
---
|
||||
@@ -1 +0,0 @@
|
||||
IDI_ICON1 ICON "app_icon.ico"
|
||||
|
Before Width: | Height: | Size: 687 B |
BIN
icons/linux/crossdesk_128x128.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
icons/linux/crossdesk_16x16.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
icons/linux/crossdesk_24x24.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/linux/crossdesk_256x256.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
icons/linux/crossdesk_32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/linux/crossdesk_48x48.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/linux/crossdesk_512x512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
icons/linux/crossdesk_64x64.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
icons/linux/crossdesk_96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
icons/macos/crossdesk.icns
Normal file
BIN
icons/windows/crossdesk.ico
Normal file
|
After Width: | Height: | Size: 84 KiB |
120
scripts/linux/pkg_amd64.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
|
||||
APP_VERSION="$1"
|
||||
ARCHITECTURE="amd64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR"
|
||||
|
||||
cp build/linux/x86_64/release/crossdesk "$BIN_DIR/$PKG_NAME"
|
||||
chmod +x "$BIN_DIR/$PKG_NAME"
|
||||
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
cp "icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $APP_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
|
||||
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
|
||||
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
|
||||
libsndio7.0, libxcb-shm0, libpulse0
|
||||
Recommends: nvidia-cuda-toolkit
|
||||
Priority: optional
|
||||
Section: utils
|
||||
EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
Icon=$PKG_NAME
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/bin/$PKG_NAME || true
|
||||
rm -f /usr/bin/$APP_NAME || true
|
||||
rm -f /usr/share/applications/$PKG_NAME.desktop || true
|
||||
rm -rf /opt/$PKG_NAME/certs || true
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
cat > "$DEBIAN_DIR/postinst" << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CERT_SRC="/opt/crossdesk/certs"
|
||||
CERT_FILE="crossdesk.cn_root.crt"
|
||||
|
||||
for user_home in /home/*; do
|
||||
[ -d "$user_home" ] || continue
|
||||
username=$(basename "$user_home")
|
||||
config_dir="$user_home/.config/CrossDesk/certs"
|
||||
target="$config_dir/$CERT_FILE"
|
||||
|
||||
if [ ! -f "$target" ]; then
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$target" || true
|
||||
chown -R "$username:$username" "$user_home/.config/CrossDesk" || true
|
||||
echo "✔ Installed cert for $username at $target"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "/root" ]; then
|
||||
config_dir="/root/.config/CrossDesk/certs"
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true
|
||||
chown -R root:root /root/.config/CrossDesk || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
OUTPUT_FILE="${PKG_NAME}-linux-${ARCHITECTURE}-${APP_VERSION}.deb"
|
||||
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package created: $OUTPUT_FILE"
|
||||
120
scripts/linux/pkg_arm64.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
|
||||
APP_VERSION="$1"
|
||||
ARCHITECTURE="arm64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR"
|
||||
|
||||
cp build/linux/arm64/release/crossdesk "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/$PKG_NAME"
|
||||
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
cp "icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $APP_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
|
||||
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
|
||||
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
|
||||
libsndio7.0, libxcb-shm0, libpulse0
|
||||
Priority: optional
|
||||
Section: utils
|
||||
EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
Icon=$PKG_NAME
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/bin/$PKG_NAME || true
|
||||
rm -f /usr/bin/$APP_NAME || true
|
||||
rm -f /usr/share/applications/$PKG_NAME.desktop || true
|
||||
rm -rf /opt/$PKG_NAME/certs || true
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
cat > "$DEBIAN_DIR/postinst" << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CERT_SRC="/opt/crossdesk/certs"
|
||||
CERT_FILE="crossdesk.cn_root.crt"
|
||||
|
||||
for user_home in /home/*; do
|
||||
[ -d "$user_home" ] || continue
|
||||
username=$(basename "$user_home")
|
||||
config_dir="$user_home/.config/CrossDesk/certs"
|
||||
target="$config_dir/$CERT_FILE"
|
||||
|
||||
if [ ! -f "$target" ]; then
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$target" || true
|
||||
chown -R "$username:$username" "$user_home/.config/CrossDesk" || true
|
||||
echo "✔ Installed cert for $username at $target"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "/root" ]; then
|
||||
config_dir="/root/.config/CrossDesk/certs"
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true
|
||||
chown -R root:root /root/.config/CrossDesk || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
OUTPUT_FILE="crossdesk-linux-arm64-$APP_VERSION.deb"
|
||||
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package created: $OUTPUT_FILE"
|
||||
125
scripts/macosx/pkg_arm64.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
|
||||
APP_VERSION="$1"
|
||||
PLATFORM="macos"
|
||||
ARCH="arm64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
CERTS_SOURCE="certs"
|
||||
CERT_NAME="crossdesk.cn_root.crt"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?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>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
mkdir -p scripts
|
||||
|
||||
cat > scripts/postinstall <<'EOF'
|
||||
#!/bin/bash
|
||||
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
|
||||
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
|
||||
|
||||
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
|
||||
|
||||
mkdir -p "$DEST"
|
||||
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x scripts/postinstall
|
||||
|
||||
pkgbuild \
|
||||
--root "${CERTS_SOURCE}" \
|
||||
--identifier "${IDENTIFIER}.certs" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Library/Application Support/CrossDesk/certs" \
|
||||
--scripts scripts \
|
||||
build_pkg_temp/${APP_NAME}-certs.pkg
|
||||
|
||||
productbuild \
|
||||
--package build_pkg_temp/${APP_NAME}-component.pkg \
|
||||
--package build_pkg_temp/${APP_NAME}-certs.pkg \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
rm -rf build_pkg_temp scripts ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
125
scripts/macosx/pkg_x64.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
|
||||
APP_VERSION="$1"
|
||||
PLATFORM="macos"
|
||||
ARCH="x64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
CERTS_SOURCE="certs"
|
||||
CERT_NAME="crossdesk.cn_root.crt"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?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>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
mkdir -p scripts
|
||||
|
||||
cat > scripts/postinstall <<'EOF'
|
||||
#!/bin/bash
|
||||
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
|
||||
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
|
||||
|
||||
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
|
||||
|
||||
mkdir -p "$DEST"
|
||||
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x scripts/postinstall
|
||||
|
||||
pkgbuild \
|
||||
--root "${CERTS_SOURCE}" \
|
||||
--identifier "${IDENTIFIER}.certs" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Library/Application Support/CrossDesk/certs" \
|
||||
--scripts scripts \
|
||||
build_pkg_temp/${APP_NAME}-certs.pkg
|
||||
|
||||
productbuild \
|
||||
--package build_pkg_temp/${APP_NAME}-component.pkg \
|
||||
--package build_pkg_temp/${APP_NAME}-certs.pkg \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
rm -rf build_pkg_temp scripts ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
43
scripts/windows/crossdesk.manifest
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<!-- 应用程序标识 -->
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
processorArchitecture="*"
|
||||
name="CrossDesk"
|
||||
type="win32" />
|
||||
|
||||
<!-- 描述信息 -->
|
||||
<description>CrossDesk Application</description>
|
||||
|
||||
<!-- 权限:要求管理员运行 -->
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<!-- DPI 感知设置:支持高分屏 -->
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- Windows Vista/7 风格 DPI 感知 -->
|
||||
<dpiAware>true/pm</dpiAware>
|
||||
<!-- Windows 10/11 高级 DPI 感知 -->
|
||||
<dpiAwareness>PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<!-- Windows 兼容性声明 -->
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- 支持 Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- 支持 Windows 11(向下兼容 Win10 GUID) -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
</assembly>
|
||||
BIN
scripts/windows/nsProcess.dll
Normal file
164
scripts/windows/nsis_script.nsi
Normal file
@@ -0,0 +1,164 @@
|
||||
; Set search path
|
||||
!addincludedir "${__FILEDIR__}"
|
||||
|
||||
; Installer initial constants
|
||||
!define PRODUCT_NAME "CrossDesk"
|
||||
!define PRODUCT_VERSION "${VERSION}"
|
||||
!define PRODUCT_PUBLISHER "CrossDesk"
|
||||
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
|
||||
!define APP_NAME "CrossDesk"
|
||||
!define UNINSTALL_REG_KEY "CrossDesk"
|
||||
|
||||
; Installer icon path
|
||||
!define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico"
|
||||
|
||||
; Certificate path
|
||||
!define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt"
|
||||
|
||||
; Compression settings
|
||||
SetCompressor /FINAL lzma
|
||||
|
||||
; Request admin privileges (needed to write HKLM)
|
||||
RequestExecutionLevel admin
|
||||
|
||||
; ------ MUI Modern UI Definition ------
|
||||
!include "MUI.nsh"
|
||||
!define MUI_ABORTWARNING
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
; Add run-after-install option
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}"
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
|
||||
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
|
||||
; ------ End of MUI Definition ------
|
||||
|
||||
; Include LogicLib for process handling
|
||||
!include "LogicLib.nsh"
|
||||
|
||||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||
OutFile "crossdesk-win-x64-${PRODUCT_VERSION}.exe"
|
||||
InstallDir "$PROGRAMFILES\CrossDesk"
|
||||
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" "InstallDir"
|
||||
ShowInstDetails show
|
||||
|
||||
Section "MainSection"
|
||||
; Check if CrossDesk is running
|
||||
StrCpy $1 "crossdesk.exe"
|
||||
|
||||
nsProcess::_FindProcess "$1"
|
||||
Pop $R0
|
||||
${If} $R0 = 0 ;
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and continue the installation?" IDYES closeApp IDNO cancelInstall
|
||||
${Else}
|
||||
Goto installApp
|
||||
${EndIf}
|
||||
|
||||
closeApp:
|
||||
nsProcess::_KillProcess "$1"
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
Goto installApp
|
||||
|
||||
cancelInstall:
|
||||
SetDetailsPrint both
|
||||
MessageBox MB_ICONEXCLAMATION|MB_OK "Installation has been aborted."
|
||||
Abort
|
||||
|
||||
installApp:
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite ifnewer
|
||||
|
||||
; Main application executable path
|
||||
File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
|
||||
|
||||
; Copy icon file to installation directory
|
||||
File "${MUI_ICON}"
|
||||
|
||||
; Write uninstall information
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\crossdesk.ico"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1
|
||||
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "InstallDir" "$INSTDIR"
|
||||
SectionEnd
|
||||
|
||||
; After installation
|
||||
Section -Post
|
||||
ExecWait '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\mt.exe" -manifest "$INSTDIR\crossdesk.manifest" -outputresource:"$INSTDIR\crossdesk.exe";1'
|
||||
SectionEnd
|
||||
|
||||
Section "Cert"
|
||||
SetOutPath "$APPDATA\CrossDesk\certs"
|
||||
File /r "${CERT_FILE}"
|
||||
SectionEnd
|
||||
|
||||
Section -AdditionalIcons
|
||||
; Desktop shortcut
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
|
||||
|
||||
; Start menu shortcut
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
; Check if CrossDesk is running
|
||||
StrCpy $1 "crossdesk.exe"
|
||||
|
||||
nsProcess::_FindProcess "$1"
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and uninstall?" IDYES closeApp IDNO cancelUninstall
|
||||
${Else}
|
||||
Goto uninstallApp
|
||||
${EndIf}
|
||||
|
||||
closeApp:
|
||||
nsProcess::_KillProcess "$1"
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
Goto uninstallApp
|
||||
|
||||
cancelUninstall:
|
||||
SetDetailsPrint both
|
||||
MessageBox MB_ICONEXCLAMATION|MB_OK "Uninstallation has been aborted."
|
||||
Abort
|
||||
|
||||
uninstallApp:
|
||||
; Delete main executable and uninstaller
|
||||
Delete "$INSTDIR\crossdesk.exe"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
; Recursively delete installation directory
|
||||
RMDir /r "$INSTDIR"
|
||||
|
||||
; Delete desktop and start menu shortcuts
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
|
||||
|
||||
; Delete registry uninstall entry
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
|
||||
|
||||
; Delete remembered install dir
|
||||
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
|
||||
|
||||
; Recursively delete CrossDesk folder in user AppData
|
||||
RMDir /r "$APPDATA\CrossDesk"
|
||||
RMDir /r "$LOCALAPPDATA\CrossDesk"
|
||||
SectionEnd
|
||||
|
||||
; ------ Functions ------
|
||||
Function LaunchApp
|
||||
Exec "$INSTDIR\crossdesk.exe"
|
||||
FunctionEnd
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifdef _WIN32
|
||||
#ifdef REMOTE_DESK_DEBUG
|
||||
#ifdef DESK_PORT_DEBUG
|
||||
#pragma comment(linker, "/subsystem:\"console\"")
|
||||
#else
|
||||
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
|
||||
@@ -10,9 +10,7 @@
|
||||
#include "render.h"
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) {
|
||||
LOG_INFO("Remote desk");
|
||||
Render render;
|
||||
|
||||
render.Run();
|
||||
|
||||
return 0;
|
||||
44
src/common/display_info.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-05-15
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DISPLAY_INFO_H_
|
||||
#define _DISPLAY_INFO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
class DisplayInfo {
|
||||
public:
|
||||
DisplayInfo(std::string name, int left, int top, int right, int bottom)
|
||||
: name(name), left(left), top(top), right(right), bottom(bottom) {
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
}
|
||||
DisplayInfo(void* handle, std::string name, bool is_primary, int left,
|
||||
int top, int right, int bottom)
|
||||
: handle(handle),
|
||||
name(name),
|
||||
is_primary(is_primary),
|
||||
left(left),
|
||||
top(top),
|
||||
right(right),
|
||||
bottom(bottom) {
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
}
|
||||
~DisplayInfo() {}
|
||||
|
||||
void* handle = nullptr;
|
||||
std::string name = "";
|
||||
bool is_primary = false;
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
int right = 0;
|
||||
int bottom = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
3644
src/config_center/SimpleIni.h
Normal file
@@ -1,45 +1,241 @@
|
||||
#include "config_center.h"
|
||||
|
||||
ConfigCenter::ConfigCenter() {}
|
||||
ConfigCenter::ConfigCenter(const std::string& config_path,
|
||||
const std::string& cert_file_path)
|
||||
: config_path_(config_path),
|
||||
cert_file_path_(cert_file_path),
|
||||
cert_file_path_default_(cert_file_path) {
|
||||
ini_.SetUnicode(true);
|
||||
Load();
|
||||
}
|
||||
|
||||
ConfigCenter::~ConfigCenter() {}
|
||||
|
||||
int ConfigCenter::Load() {
|
||||
SI_Error rc = ini_.LoadFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
Save();
|
||||
return -1;
|
||||
}
|
||||
|
||||
language_ = static_cast<LANGUAGE>(
|
||||
ini_.GetLongValue(section_, "language", static_cast<long>(language_)));
|
||||
|
||||
video_quality_ = static_cast<VIDEO_QUALITY>(ini_.GetLongValue(
|
||||
section_, "video_quality", static_cast<long>(video_quality_)));
|
||||
|
||||
video_frame_rate_ = static_cast<VIDEO_FRAME_RATE>(ini_.GetLongValue(
|
||||
section_, "video_frame_rate", static_cast<long>(video_frame_rate_)));
|
||||
|
||||
video_encode_format_ = static_cast<VIDEO_ENCODE_FORMAT>(
|
||||
ini_.GetLongValue(section_, "video_encode_format",
|
||||
static_cast<long>(video_encode_format_)));
|
||||
|
||||
hardware_video_codec_ = ini_.GetBoolValue(section_, "hardware_video_codec",
|
||||
hardware_video_codec_);
|
||||
|
||||
enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_);
|
||||
enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_);
|
||||
server_host_ = ini_.GetValue(section_, "server_host", server_host_.c_str());
|
||||
server_port_ = static_cast<int>(
|
||||
ini_.GetLongValue(section_, "server_port", server_port_));
|
||||
cert_file_path_ =
|
||||
ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
enable_self_hosted_ =
|
||||
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||
|
||||
enable_minimize_to_tray_ = ini_.GetBoolValue(
|
||||
section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::Save() {
|
||||
ini_.SetLongValue(section_, "language", static_cast<long>(language_));
|
||||
ini_.SetLongValue(section_, "video_quality",
|
||||
static_cast<long>(video_quality_));
|
||||
ini_.SetLongValue(section_, "video_frame_rate",
|
||||
static_cast<long>(video_frame_rate_));
|
||||
ini_.SetLongValue(section_, "video_encode_format",
|
||||
static_cast<long>(video_encode_format_));
|
||||
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
|
||||
ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
|
||||
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
|
||||
ini_.SetValue(section_, "server_host", server_host_.c_str());
|
||||
ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_));
|
||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||
ini_.SetBoolValue(section_, "enable_minimize_to_tray",
|
||||
enable_minimize_to_tray_);
|
||||
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// setters
|
||||
|
||||
int ConfigCenter::SetLanguage(LANGUAGE language) {
|
||||
language_ = language;
|
||||
ini_.SetLongValue(section_, "language", static_cast<long>(language_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) {
|
||||
video_quality_ = video_quality;
|
||||
ini_.SetLongValue(section_, "video_quality",
|
||||
static_cast<long>(video_quality_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate) {
|
||||
video_frame_rate_ = video_frame_rate;
|
||||
ini_.SetLongValue(section_, "video_frame_rate",
|
||||
static_cast<long>(video_frame_rate_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoEncodeFormat(
|
||||
VIDEO_ENCODE_FORMAT video_encode_format) {
|
||||
video_encode_format_ = video_encode_format;
|
||||
ini_.SetLongValue(section_, "video_encode_format",
|
||||
static_cast<long>(video_encode_format_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) {
|
||||
hardware_video_codec_ = hardware_video_codec;
|
||||
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetTurn(bool enable_turn) {
|
||||
enable_turn_ = enable_turn;
|
||||
ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; }
|
||||
int ConfigCenter::SetSrtp(bool enable_srtp) {
|
||||
enable_srtp_ = enable_srtp;
|
||||
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() {
|
||||
int ConfigCenter::SetServerHost(const std::string& server_host) {
|
||||
server_host_ = server_host;
|
||||
ini_.SetValue(section_, "server_host", server_host_.c_str());
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetServerPort(int server_port) {
|
||||
server_port_ = server_port;
|
||||
ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) {
|
||||
cert_file_path_ = cert_file_path;
|
||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
||||
enable_self_hosted_ = enable_self_hosted;
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) {
|
||||
enable_minimize_to_tray_ = enable_minimize_to_tray;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
|
||||
|
||||
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() const {
|
||||
return video_quality_;
|
||||
}
|
||||
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() {
|
||||
ConfigCenter::VIDEO_FRAME_RATE ConfigCenter::GetVideoFrameRate() const {
|
||||
return video_frame_rate_;
|
||||
}
|
||||
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() const {
|
||||
return video_encode_format_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; }
|
||||
bool ConfigCenter::IsHardwareVideoCodec() const {
|
||||
return hardware_video_codec_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsEnableTurn() { return enable_turn_; }
|
||||
bool ConfigCenter::IsEnableTurn() const { return enable_turn_; }
|
||||
|
||||
bool ConfigCenter::IsEnableSrtp() const { return enable_srtp_; }
|
||||
|
||||
std::string ConfigCenter::GetServerHost() const { return server_host_; }
|
||||
|
||||
int ConfigCenter::GetServerPort() const { return server_port_; }
|
||||
|
||||
std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; }
|
||||
|
||||
std::string ConfigCenter::GetDefaultServerHost() const {
|
||||
return server_host_default_;
|
||||
}
|
||||
|
||||
int ConfigCenter::GetDefaultServerPort() const { return server_port_default_; }
|
||||
|
||||
std::string ConfigCenter::GetDefaultCertFilePath() const {
|
||||
return cert_file_path_default_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
|
||||
|
||||
bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; }
|
||||
@@ -7,37 +7,78 @@
|
||||
#ifndef _CONFIG_CENTER_H_
|
||||
#define _CONFIG_CENTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "SimpleIni.h"
|
||||
|
||||
class ConfigCenter {
|
||||
public:
|
||||
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 };
|
||||
enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 };
|
||||
enum class VIDEO_ENCODE_FORMAT { AV1 = 0, H264 = 1 };
|
||||
enum class VIDEO_FRAME_RATE { FPS_30 = 0, FPS_60 = 1 };
|
||||
enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 };
|
||||
|
||||
public:
|
||||
ConfigCenter();
|
||||
explicit ConfigCenter(
|
||||
const std::string& config_path = "config.ini",
|
||||
const std::string& cert_file_path = "crossdesk.cn_root.crt");
|
||||
~ConfigCenter();
|
||||
|
||||
public:
|
||||
// write config
|
||||
int SetLanguage(LANGUAGE language);
|
||||
int SetVideoQuality(VIDEO_QUALITY video_quality);
|
||||
int SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate);
|
||||
int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format);
|
||||
int SetHardwareVideoCodec(bool hardware_video_codec);
|
||||
int SetTurn(bool enable_turn);
|
||||
int SetSrtp(bool enable_srtp);
|
||||
int SetServerHost(const std::string& server_host);
|
||||
int SetServerPort(int server_port);
|
||||
int SetCertFilePath(const std::string& cert_file_path);
|
||||
int SetSelfHosted(bool enable_self_hosted);
|
||||
int SetMinimizeToTray(bool enable_minimize_to_tray);
|
||||
|
||||
public:
|
||||
LANGUAGE GetLanguage();
|
||||
VIDEO_QUALITY GetVideoQuality();
|
||||
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat();
|
||||
bool IsHardwareVideoCodec();
|
||||
bool IsEnableTurn();
|
||||
// read config
|
||||
|
||||
LANGUAGE GetLanguage() const;
|
||||
VIDEO_QUALITY GetVideoQuality() const;
|
||||
VIDEO_FRAME_RATE GetVideoFrameRate() const;
|
||||
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat() const;
|
||||
bool IsHardwareVideoCodec() const;
|
||||
bool IsEnableTurn() const;
|
||||
bool IsEnableSrtp() const;
|
||||
std::string GetServerHost() const;
|
||||
int GetServerPort() const;
|
||||
std::string GetCertFilePath() const;
|
||||
std::string GetDefaultServerHost() const;
|
||||
int GetDefaultServerPort() const;
|
||||
std::string GetDefaultCertFilePath() const;
|
||||
bool IsSelfHosted() const;
|
||||
bool IsMinimizeToTray() const;
|
||||
|
||||
int Load();
|
||||
int Save();
|
||||
|
||||
private:
|
||||
// Default value should be same with parameters in localization.h
|
||||
std::string config_path_;
|
||||
std::string cert_file_path_;
|
||||
CSimpleIniA ini_;
|
||||
const char* section_ = "Settings";
|
||||
|
||||
LANGUAGE language_ = LANGUAGE::CHINESE;
|
||||
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
|
||||
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1;
|
||||
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30;
|
||||
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
|
||||
bool hardware_video_codec_ = false;
|
||||
bool enable_turn_ = false;
|
||||
bool enable_srtp_ = false;
|
||||
std::string server_host_ = "api.crossdesk.cn";
|
||||
int server_port_ = 9099;
|
||||
std::string server_host_default_ = "api.crossdesk.cn";
|
||||
int server_port_default_ = 9099;
|
||||
std::string cert_file_path_default_ = "";
|
||||
bool enable_self_hosted_ = false;
|
||||
bool enable_minimize_to_tray_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -9,17 +9,31 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "display_info.h"
|
||||
|
||||
typedef enum {
|
||||
mouse = 0,
|
||||
keyboard,
|
||||
audio_capture,
|
||||
host_infomation
|
||||
host_infomation,
|
||||
display_id,
|
||||
} ControlType;
|
||||
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
|
||||
typedef enum {
|
||||
move = 0,
|
||||
left_down,
|
||||
left_up,
|
||||
right_down,
|
||||
right_up,
|
||||
middle_down,
|
||||
middle_up,
|
||||
wheel_vertical,
|
||||
wheel_horizontal
|
||||
} MouseFlag;
|
||||
typedef enum { key_down = 0, key_up } KeyFlag;
|
||||
typedef struct {
|
||||
size_t x;
|
||||
size_t y;
|
||||
float x;
|
||||
float y;
|
||||
int s;
|
||||
MouseFlag flag;
|
||||
} Mouse;
|
||||
|
||||
@@ -31,6 +45,12 @@ typedef struct {
|
||||
typedef struct {
|
||||
char host_name[64];
|
||||
size_t host_name_size;
|
||||
char** display_list;
|
||||
size_t display_num;
|
||||
int* left;
|
||||
int* top;
|
||||
int* right;
|
||||
int* bottom;
|
||||
} HostInfo;
|
||||
|
||||
typedef struct {
|
||||
@@ -40,6 +60,7 @@ typedef struct {
|
||||
Key k;
|
||||
HostInfo i;
|
||||
bool a;
|
||||
int d;
|
||||
};
|
||||
} RemoteAction;
|
||||
|
||||
@@ -53,7 +74,7 @@ class DeviceController {
|
||||
public:
|
||||
// virtual int Init(int screen_width, int screen_height);
|
||||
// virtual int Destroy();
|
||||
// virtual int SendCommand(RemoteAction remote_action);
|
||||
// virtual int SendMouseCommand(RemoteAction remote_action);
|
||||
|
||||
// virtual int Hook();
|
||||
// virtual int Unhook();
|
||||
|
||||
@@ -1,15 +1,69 @@
|
||||
#include "keyboard_capturer.h"
|
||||
|
||||
KeyboardCapturer::KeyboardCapturer() {}
|
||||
#include "keyboard_converter.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
KeyboardCapturer::~KeyboardCapturer() {}
|
||||
static OnKeyAction g_on_key_action = nullptr;
|
||||
static void* g_user_ptr = nullptr;
|
||||
|
||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void *user_ptr) {
|
||||
static int KeyboardEventHandler(Display* display, XEvent* event) {
|
||||
if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) {
|
||||
KeySym keySym = XKeycodeToKeysym(display, event->xkey.keycode, 0);
|
||||
int key_code = XKeysymToKeycode(display, keySym);
|
||||
bool is_key_down = (event->xkey.type == KeyPress);
|
||||
|
||||
if (g_on_key_action) {
|
||||
g_on_key_action(key_code, is_key_down, g_user_ptr);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() { return 0; }
|
||||
KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) {
|
||||
display_ = XOpenDisplay(nullptr);
|
||||
if (!display_) {
|
||||
LOG_ERROR("Failed to open X display.");
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardCapturer::~KeyboardCapturer() {
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
}
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
||||
g_on_key_action = on_key_action;
|
||||
g_user_ptr = user_ptr;
|
||||
|
||||
XSelectInput(display_, DefaultRootWindow(display_),
|
||||
KeyPressMask | KeyReleaseMask);
|
||||
|
||||
while (running_) {
|
||||
XEvent event;
|
||||
XNextEvent(display_, &event);
|
||||
KeyboardEventHandler(display_, &event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
|
||||
if (!display_) {
|
||||
LOG_ERROR("Display not initialized.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) {
|
||||
int x11_key_code = vkCodeToX11KeySym[key_code];
|
||||
KeyCode keycode = XKeysymToKeycode(display_, x11_key_code);
|
||||
XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -7,6 +7,10 @@
|
||||
#ifndef _KEYBOARD_CAPTURER_H_
|
||||
#define _KEYBOARD_CAPTURER_H_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
#include <X11/keysym.h>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class KeyboardCapturer : public DeviceController {
|
||||
@@ -20,6 +24,9 @@ class KeyboardCapturer : public DeviceController {
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down);
|
||||
|
||||
private:
|
||||
Display *display_;
|
||||
Window root_;
|
||||
bool running_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -123,12 +123,44 @@ int KeyboardCapturer::Unhook() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline bool IsFunctionKey(int key_code) {
|
||||
switch (key_code) {
|
||||
case 0x7A:
|
||||
case 0x78:
|
||||
case 0x63:
|
||||
case 0x76:
|
||||
case 0x60:
|
||||
case 0x61:
|
||||
case 0x62:
|
||||
case 0x64:
|
||||
case 0x65:
|
||||
case 0x6D:
|
||||
case 0x67:
|
||||
case 0x6F:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
|
||||
if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) {
|
||||
CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code];
|
||||
CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down);
|
||||
CGEventRef clearFlags =
|
||||
CGEventCreateKeyboardEvent(NULL, (CGKeyCode)0, true);
|
||||
CGEventSetFlags(clearFlags, 0);
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
CFRelease(event);
|
||||
|
||||
// F1-F12 keys often require the FN key to be pressed on Mac keyboards, so
|
||||
// we simulate the FN key release when an F1-F12 key is released.
|
||||
if (IsFunctionKey(cg_key_code) && !is_down) {
|
||||
CGEventRef fn_release_event =
|
||||
CGEventCreateKeyboardEvent(NULL, fn_key_code_, false);
|
||||
CGEventPost(kCGHIDEventTap, fn_release_event);
|
||||
CFRelease(fn_release_event);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -31,6 +31,7 @@ class KeyboardCapturer : public DeviceController {
|
||||
bool control_flag_ = false;
|
||||
bool option_flag_ = false;
|
||||
bool command_flag_ = false;
|
||||
int fn_key_code_ = 0x3F;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-25
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KEYBOARD_CONVERTER_H_
|
||||
#define _KEYBOARD_CONVERTER_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
// Windows vkCode to macOS CGKeyCode (104 keys)
|
||||
std::map<int, int> vkCodeToCGKeyCode = {
|
||||
// A-Z
|
||||
{0x41, 0x00}, // A
|
||||
{0x42, 0x0B}, // B
|
||||
{0x43, 0x08}, // C
|
||||
{0x44, 0x02}, // D
|
||||
{0x45, 0x0E}, // E
|
||||
{0x46, 0x03}, // F
|
||||
{0x47, 0x05}, // G
|
||||
{0x48, 0x04}, // H
|
||||
{0x49, 0x22}, // I
|
||||
{0x4A, 0x26}, // J
|
||||
{0x4B, 0x28}, // K
|
||||
{0x4C, 0x25}, // L
|
||||
{0x4D, 0x2E}, // M
|
||||
{0x4E, 0x2D}, // N
|
||||
{0x4F, 0x1F}, // O
|
||||
{0x50, 0x23}, // P
|
||||
{0x51, 0x0C}, // Q
|
||||
{0x52, 0x0F}, // R
|
||||
{0x53, 0x01}, // S
|
||||
{0x54, 0x11}, // T
|
||||
{0x55, 0x20}, // U
|
||||
{0x56, 0x09}, // V
|
||||
{0x57, 0x0D}, // W
|
||||
{0x58, 0x07}, // X
|
||||
{0x59, 0x10}, // Y
|
||||
{0x5A, 0x06}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x30, 0x1D}, // 0
|
||||
{0x31, 0x12}, // 1
|
||||
{0x32, 0x13}, // 2
|
||||
{0x33, 0x14}, // 3
|
||||
{0x34, 0x15}, // 4
|
||||
{0x35, 0x17}, // 5
|
||||
{0x36, 0x16}, // 6
|
||||
{0x37, 0x1A}, // 7
|
||||
{0x38, 0x1C}, // 8
|
||||
{0x39, 0x19}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x70, 0x7A}, // F1
|
||||
{0x71, 0x78}, // F2
|
||||
{0x72, 0x63}, // F3
|
||||
{0x73, 0x76}, // F4
|
||||
{0x74, 0x60}, // F5
|
||||
{0x75, 0x61}, // F6
|
||||
{0x76, 0x62}, // F7
|
||||
{0x77, 0x64}, // F8
|
||||
{0x78, 0x65}, // F9
|
||||
{0x79, 0x6D}, // F10
|
||||
{0x7A, 0x67}, // F11
|
||||
{0x7B, 0x6F}, // F12
|
||||
|
||||
// control keys
|
||||
{0x1B, 0x35}, // Escape
|
||||
{0x0D, 0x24}, // Enter
|
||||
{0x20, 0x31}, // Space
|
||||
{0x08, 0x33}, // Backspace
|
||||
{0x09, 0x30}, // Tab
|
||||
{0x2C, 0x74}, // Print Screen
|
||||
{0x2D, 0x72}, // Insert
|
||||
{0x2E, 0x75}, // Delete
|
||||
{0x24, 0x73}, // Home
|
||||
{0x23, 0x77}, // End
|
||||
{0x21, 0x79}, // Page Up
|
||||
{0x22, 0x7A}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x25, 0x7B}, // Left Arrow
|
||||
{0x27, 0x7C}, // Right Arrow
|
||||
{0x26, 0x7E}, // Up Arrow
|
||||
{0x28, 0x7D}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x60, 0x52}, // Numpad 0
|
||||
{0x61, 0x53}, // Numpad 1
|
||||
{0x62, 0x54}, // Numpad 2
|
||||
{0x63, 0x55}, // Numpad 3
|
||||
{0x64, 0x56}, // Numpad 4
|
||||
{0x65, 0x57}, // Numpad 5
|
||||
{0x66, 0x58}, // Numpad 6
|
||||
{0x67, 0x59}, // Numpad 7
|
||||
{0x68, 0x5B}, // Numpad 8
|
||||
{0x69, 0x5C}, // Numpad 9
|
||||
{0x6E, 0x41}, // Numpad .
|
||||
{0x6F, 0x4B}, // Numpad /
|
||||
{0x6A, 0x43}, // Numpad *
|
||||
{0x6D, 0x4E}, // Numpad -
|
||||
{0x6B, 0x45}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0xBA, 0x29}, // ; (Semicolon)
|
||||
{0xDE, 0x27}, // ' (Quote)
|
||||
{0xC0, 0x32}, // ` (Backtick)
|
||||
{0xBC, 0x2B}, // , (Comma)
|
||||
{0xBE, 0x2F}, // . (Period)
|
||||
{0xBF, 0x2C}, // / (Slash)
|
||||
{0xDC, 0x2A}, // \ (Backslash)
|
||||
{0xDB, 0x21}, // [ (Left Bracket)
|
||||
{0xDD, 0x1E}, // ] (Right Bracket)
|
||||
{0xBD, 0x1B}, // - (Minus)
|
||||
{0xBB, 0x18}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x14, 0x39}, // Caps Lock
|
||||
{0xA0, 0x38}, // Shift (Left)
|
||||
{0xA1, 0x3C}, // Shift (Right)
|
||||
{0xA2, 0x3B}, // Ctrl (Left)
|
||||
{0xA3, 0x3E}, // Ctrl (Right)
|
||||
{0xA4, 0x3A}, // Alt (Left)
|
||||
{0xA5, 0x3D}, // Alt (Right)
|
||||
{0x5B, 0x37}, // Left Command (Windows key)
|
||||
{0x5C, 0x36}, // Right Command
|
||||
};
|
||||
|
||||
// macOS CGKeyCode to Windows vkCode
|
||||
std::map<int, int> CGKeyCodeToVkCode = {
|
||||
// A-Z
|
||||
{0x00, 0x41}, // A
|
||||
{0x0B, 0x42}, // B
|
||||
{0x08, 0x43}, // C
|
||||
{0x02, 0x44}, // D
|
||||
{0x0E, 0x45}, // E
|
||||
{0x03, 0x46}, // F
|
||||
{0x05, 0x47}, // G
|
||||
{0x04, 0x48}, // H
|
||||
{0x22, 0x49}, // I
|
||||
{0x26, 0x4A}, // J
|
||||
{0x28, 0x4B}, // K
|
||||
{0x25, 0x4C}, // L
|
||||
{0x2E, 0x4D}, // M
|
||||
{0x2D, 0x4E}, // N
|
||||
{0x1F, 0x4F}, // O
|
||||
{0x23, 0x50}, // P
|
||||
{0x0C, 0x51}, // Q
|
||||
{0x0F, 0x52}, // R
|
||||
{0x01, 0x53}, // S
|
||||
{0x11, 0x54}, // T
|
||||
{0x20, 0x55}, // U
|
||||
{0x09, 0x56}, // V
|
||||
{0x0D, 0x57}, // W
|
||||
{0x07, 0x58}, // X
|
||||
{0x10, 0x59}, // Y
|
||||
{0x06, 0x5A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x1D, 0x30}, // 0
|
||||
{0x12, 0x31}, // 1
|
||||
{0x13, 0x32}, // 2
|
||||
{0x14, 0x33}, // 3
|
||||
{0x15, 0x34}, // 4
|
||||
{0x17, 0x35}, // 5
|
||||
{0x16, 0x36}, // 6
|
||||
{0x1A, 0x37}, // 7
|
||||
{0x1C, 0x38}, // 8
|
||||
{0x19, 0x39}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x7A, 0x70}, // F1
|
||||
{0x78, 0x71}, // F2
|
||||
{0x63, 0x72}, // F3
|
||||
{0x76, 0x73}, // F4
|
||||
{0x60, 0x74}, // F5
|
||||
{0x61, 0x75}, // F6
|
||||
{0x62, 0x76}, // F7
|
||||
{0x64, 0x77}, // F8
|
||||
{0x65, 0x78}, // F9
|
||||
{0x6D, 0x79}, // F10
|
||||
{0x67, 0x7A}, // F11
|
||||
{0x6F, 0x7B}, // F12
|
||||
|
||||
// control keys
|
||||
{0x35, 0x1B}, // Escape
|
||||
{0x24, 0x0D}, // Enter
|
||||
{0x31, 0x20}, // Space
|
||||
{0x33, 0x08}, // Backspace
|
||||
{0x30, 0x09}, // Tab
|
||||
{0x74, 0x2C}, // Print Screen
|
||||
{0x72, 0x2D}, // Insert
|
||||
{0x75, 0x2E}, // Delete
|
||||
{0x73, 0x24}, // Home
|
||||
{0x77, 0x23}, // End
|
||||
{0x79, 0x21}, // Page Up
|
||||
{0x7A, 0x22}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x7B, 0x25}, // Left Arrow
|
||||
{0x7C, 0x27}, // Right Arrow
|
||||
{0x7E, 0x26}, // Up Arrow
|
||||
{0x7D, 0x28}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x52, 0x60}, // Numpad 0
|
||||
{0x53, 0x61}, // Numpad 1
|
||||
{0x54, 0x62}, // Numpad 2
|
||||
{0x55, 0x63}, // Numpad 3
|
||||
{0x56, 0x64}, // Numpad 4
|
||||
{0x57, 0x65}, // Numpad 5
|
||||
{0x58, 0x66}, // Numpad 6
|
||||
{0x59, 0x67}, // Numpad 7
|
||||
{0x5B, 0x68}, // Numpad 8
|
||||
{0x5C, 0x69}, // Numpad 9
|
||||
{0x41, 0x6E}, // Numpad .
|
||||
{0x4B, 0x6F}, // Numpad /
|
||||
{0x43, 0x6A}, // Numpad *
|
||||
{0x4E, 0x6D}, // Numpad -
|
||||
{0x45, 0x6B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x29, 0xBA}, // ; (Semicolon)
|
||||
{0x27, 0xDE}, // ' (Quote)
|
||||
{0x32, 0xC0}, // ` (Backtick)
|
||||
{0x2B, 0xBC}, // , (Comma)
|
||||
{0x2F, 0xBE}, // . (Period)
|
||||
{0x2C, 0xBF}, // / (Slash)
|
||||
{0x2A, 0xDC}, // \ (Backslash)
|
||||
{0x21, 0xDB}, // [ (Left Bracket)
|
||||
{0x1E, 0xDD}, // ] (Right Bracket)
|
||||
{0x1B, 0xBD}, // - (Minus)
|
||||
{0x18, 0xBB}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x39, 0x14}, // Caps Lock
|
||||
{0x38, 0xA0}, // Shift (Left)
|
||||
{0x3C, 0xA1}, // Shift (Right)
|
||||
{0x3B, 0xA2}, // Control (Left)
|
||||
{0x3E, 0xA3}, // Control (Right)
|
||||
{0x3A, 0xA4}, // Alt (Left)
|
||||
{0x3D, 0xA5}, // Alt (Right)
|
||||
{0x37, 0x5B}, // Left Command (Windows key)
|
||||
{0x36, 0x5C}, // Right Command
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -30,7 +30,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
||||
|
||||
keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
|
||||
if (!keyboard_hook_) {
|
||||
LOG_ERROR("Failed to install keyboard hook!")
|
||||
LOG_ERROR("Failed to install keyboard hook");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
740
src/device_controller/keyboard_converter.h
Normal file
@@ -0,0 +1,740 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-25
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KEYBOARD_CONVERTER_H_
|
||||
#define _KEYBOARD_CONVERTER_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
// Windows vkCode to macOS CGKeyCode (104 keys)
|
||||
std::map<int, int> vkCodeToCGKeyCode = {
|
||||
// A-Z
|
||||
{0x41, 0x00}, // A
|
||||
{0x42, 0x0B}, // B
|
||||
{0x43, 0x08}, // C
|
||||
{0x44, 0x02}, // D
|
||||
{0x45, 0x0E}, // E
|
||||
{0x46, 0x03}, // F
|
||||
{0x47, 0x05}, // G
|
||||
{0x48, 0x04}, // H
|
||||
{0x49, 0x22}, // I
|
||||
{0x4A, 0x26}, // J
|
||||
{0x4B, 0x28}, // K
|
||||
{0x4C, 0x25}, // L
|
||||
{0x4D, 0x2E}, // M
|
||||
{0x4E, 0x2D}, // N
|
||||
{0x4F, 0x1F}, // O
|
||||
{0x50, 0x23}, // P
|
||||
{0x51, 0x0C}, // Q
|
||||
{0x52, 0x0F}, // R
|
||||
{0x53, 0x01}, // S
|
||||
{0x54, 0x11}, // T
|
||||
{0x55, 0x20}, // U
|
||||
{0x56, 0x09}, // V
|
||||
{0x57, 0x0D}, // W
|
||||
{0x58, 0x07}, // X
|
||||
{0x59, 0x10}, // Y
|
||||
{0x5A, 0x06}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x30, 0x1D}, // 0
|
||||
{0x31, 0x12}, // 1
|
||||
{0x32, 0x13}, // 2
|
||||
{0x33, 0x14}, // 3
|
||||
{0x34, 0x15}, // 4
|
||||
{0x35, 0x17}, // 5
|
||||
{0x36, 0x16}, // 6
|
||||
{0x37, 0x1A}, // 7
|
||||
{0x38, 0x1C}, // 8
|
||||
{0x39, 0x19}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x70, 0x7A}, // F1
|
||||
{0x71, 0x78}, // F2
|
||||
{0x72, 0x63}, // F3
|
||||
{0x73, 0x76}, // F4
|
||||
{0x74, 0x60}, // F5
|
||||
{0x75, 0x61}, // F6
|
||||
{0x76, 0x62}, // F7
|
||||
{0x77, 0x64}, // F8
|
||||
{0x78, 0x65}, // F9
|
||||
{0x79, 0x6D}, // F10
|
||||
{0x7A, 0x67}, // F11
|
||||
{0x7B, 0x6F}, // F12
|
||||
|
||||
// control keys
|
||||
{0x1B, 0x35}, // Escape
|
||||
{0x0D, 0x24}, // Enter
|
||||
{0x20, 0x31}, // Space
|
||||
{0x08, 0x33}, // Backspace
|
||||
{0x09, 0x30}, // Tab
|
||||
{0x2C, 0x74}, // Print Screen
|
||||
{0x2D, 0x72}, // Insert
|
||||
{0x2E, 0x75}, // Delete
|
||||
{0x24, 0x73}, // Home
|
||||
{0x23, 0x77}, // End
|
||||
{0x21, 0x79}, // Page Up
|
||||
{0x22, 0x7A}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x25, 0x7B}, // Left Arrow
|
||||
{0x27, 0x7C}, // Right Arrow
|
||||
{0x26, 0x7E}, // Up Arrow
|
||||
{0x28, 0x7D}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x60, 0x52}, // Numpad 0
|
||||
{0x61, 0x53}, // Numpad 1
|
||||
{0x62, 0x54}, // Numpad 2
|
||||
{0x63, 0x55}, // Numpad 3
|
||||
{0x64, 0x56}, // Numpad 4
|
||||
{0x65, 0x57}, // Numpad 5
|
||||
{0x66, 0x58}, // Numpad 6
|
||||
{0x67, 0x59}, // Numpad 7
|
||||
{0x68, 0x5B}, // Numpad 8
|
||||
{0x69, 0x5C}, // Numpad 9
|
||||
{0x6E, 0x41}, // Numpad .
|
||||
{0x6F, 0x4B}, // Numpad /
|
||||
{0x6A, 0x43}, // Numpad *
|
||||
{0x6D, 0x4E}, // Numpad -
|
||||
{0x6B, 0x45}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0xBA, 0x29}, // ; (Semicolon)
|
||||
{0xDE, 0x27}, // ' (Quote)
|
||||
{0xC0, 0x32}, // ` (Backtick)
|
||||
{0xBC, 0x2B}, // , (Comma)
|
||||
{0xBE, 0x2F}, // . (Period)
|
||||
{0xBF, 0x2C}, // / (Slash)
|
||||
{0xDC, 0x2A}, // \ (Backslash)
|
||||
{0xDB, 0x21}, // [ (Left Bracket)
|
||||
{0xDD, 0x1E}, // ] (Right Bracket)
|
||||
{0xBD, 0x1B}, // - (Minus)
|
||||
{0xBB, 0x18}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x14, 0x39}, // Caps Lock
|
||||
{0xA0, 0x38}, // Shift (Left)
|
||||
{0xA1, 0x3C}, // Shift (Right)
|
||||
{0xA2, 0x3B}, // Ctrl (Left)
|
||||
{0xA3, 0x3E}, // Ctrl (Right)
|
||||
{0xA4, 0x3A}, // Alt (Left)
|
||||
{0xA5, 0x3D}, // Alt (Right)
|
||||
{0x5B, 0x37}, // Left Command (Windows key)
|
||||
{0x5C, 0x36}, // Right Command
|
||||
};
|
||||
|
||||
// macOS CGKeyCode to Windows vkCode
|
||||
std::map<int, int> CGKeyCodeToVkCode = {
|
||||
// A-Z
|
||||
{0x00, 0x41}, // A
|
||||
{0x0B, 0x42}, // B
|
||||
{0x08, 0x43}, // C
|
||||
{0x02, 0x44}, // D
|
||||
{0x0E, 0x45}, // E
|
||||
{0x03, 0x46}, // F
|
||||
{0x05, 0x47}, // G
|
||||
{0x04, 0x48}, // H
|
||||
{0x22, 0x49}, // I
|
||||
{0x26, 0x4A}, // J
|
||||
{0x28, 0x4B}, // K
|
||||
{0x25, 0x4C}, // L
|
||||
{0x2E, 0x4D}, // M
|
||||
{0x2D, 0x4E}, // N
|
||||
{0x1F, 0x4F}, // O
|
||||
{0x23, 0x50}, // P
|
||||
{0x0C, 0x51}, // Q
|
||||
{0x0F, 0x52}, // R
|
||||
{0x01, 0x53}, // S
|
||||
{0x11, 0x54}, // T
|
||||
{0x20, 0x55}, // U
|
||||
{0x09, 0x56}, // V
|
||||
{0x0D, 0x57}, // W
|
||||
{0x07, 0x58}, // X
|
||||
{0x10, 0x59}, // Y
|
||||
{0x06, 0x5A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x1D, 0x30}, // 0
|
||||
{0x12, 0x31}, // 1
|
||||
{0x13, 0x32}, // 2
|
||||
{0x14, 0x33}, // 3
|
||||
{0x15, 0x34}, // 4
|
||||
{0x17, 0x35}, // 5
|
||||
{0x16, 0x36}, // 6
|
||||
{0x1A, 0x37}, // 7
|
||||
{0x1C, 0x38}, // 8
|
||||
{0x19, 0x39}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x7A, 0x70}, // F1
|
||||
{0x78, 0x71}, // F2
|
||||
{0x63, 0x72}, // F3
|
||||
{0x76, 0x73}, // F4
|
||||
{0x60, 0x74}, // F5
|
||||
{0x61, 0x75}, // F6
|
||||
{0x62, 0x76}, // F7
|
||||
{0x64, 0x77}, // F8
|
||||
{0x65, 0x78}, // F9
|
||||
{0x6D, 0x79}, // F10
|
||||
{0x67, 0x7A}, // F11
|
||||
{0x6F, 0x7B}, // F12
|
||||
|
||||
// control keys
|
||||
{0x35, 0x1B}, // Escape
|
||||
{0x24, 0x0D}, // Enter
|
||||
{0x31, 0x20}, // Space
|
||||
{0x33, 0x08}, // Backspace
|
||||
{0x30, 0x09}, // Tab
|
||||
{0x74, 0x2C}, // Print Screen
|
||||
{0x72, 0x2D}, // Insert
|
||||
{0x75, 0x2E}, // Delete
|
||||
{0x73, 0x24}, // Home
|
||||
{0x77, 0x23}, // End
|
||||
{0x79, 0x21}, // Page Up
|
||||
{0x7A, 0x22}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x7B, 0x25}, // Left Arrow
|
||||
{0x7C, 0x27}, // Right Arrow
|
||||
{0x7E, 0x26}, // Up Arrow
|
||||
{0x7D, 0x28}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x52, 0x60}, // Numpad 0
|
||||
{0x53, 0x61}, // Numpad 1
|
||||
{0x54, 0x62}, // Numpad 2
|
||||
{0x55, 0x63}, // Numpad 3
|
||||
{0x56, 0x64}, // Numpad 4
|
||||
{0x57, 0x65}, // Numpad 5
|
||||
{0x58, 0x66}, // Numpad 6
|
||||
{0x59, 0x67}, // Numpad 7
|
||||
{0x5B, 0x68}, // Numpad 8
|
||||
{0x5C, 0x69}, // Numpad 9
|
||||
{0x41, 0x6E}, // Numpad .
|
||||
{0x4B, 0x6F}, // Numpad /
|
||||
{0x43, 0x6A}, // Numpad *
|
||||
{0x4E, 0x6D}, // Numpad -
|
||||
{0x45, 0x6B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x29, 0xBA}, // ; (Semicolon)
|
||||
{0x27, 0xDE}, // ' (Quote)
|
||||
{0x32, 0xC0}, // ` (Backtick)
|
||||
{0x2B, 0xBC}, // , (Comma)
|
||||
{0x2F, 0xBE}, // . (Period)
|
||||
{0x2C, 0xBF}, // / (Slash)
|
||||
{0x2A, 0xDC}, // \ (Backslash)
|
||||
{0x21, 0xDB}, // [ (Left Bracket)
|
||||
{0x1E, 0xDD}, // ] (Right Bracket)
|
||||
{0x1B, 0xBD}, // - (Minus)
|
||||
{0x18, 0xBB}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x39, 0x14}, // Caps Lock
|
||||
{0x38, 0xA0}, // Shift (Left)
|
||||
{0x3C, 0xA1}, // Shift (Right)
|
||||
{0x3B, 0xA2}, // Control (Left)
|
||||
{0x3E, 0xA3}, // Control (Right)
|
||||
{0x3A, 0xA4}, // Alt (Left)
|
||||
{0x3D, 0xA5}, // Alt (Right)
|
||||
{0x37, 0x5B}, // Left Command (Windows key)
|
||||
{0x36, 0x5C}, // Right Command
|
||||
};
|
||||
|
||||
// Windows vkCode to X11 KeySym
|
||||
std::map<int, int> vkCodeToX11KeySym = {
|
||||
// A-Z
|
||||
{0x41, 0x0041}, // A
|
||||
{0x42, 0x0042}, // B
|
||||
{0x43, 0x0043}, // C
|
||||
{0x44, 0x0044}, // D
|
||||
{0x45, 0x0045}, // E
|
||||
{0x46, 0x0046}, // F
|
||||
{0x47, 0x0047}, // G
|
||||
{0x48, 0x0048}, // H
|
||||
{0x49, 0x0049}, // I
|
||||
{0x4A, 0x004A}, // J
|
||||
{0x4B, 0x004B}, // K
|
||||
{0x4C, 0x004C}, // L
|
||||
{0x4D, 0x004D}, // M
|
||||
{0x4E, 0x004E}, // N
|
||||
{0x4F, 0x004F}, // O
|
||||
{0x50, 0x0050}, // P
|
||||
{0x51, 0x0051}, // Q
|
||||
{0x52, 0x0052}, // R
|
||||
{0x53, 0x0053}, // S
|
||||
{0x54, 0x0054}, // T
|
||||
{0x55, 0x0055}, // U
|
||||
{0x56, 0x0056}, // V
|
||||
{0x57, 0x0057}, // W
|
||||
{0x58, 0x0058}, // X
|
||||
{0x59, 0x0059}, // Y
|
||||
{0x5A, 0x005A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x30, 0x0030}, // 0
|
||||
{0x31, 0x0031}, // 1
|
||||
{0x32, 0x0032}, // 2
|
||||
{0x33, 0x0033}, // 3
|
||||
{0x34, 0x0034}, // 4
|
||||
{0x35, 0x0035}, // 5
|
||||
{0x36, 0x0036}, // 6
|
||||
{0x37, 0x0037}, // 7
|
||||
{0x38, 0x0038}, // 8
|
||||
{0x39, 0x0039}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x70, 0xFFBE}, // F1
|
||||
{0x71, 0xFFBF}, // F2
|
||||
{0x72, 0xFFC0}, // F3
|
||||
{0x73, 0xFFC1}, // F4
|
||||
{0x74, 0xFFC2}, // F5
|
||||
{0x75, 0xFFC3}, // F6
|
||||
{0x76, 0xFFC4}, // F7
|
||||
{0x77, 0xFFC5}, // F8
|
||||
{0x78, 0xFFC6}, // F9
|
||||
{0x79, 0xFFC7}, // F10
|
||||
{0x7A, 0xFFC8}, // F11
|
||||
{0x7B, 0xFFC9}, // F12
|
||||
|
||||
// control keys
|
||||
{0x1B, 0xFF1B}, // Escape
|
||||
{0x0D, 0xFF0D}, // Enter
|
||||
{0x20, 0x0020}, // Space
|
||||
{0x08, 0xFF08}, // Backspace
|
||||
{0x09, 0xFF09}, // Tab
|
||||
{0x2C, 0xFF15}, // Print Screen
|
||||
{0x91, 0xFF14}, // Scroll Lock
|
||||
{0x13, 0xFF13}, // Pause/Break
|
||||
{0x2D, 0xFF63}, // Insert
|
||||
{0x2E, 0xFFFF}, // Delete
|
||||
{0x24, 0xFF50}, // Home
|
||||
{0x23, 0xFF57}, // End
|
||||
{0x21, 0xFF55}, // Page Up
|
||||
{0x22, 0xFF56}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x25, 0xFF51}, // Left Arrow
|
||||
{0x27, 0xFF53}, // Right Arrow
|
||||
{0x26, 0xFF52}, // Up Arrow
|
||||
{0x28, 0xFF54}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x60, 0x0030}, // Numpad 0
|
||||
{0x61, 0x0031}, // Numpad 1
|
||||
{0x62, 0x0032}, // Numpad 2
|
||||
{0x63, 0x0033}, // Numpad 3
|
||||
{0x64, 0x0034}, // Numpad 4
|
||||
{0x65, 0x0035}, // Numpad 5
|
||||
{0x66, 0x0036}, // Numpad 6
|
||||
{0x67, 0x0037}, // Numpad 7
|
||||
{0x68, 0x0038}, // Numpad 8
|
||||
{0x69, 0x0039}, // Numpad 9
|
||||
{0x6E, 0x003A}, // Numpad .
|
||||
{0x6F, 0x002F}, // Numpad /
|
||||
{0x6A, 0x002A}, // Numpad *
|
||||
{0x6D, 0x002D}, // Numpad -
|
||||
{0x6B, 0x002B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0xBA, 0x003B}, // ; (Semicolon)
|
||||
{0xDE, 0x0027}, // ' (Quote)
|
||||
{0xC0, 0x007E}, // ` (Grave)
|
||||
{0xBC, 0x002C}, // , (Comma)
|
||||
{0xBE, 0x002E}, // . (Period)
|
||||
{0xBF, 0x002F}, // / (Slash)
|
||||
{0xDC, 0x005C}, // \ (Backslash)
|
||||
{0xDB, 0x005B}, // [ (Left Bracket)
|
||||
{0xDD, 0x005D}, // ] (Right Bracket)
|
||||
{0xBD, 0x002D}, // - (Minus)
|
||||
{0xBB, 0x003D}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x14, 0xFFE5}, // Caps Lock
|
||||
{0xA0, 0xFFE1}, // Shift (Left)
|
||||
{0xA1, 0xFFE2}, // Shift (Right)
|
||||
{0xA2, 0xFFE3}, // Ctrl (Left)
|
||||
{0xA3, 0xFFE4}, // Ctrl (Right)
|
||||
{0xA4, 0xFFE9}, // Alt (Left)
|
||||
{0xA5, 0xFFEA}, // Alt (Right)
|
||||
{0x5B, 0xFFEB}, // Left Command (Windows key)
|
||||
{0x5C, 0xFFEC}, // Right Command
|
||||
};
|
||||
|
||||
// X11 KeySym to Windows vkCode
|
||||
// std::map<int, int> x11KeySymToVkCode = []() {
|
||||
// std::map<int, int> result;
|
||||
// for (const auto& pair : vkCodeToX11KeySym) {
|
||||
// result[pair.second] = pair.first;
|
||||
// }
|
||||
// return result;
|
||||
// }();
|
||||
|
||||
std::map<int, int> x11KeySymToVkCode = {
|
||||
// A-Z
|
||||
{0x0041, 0x41}, // A
|
||||
{0x0042, 0x42}, // B
|
||||
{0x0043, 0x43}, // C
|
||||
{0x0044, 0x44}, // D
|
||||
{0x0045, 0x45}, // E
|
||||
{0x0046, 0x46}, // F
|
||||
{0x0047, 0x47}, // G
|
||||
{0x0048, 0x48}, // H
|
||||
{0x0049, 0x49}, // I
|
||||
{0x004A, 0x4A}, // J
|
||||
{0x004B, 0x4B}, // K
|
||||
{0x004C, 0x4C}, // L
|
||||
{0x004D, 0x4D}, // M
|
||||
{0x004E, 0x4E}, // N
|
||||
{0x004F, 0x4F}, // O
|
||||
{0x0050, 0x50}, // P
|
||||
{0x0051, 0x51}, // Q
|
||||
{0x0052, 0x52}, // R
|
||||
{0x0053, 0x53}, // S
|
||||
{0x0054, 0x54}, // T
|
||||
{0x0055, 0x55}, // U
|
||||
{0x0056, 0x56}, // V
|
||||
{0x0057, 0x57}, // W
|
||||
{0x0058, 0x58}, // X
|
||||
{0x0059, 0x59}, // Y
|
||||
{0x005A, 0x5A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x0030, 0x30}, // 0
|
||||
{0x0031, 0x31}, // 1
|
||||
{0x0032, 0x32}, // 2
|
||||
{0x0033, 0x33}, // 3
|
||||
{0x0034, 0x34}, // 4
|
||||
{0x0035, 0x35}, // 5
|
||||
{0x0036, 0x36}, // 6
|
||||
{0x0037, 0x37}, // 7
|
||||
{0x0038, 0x38}, // 8
|
||||
{0x0039, 0x39}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0xFFBE, 0x70}, // F1
|
||||
{0xFFBF, 0x71}, // F2
|
||||
{0xFFC0, 0x72}, // F3
|
||||
{0xFFC1, 0x73}, // F4
|
||||
{0xFFC2, 0x74}, // F5
|
||||
{0xFFC3, 0x75}, // F6
|
||||
{0xFFC4, 0x76}, // F7
|
||||
{0xFFC5, 0x77}, // F8
|
||||
{0xFFC6, 0x78}, // F9
|
||||
{0xFFC7, 0x79}, // F10
|
||||
{0xFFC8, 0x7A}, // F11
|
||||
{0xFFC9, 0x7B}, // F12
|
||||
|
||||
// control keys
|
||||
{0xFF1B, 0x1B}, // Escape
|
||||
{0xFF0D, 0x0D}, // Enter
|
||||
{0x0020, 0x20}, // Space
|
||||
{0xFF08, 0x08}, // Backspace
|
||||
{0xFF09, 0x09}, // Tab
|
||||
{0xFF15, 0x2C}, // Print Screen
|
||||
{0xFF14, 0x91}, // Scroll Lock
|
||||
{0xFF13, 0x13}, // Pause/Break
|
||||
{0xFF63, 0x2D}, // Insert
|
||||
{0xFFFF, 0x2E}, // Delete
|
||||
{0xFF50, 0x24}, // Home
|
||||
{0xFF57, 0x23}, // End
|
||||
{0xFF55, 0x21}, // Page Up
|
||||
{0xFF56, 0x22}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0xFF51, 0x25}, // Left Arrow
|
||||
{0xFF53, 0x27}, // Right Arrow
|
||||
{0xFF52, 0x26}, // Up Arrow
|
||||
{0xFF54, 0x28}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x0030, 0x60}, // Numpad 0
|
||||
{0x0031, 0x61}, // Numpad 1
|
||||
{0x0032, 0x62}, // Numpad 2
|
||||
{0x0033, 0x63}, // Numpad 3
|
||||
{0x0034, 0x64}, // Numpad 4
|
||||
{0x0035, 0x65}, // Numpad 5
|
||||
{0x0036, 0x66}, // Numpad 6
|
||||
{0x0037, 0x67}, // Numpad 7
|
||||
{0x0038, 0x68}, // Numpad 8
|
||||
{0x0039, 0x69}, // Numpad 9
|
||||
{0x003A, 0x6E}, // Numpad .
|
||||
{0x002F, 0x6F}, // Numpad /
|
||||
{0x002A, 0x6A}, // Numpad *
|
||||
{0x002D, 0x6D}, // Numpad -
|
||||
{0x002B, 0x6B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x003B, 0xBA}, // ; (Semicolon)
|
||||
{0x0027, 0xDE}, // ' (Quote)
|
||||
{0x007E, 0xC0}, // ` (Grave)
|
||||
{0x002C, 0xBC}, // , (Comma)
|
||||
{0x002E, 0xBE}, // . (Period)
|
||||
{0x002F, 0xBF}, // / (Slash)
|
||||
{0x005C, 0xDC}, // \ (Backslash)
|
||||
{0x005B, 0xDB}, // [ (Left Bracket)
|
||||
{0x005D, 0xDD}, // ] (Right Bracket)
|
||||
{0x002D, 0xBD}, // - (Minus)
|
||||
{0x003D, 0xBB}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0xFFE5, 0x14}, // Caps Lock
|
||||
{0xFFE1, 0xA0}, // Shift (Left)
|
||||
{0xFFE2, 0xA1}, // Shift (Right)
|
||||
{0xFFE3, 0xA2}, // Ctrl (Left)
|
||||
{0xFFE4, 0xA3}, // Ctrl (Right)
|
||||
{0xFFE9, 0xA4}, // Alt (Left)
|
||||
{0xFFEA, 0xA5}, // Alt (Right)
|
||||
{0xFFEB, 0x5B}, // Left Command (Windows key)
|
||||
{0xFFEC, 0x5C}, // Right Command
|
||||
};
|
||||
|
||||
// macOS CGKeyCode to X11 KeySym
|
||||
std::map<int, int> cgKeyCodeToX11KeySym = {
|
||||
// A-Z
|
||||
{0x00, 0x0041}, // A
|
||||
{0x0B, 0x0042}, // B
|
||||
{0x08, 0x0043}, // C
|
||||
{0x02, 0x0044}, // D
|
||||
{0x0E, 0x0045}, // E
|
||||
{0x03, 0x0046}, // F
|
||||
{0x05, 0x0047}, // G
|
||||
{0x04, 0x0048}, // H
|
||||
{0x22, 0x0049}, // I
|
||||
{0x26, 0x004A}, // J
|
||||
{0x28, 0x004B}, // K
|
||||
{0x25, 0x004C}, // L
|
||||
{0x2E, 0x004D}, // M
|
||||
{0x2D, 0x004E}, // N
|
||||
{0x1F, 0x004F}, // O
|
||||
{0x23, 0x0050}, // P
|
||||
{0x0C, 0x0051}, // Q
|
||||
{0x0F, 0x0052}, // R
|
||||
{0x01, 0x0053}, // S
|
||||
{0x11, 0x0054}, // T
|
||||
{0x20, 0x0055}, // U
|
||||
{0x09, 0x0056}, // V
|
||||
{0x0D, 0x0057}, // W
|
||||
{0x07, 0x0058}, // X
|
||||
{0x10, 0x0059}, // Y
|
||||
{0x06, 0x005A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x1D, 0x0030}, // 0
|
||||
{0x12, 0x0031}, // 1
|
||||
{0x13, 0x0032}, // 2
|
||||
{0x14, 0x0033}, // 3
|
||||
{0x15, 0x0034}, // 4
|
||||
{0x17, 0x0035}, // 5
|
||||
{0x16, 0x0036}, // 6
|
||||
{0x1A, 0x0037}, // 7
|
||||
{0x1C, 0x0038}, // 8
|
||||
{0x19, 0x0039}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x7A, 0xFFBE}, // F1
|
||||
{0x78, 0xFFBF}, // F2
|
||||
{0x63, 0xFFC0}, // F3
|
||||
{0x76, 0xFFC1}, // F4
|
||||
{0x60, 0xFFC2}, // F5
|
||||
{0x61, 0xFFC3}, // F6
|
||||
{0x62, 0xFFC4}, // F7
|
||||
{0x64, 0xFFC5}, // F8
|
||||
{0x65, 0xFFC6}, // F9
|
||||
{0x6D, 0xFFC7}, // F10
|
||||
{0x67, 0xFFC8}, // F11
|
||||
{0x6F, 0xFFC9}, // F12
|
||||
|
||||
// control keys
|
||||
{0x35, 0xFF1B}, // Escape
|
||||
{0x24, 0xFF0D}, // Enter
|
||||
{0x31, 0x0020}, // Space
|
||||
{0x33, 0xFF08}, // Backspace
|
||||
{0x30, 0xFF09}, // Tab
|
||||
{0x74, 0xFF15}, // Print Screen
|
||||
{0x72, 0xFF63}, // Insert
|
||||
{0x75, 0xFFFF}, // Delete
|
||||
{0x73, 0xFF50}, // Home
|
||||
{0x77, 0xFF57}, // End
|
||||
{0x79, 0xFF55}, // Page Up
|
||||
{0x7A, 0xFF56}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x7B, 0xFF51}, // Left Arrow
|
||||
{0x7C, 0xFF53}, // Right Arrow
|
||||
{0x7E, 0xFF52}, // Up Arrow
|
||||
{0x7D, 0xFF54}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x52, 0x0030}, // Numpad 0
|
||||
{0x53, 0x0031}, // Numpad 1
|
||||
{0x54, 0x0032}, // Numpad 2
|
||||
{0x55, 0x0033}, // Numpad 3
|
||||
{0x56, 0x0034}, // Numpad 4
|
||||
{0x57, 0x0035}, // Numpad 5
|
||||
{0x58, 0x0036}, // Numpad 6
|
||||
{0x59, 0x0037}, // Numpad 7
|
||||
{0x5B, 0x0038}, // Numpad 8
|
||||
{0x5C, 0x0039}, // Numpad 9
|
||||
{0x41, 0x003A}, // Numpad .
|
||||
{0x4B, 0x002F}, // Numpad /
|
||||
{0x43, 0x002A}, // Numpad *
|
||||
{0x4E, 0x002D}, // Numpad -
|
||||
{0x45, 0x002B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x29, 0x003B}, // ; (Semicolon)
|
||||
{0x27, 0x0027}, // ' (Quote)
|
||||
{0x32, 0x007E}, // ` (Backtick)
|
||||
{0x2B, 0x002C}, // , (Comma)
|
||||
{0x2F, 0x002E}, // . (Period)
|
||||
{0x2C, 0x002F}, // / (Slash)
|
||||
{0x2A, 0x005C}, // \ (Backslash)
|
||||
{0x21, 0x005B}, // [ (Left Bracket)
|
||||
{0x1E, 0x005D}, // ] (Right Bracket)
|
||||
{0x1B, 0x002D}, // - (Minus)
|
||||
{0x18, 0x003D}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x39, 0xFFE5}, // Caps Lock
|
||||
{0x38, 0xFFE1}, // Shift (Left)
|
||||
{0x3C, 0xFFE2}, // Shift (Right)
|
||||
{0x3B, 0xFFE3}, // Control (Left)
|
||||
{0x3E, 0xFFE4}, // Control (Right)
|
||||
{0x3A, 0xFFE9}, // Alt (Left)
|
||||
{0x3D, 0xFFEA}, // Alt (Right)
|
||||
{0x37, 0xFFEB}, // Left Command (Windows key)
|
||||
{0x36, 0xFFEC}, // Right Command
|
||||
};
|
||||
|
||||
// X11 KeySym to macOS CGKeyCode
|
||||
// std::map<int, int> x11KeySymToCgKeyCode = []() {
|
||||
// std::map<int, int> result;
|
||||
// for (const auto& pair : cgKeyCodeToX11KeySym) {
|
||||
// result[pair.second] = pair.first;
|
||||
// }
|
||||
// return result;
|
||||
// }();
|
||||
|
||||
std::map<int, int> x11KeySymToCgKeyCode = {
|
||||
// A-Z
|
||||
{0x0041, 0x00}, // A
|
||||
{0x0042, 0x0B}, // B
|
||||
{0x0043, 0x08}, // C
|
||||
{0x0044, 0x02}, // D
|
||||
{0x0045, 0x0E}, // E
|
||||
{0x0046, 0x03}, // F
|
||||
{0x0047, 0x05}, // G
|
||||
{0x0048, 0x04}, // H
|
||||
{0x0049, 0x22}, // I
|
||||
{0x004A, 0x26}, // J
|
||||
{0x004B, 0x28}, // K
|
||||
{0x004C, 0x25}, // L
|
||||
{0x004D, 0x2E}, // M
|
||||
{0x004E, 0x2D}, // N
|
||||
{0x004F, 0x1F}, // O
|
||||
{0x0050, 0x23}, // P
|
||||
{0x0051, 0x0C}, // Q
|
||||
{0x0052, 0x0F}, // R
|
||||
{0x0053, 0x01}, // S
|
||||
{0x0054, 0x11}, // T
|
||||
{0x0055, 0x20}, // U
|
||||
{0x0056, 0x09}, // V
|
||||
{0x0057, 0x0D}, // W
|
||||
{0x0058, 0x07}, // X
|
||||
{0x0059, 0x10}, // Y
|
||||
{0x005A, 0x06}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x0030, 0x1D}, // 0
|
||||
{0x0031, 0x12}, // 1
|
||||
{0x0032, 0x13}, // 2
|
||||
{0x0033, 0x14}, // 3
|
||||
{0x0034, 0x15}, // 4
|
||||
{0x0035, 0x17}, // 5
|
||||
{0x0036, 0x16}, // 6
|
||||
{0x0037, 0x1A}, // 7
|
||||
{0x0038, 0x1C}, // 8
|
||||
{0x0039, 0x19}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0xFFBE, 0x7A}, // F1
|
||||
{0xFFBF, 0x78}, // F2
|
||||
{0xFFC0, 0x63}, // F3
|
||||
{0xFFC1, 0x76}, // F4
|
||||
{0xFFC2, 0x60}, // F5
|
||||
{0xFFC3, 0x61}, // F6
|
||||
{0xFFC4, 0x62}, // F7
|
||||
{0xFFC5, 0x64}, // F8
|
||||
{0xFFC6, 0x65}, // F9
|
||||
{0xFFC7, 0x6D}, // F10
|
||||
{0xFFC8, 0x67}, // F11
|
||||
{0xFFC9, 0x6F}, // F12
|
||||
|
||||
// control keys
|
||||
{0xFF1B, 0x35}, // Escape
|
||||
{0xFF0D, 0x24}, // Enter
|
||||
{0x0020, 0x31}, // Space
|
||||
{0xFF08, 0x33}, // Backspace
|
||||
{0xFF09, 0x30}, // Tab
|
||||
{0xFF15, 0x74}, // Print Screen
|
||||
{0xFF63, 0x72}, // Insert
|
||||
{0xFFFF, 0x75}, // Delete
|
||||
{0xFF50, 0x73}, // Home
|
||||
{0xFF57, 0x77}, // End
|
||||
{0xFF55, 0x79}, // Page Up
|
||||
{0xFF56, 0x7A}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0xFF51, 0x7B}, // Left Arrow
|
||||
{0xFF53, 0x7C}, // Right Arrow
|
||||
{0xFF52, 0x7E}, // Up Arrow
|
||||
{0xFF54, 0x7D}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x0030, 0x52}, // Numpad 0
|
||||
{0x0031, 0x53}, // Numpad 1
|
||||
{0x0032, 0x54}, // Numpad 2
|
||||
{0x0033, 0x55}, // Numpad 3
|
||||
{0x0034, 0x56}, // Numpad 4
|
||||
{0x0035, 0x57}, // Numpad 5
|
||||
{0x0036, 0x58}, // Numpad 6
|
||||
{0x0037, 0x59}, // Numpad 7
|
||||
{0x0038, 0x5B}, // Numpad 8
|
||||
{0x0039, 0x5C}, // Numpad 9
|
||||
{0x003A, 0x41}, // Numpad .
|
||||
{0x002F, 0x4B}, // Numpad /
|
||||
{0x002A, 0x43}, // Numpad *
|
||||
{0x002D, 0x4E}, // Numpad -
|
||||
{0x002B, 0x45}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x003B, 0x29}, // ; (Semicolon)
|
||||
{0x0027, 0x27}, // ' (Quote)
|
||||
{0x007E, 0x32}, // ` (Backtick)
|
||||
{0x002C, 0x2B}, // , (Comma)
|
||||
{0x002E, 0x2F}, // . (Period)
|
||||
{0x002F, 0x2C}, // / (Slash)
|
||||
{0x005C, 0x2A}, // \ (Backslash)
|
||||
{0x005B, 0x21}, // [ (Left Bracket)
|
||||
{0x005D, 0x1E}, // ] (Right Bracket)
|
||||
{0x002D, 0x1B}, // - (Minus)
|
||||
{0x003D, 0x18}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0xFFE5, 0x39}, // Caps Lock
|
||||
{0xFFE1, 0x38}, // Shift (Left)
|
||||
{0xFFE2, 0x3C}, // Shift (Right)
|
||||
{0xFFE3, 0x3B}, // Control (Left)
|
||||
{0xFFE4, 0x3E}, // Control (Right)
|
||||
{0xFFE9, 0x3A}, // Alt (Left)
|
||||
{0xFFEA, 0x3D}, // Alt (Right)
|
||||
{0xFFEB, 0x37}, // Left Command
|
||||
{0xFFEC, 0x36}, // Right Command
|
||||
};
|
||||
|
||||
#endif
|
||||
2802
src/device_controller/linux_keycode.h
Normal file
861
src/device_controller/macos_keycode.h
Normal file
@@ -0,0 +1,861 @@
|
||||
/*
|
||||
File: HIToolbox/Events.h
|
||||
|
||||
Contains: Event Manager Interfaces.
|
||||
|
||||
Copyright: 锟<> 1985-2008 by Apple Computer, Inc., all rights reserved
|
||||
|
||||
Bugs?: For bug reports, consult the following page on
|
||||
the World Wide Web:
|
||||
|
||||
http://developer.apple.com/bugreporter/
|
||||
|
||||
*/
|
||||
#ifndef __EVENTS__
|
||||
#define __EVENTS__
|
||||
|
||||
#ifndef __APPLICATIONSERVICES__
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#endif
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
#if PRAGMA_ONCE
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 2)
|
||||
|
||||
typedef UInt16 EventKind;
|
||||
typedef UInt16 EventMask;
|
||||
enum {
|
||||
nullEvent = 0,
|
||||
mouseDown = 1,
|
||||
mouseUp = 2,
|
||||
keyDown = 3,
|
||||
keyUp = 4,
|
||||
autoKey = 5,
|
||||
updateEvt = 6,
|
||||
diskEvt = 7, /* Not sent in Carbon. See kEventClassVolume in CarbonEvents.h*/
|
||||
activateEvt = 8,
|
||||
osEvt = 15,
|
||||
kHighLevelEvent = 23
|
||||
};
|
||||
|
||||
enum {
|
||||
mDownMask = 1 << mouseDown, /* mouse button pressed*/
|
||||
mUpMask = 1 << mouseUp, /* mouse button released*/
|
||||
keyDownMask = 1 << keyDown, /* key pressed*/
|
||||
keyUpMask = 1 << keyUp, /* key released*/
|
||||
autoKeyMask = 1 << autoKey, /* key repeatedly held down*/
|
||||
updateMask = 1 << updateEvt, /* window needs updating*/
|
||||
diskMask = 1 << diskEvt, /* disk inserted*/
|
||||
activMask = 1 << activateEvt, /* activate/deactivate window*/
|
||||
highLevelEventMask = 0x0400, /* high-level events (includes AppleEvents)*/
|
||||
osMask = 1 << osEvt, /* operating system events (suspend, resume)*/
|
||||
everyEvent = 0xFFFF /* all of the above*/
|
||||
};
|
||||
|
||||
enum {
|
||||
charCodeMask = 0x000000FF,
|
||||
keyCodeMask = 0x0000FF00,
|
||||
adbAddrMask = 0x00FF0000,
|
||||
osEvtMessageMask = (UInt32)0xFF000000
|
||||
};
|
||||
|
||||
enum {
|
||||
/* OS event messages. Event (sub)code is in the high byte of the message
|
||||
field.*/
|
||||
mouseMovedMessage = 0x00FA,
|
||||
suspendResumeMessage = 0x0001
|
||||
};
|
||||
|
||||
enum {
|
||||
resumeFlag = 1 /* Bit 0 of message indicates resume vs suspend*/
|
||||
};
|
||||
|
||||
#if CALL_NOT_IN_CARBON
|
||||
/* convertClipboardFlag is not ever set under Carbon. This is because scrap
|
||||
* conversion is */
|
||||
/* not tied to suspend/resume events any longer. Your application should
|
||||
* instead use the */
|
||||
/* scrap promise mechanism and fulfill scrap requests only when your promise
|
||||
* keeper proc */
|
||||
/* is called. If you need to know if the scrap has changed, you can cache the
|
||||
* last */
|
||||
/* ScrapRef you received and compare it with the current ScrapRef */
|
||||
enum {
|
||||
convertClipboardFlag =
|
||||
2 /* Bit 1 in resume message indicates clipboard change*/
|
||||
};
|
||||
|
||||
#endif /* CALL_NOT_IN_CARBON */
|
||||
|
||||
/*
|
||||
CARBON ALERT! BATTLESTATIONS!
|
||||
|
||||
The EventModifiers bits defined here are also used in the newer Carbon Event
|
||||
key modifiers parameters. There are two main differences:
|
||||
|
||||
1) The Carbon key modifiers parameter is a UInt32, not a UInt16. Never try
|
||||
to extract the key modifiers parameter from a Carbon Event into an
|
||||
EventModifiers type. You will probably get your stack trashed. 2) The Carbon
|
||||
key modifiers is just that: key modifiers. That parameter will never contain
|
||||
the button state bit.
|
||||
*/
|
||||
typedef UInt16 EventModifiers;
|
||||
enum {
|
||||
/* modifiers */
|
||||
activeFlagBit = 0, /* activate? (activateEvt and mouseDown)*/
|
||||
btnStateBit = 7, /* state of button?*/
|
||||
cmdKeyBit = 8, /* command key down?*/
|
||||
shiftKeyBit = 9, /* shift key down?*/
|
||||
alphaLockBit = 10, /* alpha lock down?*/
|
||||
optionKeyBit = 11, /* option key down?*/
|
||||
controlKeyBit = 12, /* control key down?*/
|
||||
rightShiftKeyBit = 13, /* right shift key down? Not supported on Mac OS X.*/
|
||||
rightOptionKeyBit = 14, /* right Option key down? Not supported on Mac OS X.*/
|
||||
rightControlKeyBit =
|
||||
15 /* right Control key down? Not supported on Mac OS X.*/
|
||||
};
|
||||
|
||||
enum {
|
||||
activeFlag = 1 << activeFlagBit,
|
||||
btnState = 1 << btnStateBit,
|
||||
cmdKey = 1 << cmdKeyBit,
|
||||
shiftKey = 1 << shiftKeyBit,
|
||||
alphaLock = 1 << alphaLockBit,
|
||||
optionKey = 1 << optionKeyBit,
|
||||
controlKey = 1 << controlKeyBit,
|
||||
rightShiftKey = 1 << rightShiftKeyBit, /* Not supported on Mac OS X.*/
|
||||
rightOptionKey = 1 << rightOptionKeyBit, /* Not supported on Mac OS X.*/
|
||||
rightControlKey = 1 << rightControlKeyBit /* Not supported on Mac OS X.*/
|
||||
};
|
||||
|
||||
/* MacRoman character codes*/
|
||||
enum {
|
||||
kNullCharCode = 0,
|
||||
kHomeCharCode = 1,
|
||||
kEnterCharCode = 3,
|
||||
kEndCharCode = 4,
|
||||
kHelpCharCode = 5,
|
||||
kBellCharCode = 7,
|
||||
kBackspaceCharCode = 8,
|
||||
kTabCharCode = 9,
|
||||
kLineFeedCharCode = 10,
|
||||
kVerticalTabCharCode = 11,
|
||||
kPageUpCharCode = 11,
|
||||
kFormFeedCharCode = 12,
|
||||
kPageDownCharCode = 12,
|
||||
kReturnCharCode = 13,
|
||||
kFunctionKeyCharCode = 16,
|
||||
kCommandCharCode = 17, /* glyph available only in system fonts*/
|
||||
kCheckCharCode = 18, /* glyph available only in system fonts*/
|
||||
kDiamondCharCode = 19, /* glyph available only in system fonts*/
|
||||
kAppleLogoCharCode = 20, /* glyph available only in system fonts*/
|
||||
kEscapeCharCode = 27,
|
||||
kClearCharCode = 27,
|
||||
kLeftArrowCharCode = 28,
|
||||
kRightArrowCharCode = 29,
|
||||
kUpArrowCharCode = 30,
|
||||
kDownArrowCharCode = 31,
|
||||
kSpaceCharCode = 32,
|
||||
kDeleteCharCode = 127,
|
||||
kBulletCharCode = 165,
|
||||
kNonBreakingSpaceCharCode = 202
|
||||
};
|
||||
|
||||
/* useful Unicode code points*/
|
||||
enum {
|
||||
kShiftUnicode = 0x21E7, /* Unicode UPWARDS WHITE ARROW*/
|
||||
kControlUnicode = 0x2303, /* Unicode UP ARROWHEAD*/
|
||||
kOptionUnicode = 0x2325, /* Unicode OPTION KEY*/
|
||||
kCommandUnicode = 0x2318, /* Unicode PLACE OF INTEREST SIGN*/
|
||||
kPencilUnicode = 0x270E, /* Unicode LOWER RIGHT PENCIL; actually pointed left
|
||||
until Mac OS X 10.3*/
|
||||
kPencilLeftUnicode = 0xF802, /* Unicode LOWER LEFT PENCIL; available in Mac OS
|
||||
X 10.3 and later*/
|
||||
kCheckUnicode = 0x2713, /* Unicode CHECK MARK*/
|
||||
kDiamondUnicode = 0x25C6, /* Unicode BLACK DIAMOND*/
|
||||
kBulletUnicode = 0x2022, /* Unicode BULLET*/
|
||||
kAppleLogoUnicode = 0xF8FF /* Unicode APPLE LOGO*/
|
||||
};
|
||||
|
||||
/*
|
||||
* Summary:
|
||||
* Virtual keycodes
|
||||
*
|
||||
* Discussion:
|
||||
* These constants are the virtual keycodes defined originally in
|
||||
* Inside Mac Volume V, pg. V-191. They identify physical keys on a
|
||||
* keyboard. Those constants with "ANSI" in the name are labeled
|
||||
* according to the key position on an ANSI-standard US keyboard.
|
||||
* For example, kVK_ANSI_A indicates the virtual keycode for the key
|
||||
* with the letter 'A' in the US keyboard layout. Other keyboard
|
||||
* layouts may have the 'A' key label on a different physical key;
|
||||
* in this case, pressing 'A' will generate a different virtual
|
||||
* keycode.
|
||||
*/
|
||||
enum {
|
||||
kVK_ANSI_A = 0x00,
|
||||
kVK_ANSI_S = 0x01,
|
||||
kVK_ANSI_D = 0x02,
|
||||
kVK_ANSI_F = 0x03,
|
||||
kVK_ANSI_H = 0x04,
|
||||
kVK_ANSI_G = 0x05,
|
||||
kVK_ANSI_Z = 0x06,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_ANSI_C = 0x08,
|
||||
kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_B = 0x0B,
|
||||
kVK_ANSI_Q = 0x0C,
|
||||
kVK_ANSI_W = 0x0D,
|
||||
kVK_ANSI_E = 0x0E,
|
||||
kVK_ANSI_R = 0x0F,
|
||||
kVK_ANSI_Y = 0x10,
|
||||
kVK_ANSI_T = 0x11,
|
||||
kVK_ANSI_1 = 0x12,
|
||||
kVK_ANSI_2 = 0x13,
|
||||
kVK_ANSI_3 = 0x14,
|
||||
kVK_ANSI_4 = 0x15,
|
||||
kVK_ANSI_6 = 0x16,
|
||||
kVK_ANSI_5 = 0x17,
|
||||
kVK_ANSI_Equal = 0x18,
|
||||
kVK_ANSI_9 = 0x19,
|
||||
kVK_ANSI_7 = 0x1A,
|
||||
kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_8 = 0x1C,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_RightBracket = 0x1E,
|
||||
kVK_ANSI_O = 0x1F,
|
||||
kVK_ANSI_U = 0x20,
|
||||
kVK_ANSI_LeftBracket = 0x21,
|
||||
kVK_ANSI_I = 0x22,
|
||||
kVK_ANSI_P = 0x23,
|
||||
kVK_ANSI_L = 0x25,
|
||||
kVK_ANSI_J = 0x26,
|
||||
kVK_ANSI_Quote = 0x27,
|
||||
kVK_ANSI_K = 0x28,
|
||||
kVK_ANSI_Semicolon = 0x29,
|
||||
kVK_ANSI_Backslash = 0x2A,
|
||||
kVK_ANSI_Comma = 0x2B,
|
||||
kVK_ANSI_Slash = 0x2C,
|
||||
kVK_ANSI_N = 0x2D,
|
||||
kVK_ANSI_M = 0x2E,
|
||||
kVK_ANSI_Period = 0x2F,
|
||||
kVK_ANSI_Grave = 0x32,
|
||||
kVK_ANSI_KeypadDecimal = 0x41,
|
||||
kVK_ANSI_KeypadMultiply = 0x43,
|
||||
kVK_ANSI_KeypadPlus = 0x45,
|
||||
kVK_ANSI_KeypadClear = 0x47,
|
||||
kVK_ANSI_KeypadDivide = 0x4B,
|
||||
kVK_ANSI_KeypadEnter = 0x4C,
|
||||
kVK_ANSI_KeypadMinus = 0x4E,
|
||||
kVK_ANSI_KeypadEquals = 0x51,
|
||||
kVK_ANSI_Keypad0 = 0x52,
|
||||
kVK_ANSI_Keypad1 = 0x53,
|
||||
kVK_ANSI_Keypad2 = 0x54,
|
||||
kVK_ANSI_Keypad3 = 0x55,
|
||||
kVK_ANSI_Keypad4 = 0x56,
|
||||
kVK_ANSI_Keypad5 = 0x57,
|
||||
kVK_ANSI_Keypad6 = 0x58,
|
||||
kVK_ANSI_Keypad7 = 0x59,
|
||||
kVK_ANSI_Keypad8 = 0x5B,
|
||||
kVK_ANSI_Keypad9 = 0x5C
|
||||
};
|
||||
|
||||
/* keycodes for keys that are independent of keyboard layout*/
|
||||
enum {
|
||||
kVK_Return = 0x24,
|
||||
kVK_Tab = 0x30,
|
||||
kVK_Space = 0x31,
|
||||
kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35,
|
||||
kVK_Command = 0x37,
|
||||
kVK_Shift = 0x38,
|
||||
kVK_CapsLock = 0x39,
|
||||
kVK_Option = 0x3A,
|
||||
kVK_Control = 0x3B,
|
||||
kVK_RightCommand = 0x36,
|
||||
kVK_RightShift = 0x3C,
|
||||
kVK_RightOption = 0x3D,
|
||||
kVK_RightControl = 0x3E,
|
||||
kVK_Function = 0x3F,
|
||||
kVK_F17 = 0x40,
|
||||
kVK_VolumeUp = 0x48,
|
||||
kVK_VolumeDown = 0x49,
|
||||
kVK_Mute = 0x4A,
|
||||
kVK_F18 = 0x4F,
|
||||
kVK_F19 = 0x50,
|
||||
kVK_F20 = 0x5A,
|
||||
kVK_F5 = 0x60,
|
||||
kVK_F6 = 0x61,
|
||||
kVK_F7 = 0x62,
|
||||
kVK_F3 = 0x63,
|
||||
kVK_F8 = 0x64,
|
||||
kVK_F9 = 0x65,
|
||||
kVK_F11 = 0x67,
|
||||
kVK_F13 = 0x69,
|
||||
kVK_F16 = 0x6A,
|
||||
kVK_F14 = 0x6B,
|
||||
kVK_F10 = 0x6D,
|
||||
kVK_ContextualMenu = 0x6E,
|
||||
kVK_F12 = 0x6F,
|
||||
kVK_F15 = 0x71,
|
||||
kVK_Help = 0x72,
|
||||
kVK_Home = 0x73,
|
||||
kVK_PageUp = 0x74,
|
||||
kVK_ForwardDelete = 0x75,
|
||||
kVK_F4 = 0x76,
|
||||
kVK_End = 0x77,
|
||||
kVK_F2 = 0x78,
|
||||
kVK_PageDown = 0x79,
|
||||
kVK_F1 = 0x7A,
|
||||
kVK_LeftArrow = 0x7B,
|
||||
kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D,
|
||||
kVK_UpArrow = 0x7E
|
||||
};
|
||||
|
||||
/* ISO keyboards only*/
|
||||
enum { kVK_ISO_Section = 0x0A };
|
||||
|
||||
/* JIS keyboards only*/
|
||||
enum {
|
||||
kVK_JIS_Yen = 0x5D,
|
||||
kVK_JIS_Underscore = 0x5E,
|
||||
kVK_JIS_KeypadComma = 0x5F,
|
||||
kVK_JIS_Eisu = 0x66,
|
||||
kVK_JIS_Kana = 0x68
|
||||
};
|
||||
|
||||
struct EventRecord {
|
||||
EventKind what;
|
||||
unsigned long message;
|
||||
UInt32 when;
|
||||
Point where;
|
||||
EventModifiers modifiers;
|
||||
};
|
||||
typedef struct EventRecord EventRecord;
|
||||
typedef CALLBACK_API(void, FKEYProcPtr)(void);
|
||||
typedef STACK_UPP_TYPE(FKEYProcPtr) FKEYUPP;
|
||||
/*
|
||||
* NewFKEYUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* DisposeFKEYUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* InvokeFKEYUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* GetMouse() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use HIGetMousePosition instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.5 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void GetMouse(Point *mouseLoc)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* Button() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use GetCurrentButtonState or GetCurrentEventButtonState instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework but
|
||||
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
|
||||
* CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean Button(void)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* StillDown()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean StillDown(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* WaitMouseUp()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean WaitMouseUp(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* KeyTranslate() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use UCKeyTranslate instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt32 KeyTranslate(const void *transData, UInt16 keycode, UInt32 *state)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* GetCaretTime()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt32 GetCaretTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
QuickTime 3.0 supports GetKeys() on unix and win32
|
||||
But, on little endian machines you will have to be
|
||||
careful about bit numberings and/or use a KeyMapByteArray
|
||||
instead.
|
||||
*/
|
||||
#if TARGET_API_MAC_OS8
|
||||
|
||||
typedef UInt32 KeyMap[4];
|
||||
#else
|
||||
typedef BigEndianUInt32 KeyMap[4];
|
||||
#endif /* TARGET_API_MAC_OS8 */
|
||||
|
||||
typedef UInt8 KeyMapByteArray[16];
|
||||
/*
|
||||
* GetKeys()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void GetKeys(KeyMap theKeys) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/* Obsolete event types & masks */
|
||||
enum {
|
||||
networkEvt = 10,
|
||||
driverEvt = 11,
|
||||
app1Evt = 12,
|
||||
app2Evt = 13,
|
||||
app3Evt = 14,
|
||||
app4Evt = 15,
|
||||
networkMask = 0x0400,
|
||||
driverMask = 0x0800,
|
||||
app1Mask = 0x1000,
|
||||
app2Mask = 0x2000,
|
||||
app3Mask = 0x4000,
|
||||
app4Mask = 0x8000
|
||||
};
|
||||
|
||||
struct EvQEl {
|
||||
QElemPtr qLink;
|
||||
SInt16 qType;
|
||||
EventKind
|
||||
evtQWhat; /* this part is identical to the EventRecord as defined above */
|
||||
unsigned long evtQMessage;
|
||||
UInt32 evtQWhen;
|
||||
Point evtQWhere;
|
||||
EventModifiers evtQModifiers;
|
||||
};
|
||||
typedef struct EvQEl EvQEl;
|
||||
typedef EvQEl *EvQElPtr;
|
||||
typedef CALLBACK_API(void, GetNextEventFilterProcPtr)(EventRecord *theEvent,
|
||||
Boolean *result);
|
||||
typedef STACK_UPP_TYPE(GetNextEventFilterProcPtr) GetNextEventFilterUPP;
|
||||
/*
|
||||
* NewGetNextEventFilterUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* DisposeGetNextEventFilterUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* InvokeGetNextEventFilterUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
typedef GetNextEventFilterUPP GNEFilterUPP;
|
||||
#if !__LP64__
|
||||
/*
|
||||
* GetDblTime()
|
||||
*
|
||||
* Summary:
|
||||
* Returns the maximum time (in units of 1/60th of a second) allowed
|
||||
* between two consecutive mouse-down events in order for the second
|
||||
* click to be considered a double-click.
|
||||
*
|
||||
* Discussion:
|
||||
* In 64-bit applications, you may replace calls to this API with
|
||||
* calls to NXClickTime (declared in
|
||||
* <IOKit/hidsystem/event_status_driver.h>) or with +[NSEvent
|
||||
* doubleClickInterval] (available in Mac OS X 10.6 and later).
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Result:
|
||||
* The maximum time between mouse-downs allowed for a double-click.
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt32 GetDblTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* SetEventMask()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void SetEventMask(EventMask value)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* GetNextEvent() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use ReceiveNextEvent instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean GetNextEvent(EventMask eventMask, EventRecord *theEvent)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* WaitNextEvent() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use ReceiveNextEvent instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean WaitNextEvent(EventMask eventMask, EventRecord *theEvent,
|
||||
UInt32 sleep, RgnHandle mouseRgn) /* can be NULL */
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* EventAvail() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use FindSpecificEventInQueue instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean EventAvail(EventMask eventMask, EventRecord *theEvent)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* PostEvent() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use PostEventToQueue or CGEventPost instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern OSErr PostEvent(EventKind eventNum, UInt32 eventMsg)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* FlushEvents() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use FlushEventsMatchingListFromQueue,
|
||||
* FlushSpecificEventsFromQueue, or FlushEventQueue instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework but
|
||||
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
|
||||
* CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void FlushEvents(EventMask whichMask, EventMask stopMask)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
#if OLDROUTINENAMES
|
||||
#define KeyTrans(transData, keycode, state) \
|
||||
KeyTranslate(transData, keycode, state)
|
||||
#endif /* OLDROUTINENAMES */
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* KeyScript() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use TISSelectInputSource API for positive verbs (ScriptCode).
|
||||
* Use TSMDocument properties to restrict input sources:
|
||||
* kTSMDocumentEnabledInputSourcesPropertyTag
|
||||
* kTSMDocumentInputSourceOverridePropertyTag
|
||||
*
|
||||
* Summary:
|
||||
* Switch to the specified script's default (last used) input source.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.5 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void KeyScript(short code)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* IsCmdChar() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use IsUserCancelEventRef or CheckEventQueueForUserCancel instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework but
|
||||
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
|
||||
* CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean IsCmdChar(const EventRecord *event, short test)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
LowMem accessor functions previously in LowMem.h
|
||||
*/
|
||||
/*
|
||||
* LMGetKeyThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern SInt16 LMGetKeyThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKeyThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKeyThresh(SInt16 value)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* LMGetKeyRepThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern SInt16 LMGetKeyRepThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKeyRepThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKeyRepThresh(SInt16 value)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* LMGetKbdLast()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt8 LMGetKbdLast(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKbdLast()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKbdLast(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* LMGetKbdType()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt8 LMGetKbdType(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKbdType()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKbdType(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __EVENTS__ */
|
||||
@@ -1,127 +1,124 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include "log.h"
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {
|
||||
if (uinput_fd_) {
|
||||
ioctl(uinput_fd_, UI_DEV_DESTROY);
|
||||
close(uinput_fd_);
|
||||
}
|
||||
}
|
||||
MouseController::~MouseController() { Destroy(); }
|
||||
|
||||
int MouseController::Init(int screen_width, int screen_height) {
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
|
||||
uinput_fd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||
if (uinput_fd_ < 0) {
|
||||
LOG_ERROR("Cannot open device: /dev/uinput");
|
||||
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
|
||||
display_info_list_ = display_info_list;
|
||||
display_ = XOpenDisplay(NULL);
|
||||
if (!display_) {
|
||||
LOG_ERROR("Cannot connect to X server");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ioctl(uinput_fd_, UI_SET_EVBIT, EV_KEY);
|
||||
ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_RIGHT);
|
||||
ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_LEFT);
|
||||
ioctl(uinput_fd_, UI_SET_EVBIT, EV_ABS);
|
||||
ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_X);
|
||||
ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_Y);
|
||||
ioctl(uinput_fd_, UI_SET_EVBIT, EV_REL);
|
||||
root_ = DefaultRootWindow(display_);
|
||||
|
||||
struct uinput_user_dev uidev;
|
||||
memset(&uidev, 0, sizeof(uidev));
|
||||
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "VirtualMouse");
|
||||
uidev.id.bustype = BUS_USB;
|
||||
uidev.id.version = 1;
|
||||
uidev.id.vendor = 0x1;
|
||||
uidev.id.product = 0x1;
|
||||
uidev.absmin[ABS_X] = 0;
|
||||
uidev.absmax[ABS_X] = screen_width_;
|
||||
uidev.absmin[ABS_Y] = 0;
|
||||
uidev.absmax[ABS_Y] = screen_height_;
|
||||
|
||||
int res_uidev = write(uinput_fd_, &uidev, sizeof(uidev));
|
||||
ioctl(uinput_fd_, UI_DEV_CREATE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
int MouseController::SendCommand(RemoteAction remote_action) {
|
||||
int mouse_pos_x = remote_action.m.x;
|
||||
int mouse_pos_y = remote_action.m.y;
|
||||
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, NULL);
|
||||
|
||||
if (remote_action.m.flag == MouseFlag::left_down) {
|
||||
SimulateKeyDown(uinput_fd_, BTN_LEFT);
|
||||
} else if (remote_action.m.flag == MouseFlag::left_up) {
|
||||
SimulateKeyUp(uinput_fd_, BTN_LEFT);
|
||||
} else if (remote_action.m.flag == MouseFlag::right_down) {
|
||||
SimulateKeyDown(uinput_fd_, BTN_RIGHT);
|
||||
} else if (remote_action.m.flag == MouseFlag::right_up) {
|
||||
SimulateKeyUp(uinput_fd_, BTN_RIGHT);
|
||||
} else {
|
||||
SetMousePosition(uinput_fd_, mouse_pos_x, mouse_pos_y);
|
||||
}
|
||||
int event_base, error_base, major_version, minor_version;
|
||||
if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version,
|
||||
&minor_version)) {
|
||||
LOG_ERROR("XTest extension not available");
|
||||
XCloseDisplay(display_);
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyDown(int fd, int kval) {
|
||||
int res_ev = 0;
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, 0);
|
||||
|
||||
event.type = EV_KEY;
|
||||
event.value = 1;
|
||||
event.code = kval;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
|
||||
event.type = EV_SYN;
|
||||
event.value = 0;
|
||||
event.code = SYN_REPORT;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
int MouseController::Destroy() {
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
display_ = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyUp(int fd, int kval) {
|
||||
int res_ev = 0;
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, 0);
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
int display_index) {
|
||||
switch (remote_action.type) {
|
||||
case mouse:
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::move:
|
||||
SetMousePosition(
|
||||
static_cast<int>(remote_action.m.x *
|
||||
display_info_list_[display_index].width +
|
||||
display_info_list_[display_index].left),
|
||||
static_cast<int>(remote_action.m.y *
|
||||
display_info_list_[display_index].height +
|
||||
display_info_list_[display_index].top));
|
||||
break;
|
||||
case MouseFlag::left_down:
|
||||
XTestFakeButtonEvent(display_, 1, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
XTestFakeButtonEvent(display_, 1, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
XTestFakeButtonEvent(display_, 3, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
XTestFakeButtonEvent(display_, 3, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
XTestFakeButtonEvent(display_, 2, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
XTestFakeButtonEvent(display_, 2, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::wheel_vertical: {
|
||||
if (remote_action.m.s > 0) {
|
||||
SimulateMouseWheel(4, remote_action.m.s);
|
||||
} else if (remote_action.m.s < 0) {
|
||||
SimulateMouseWheel(5, -remote_action.m.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MouseFlag::wheel_horizontal: {
|
||||
if (remote_action.m.s > 0) {
|
||||
SimulateMouseWheel(6, remote_action.m.s);
|
||||
} else if (remote_action.m.s < 0) {
|
||||
SimulateMouseWheel(7, -remote_action.m.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
event.type = EV_KEY;
|
||||
event.value = 0;
|
||||
event.code = kval;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
|
||||
event.type = EV_SYN;
|
||||
event.value = 0;
|
||||
event.code = SYN_REPORT;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SetMousePosition(int fd, int x, int y) {
|
||||
struct input_event ev[2], ev_sync;
|
||||
memset(ev, 0, sizeof(ev));
|
||||
memset(&ev_sync, 0, sizeof(ev_sync));
|
||||
|
||||
ev[0].type = EV_ABS;
|
||||
ev[0].code = ABS_X;
|
||||
ev[0].value = x;
|
||||
ev[1].type = EV_ABS;
|
||||
ev[1].code = ABS_Y;
|
||||
ev[1].value = y;
|
||||
int res_w = write(fd, ev, sizeof(ev));
|
||||
|
||||
ev_sync.type = EV_SYN;
|
||||
ev_sync.value = 0;
|
||||
ev_sync.code = 0;
|
||||
int res_ev_sync = write(fd, &ev_sync, sizeof(ev_sync));
|
||||
void MouseController::SetMousePosition(int x, int y) {
|
||||
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyDown(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyUp(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateMouseWheel(int direction_button, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
XTestFakeButtonEvent(display_, direction_button, True, CurrentTime);
|
||||
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
|
||||
}
|
||||
XFlush(display_);
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
* @Date: 2025-05-07
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/uinput.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class MouseController : public DeviceController {
|
||||
@@ -22,18 +21,19 @@ class MouseController : public DeviceController {
|
||||
virtual ~MouseController();
|
||||
|
||||
public:
|
||||
virtual int Init(int screen_width, int screen_height);
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendCommand(RemoteAction remote_action);
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
|
||||
private:
|
||||
void SimulateKeyDown(int fd, int kval);
|
||||
void SimulateKeyUp(int fd, int kval);
|
||||
void SetMousePosition(int fd, int x, int y);
|
||||
void SimulateKeyDown(int kval);
|
||||
void SimulateKeyUp(int kval);
|
||||
void SetMousePosition(int x, int y);
|
||||
void SimulateMouseWheel(int direction_button, int count);
|
||||
|
||||
private:
|
||||
int uinput_fd_;
|
||||
struct uinput_user_dev uinput_dev_;
|
||||
Display* display_ = nullptr;
|
||||
Window root_ = 0;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
int screen_width_ = 0;
|
||||
int screen_height_ = 0;
|
||||
};
|
||||
|
||||
@@ -8,47 +8,94 @@ MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {}
|
||||
|
||||
int MouseController::Init(int screen_width, int screen_height) {
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
|
||||
pixel_width_ =
|
||||
CGDisplayModeGetPixelWidth(CGDisplayCopyDisplayMode(CGMainDisplayID()));
|
||||
pixel_height_ =
|
||||
CGDisplayModeGetPixelHeight(CGDisplayCopyDisplayMode(CGMainDisplayID()));
|
||||
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
|
||||
display_info_list_ = display_info_list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
int MouseController::SendCommand(RemoteAction remote_action) {
|
||||
int mouse_pos_x = remote_action.m.x * screen_width_ / pixel_width_;
|
||||
int mouse_pos_y = remote_action.m.y * screen_height_ / pixel_height_;
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
int display_index) {
|
||||
int mouse_pos_x =
|
||||
remote_action.m.x * display_info_list_[display_index].width +
|
||||
display_info_list_[display_index].left;
|
||||
int mouse_pos_y =
|
||||
remote_action.m.y * display_info_list_[display_index].height +
|
||||
display_info_list_[display_index].top;
|
||||
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
CGEventRef mouse_event;
|
||||
CGEventRef mouse_event = nullptr;
|
||||
CGEventType mouse_type;
|
||||
CGMouseButton mouse_button;
|
||||
CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y);
|
||||
|
||||
if (remote_action.m.flag == MouseFlag::left_down) {
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::left_down:
|
||||
mouse_type = kCGEventLeftMouseDown;
|
||||
} else if (remote_action.m.flag == MouseFlag::left_up) {
|
||||
left_dragging_ = true;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonLeft);
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
mouse_type = kCGEventLeftMouseUp;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_down) {
|
||||
left_dragging_ = false;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonLeft);
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
mouse_type = kCGEventRightMouseDown;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_up) {
|
||||
right_dragging_ = true;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonRight);
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
mouse_type = kCGEventRightMouseUp;
|
||||
right_dragging_ = false;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonRight);
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
mouse_type = kCGEventOtherMouseDown;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonCenter);
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
mouse_type = kCGEventOtherMouseUp;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonCenter);
|
||||
break;
|
||||
case MouseFlag::wheel_vertical:
|
||||
mouse_event = CGEventCreateScrollWheelEvent(
|
||||
NULL, kCGScrollEventUnitLine, 2, remote_action.m.s, 0);
|
||||
break;
|
||||
case MouseFlag::wheel_horizontal:
|
||||
mouse_event = CGEventCreateScrollWheelEvent(
|
||||
NULL, kCGScrollEventUnitLine, 2, 0, remote_action.m.s);
|
||||
break;
|
||||
default:
|
||||
if (left_dragging_) {
|
||||
mouse_type = kCGEventLeftMouseDragged;
|
||||
mouse_button = kCGMouseButtonLeft;
|
||||
} else if (right_dragging_) {
|
||||
mouse_type = kCGEventRightMouseDragged;
|
||||
mouse_button = kCGMouseButtonRight;
|
||||
} else {
|
||||
mouse_type = kCGEventMouseMoved;
|
||||
mouse_button = kCGMouseButtonLeft;
|
||||
}
|
||||
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type,
|
||||
CGPointMake(mouse_pos_x, mouse_pos_y),
|
||||
kCGMouseButtonLeft);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
mouse_button);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mouse_event) {
|
||||
CGEventPost(kCGHIDEventTap, mouse_event);
|
||||
CFRelease(mouse_event);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class MouseController : public DeviceController {
|
||||
@@ -15,16 +17,14 @@ class MouseController : public DeviceController {
|
||||
virtual ~MouseController();
|
||||
|
||||
public:
|
||||
virtual int Init(int screen_width, int screen_height);
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendCommand(RemoteAction remote_action);
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
|
||||
private:
|
||||
int screen_width_ = 0;
|
||||
int screen_height_ = 0;
|
||||
|
||||
int pixel_width_ = 0;
|
||||
int pixel_height_ = 0;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
bool left_dragging_ = false;
|
||||
bool right_dragging_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -6,34 +6,59 @@ MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {}
|
||||
|
||||
int MouseController::Init(int screen_width, int screen_height) {
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
|
||||
display_info_list_ = display_info_list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
int MouseController::SendCommand(RemoteAction remote_action) {
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
int display_index) {
|
||||
INPUT ip;
|
||||
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
ip.type = INPUT_MOUSE;
|
||||
ip.mi.dx = (LONG)remote_action.m.x;
|
||||
ip.mi.dy = (LONG)remote_action.m.y;
|
||||
if (remote_action.m.flag == MouseFlag::left_down) {
|
||||
ip.mi.dx =
|
||||
(LONG)(remote_action.m.x * display_info_list_[display_index].width) +
|
||||
display_info_list_[display_index].left;
|
||||
ip.mi.dy =
|
||||
(LONG)(remote_action.m.y * display_info_list_[display_index].height) +
|
||||
display_info_list_[display_index].top;
|
||||
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::left_down:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
|
||||
} else if (remote_action.m.flag == MouseFlag::left_up) {
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_down) {
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_up) {
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE;
|
||||
} else {
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_ABSOLUTE;
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_MIDDLEUP | MOUSEEVENTF_ABSOLUTE;
|
||||
break;
|
||||
case MouseFlag::wheel_vertical:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_WHEEL;
|
||||
ip.mi.mouseData = remote_action.m.s * 120;
|
||||
break;
|
||||
case MouseFlag::wheel_horizontal:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_HWHEEL;
|
||||
ip.mi.mouseData = remote_action.m.s * 120;
|
||||
break;
|
||||
default:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_MOVE;
|
||||
break;
|
||||
}
|
||||
ip.mi.mouseData = 0;
|
||||
|
||||
ip.mi.time = 0;
|
||||
|
||||
SetCursorPos(ip.mi.dx, ip.mi.dy);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class MouseController : public DeviceController {
|
||||
@@ -15,13 +17,12 @@ class MouseController : public DeviceController {
|
||||
virtual ~MouseController();
|
||||
|
||||
public:
|
||||
virtual int Init(int screen_width, int screen_height);
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendCommand(RemoteAction remote_action);
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
|
||||
private:
|
||||
int screen_width_ = 0;
|
||||
int screen_height_ = 0;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
};
|
||||
|
||||
#endif
|
||||
206
src/device_controller/windows_keycode.h
Normal file
@@ -0,0 +1,206 @@
|
||||
// virtual_key_codes.h
|
||||
#ifndef VIRTUAL_KEY_CODES_H
|
||||
#define VIRTUAL_KEY_CODES_H
|
||||
|
||||
#define VK_LBUTTON 0x01 // Left mouse button
|
||||
#define VK_RBUTTON 0x02 // Right mouse button
|
||||
#define VK_CANCEL 0x03 // Control-break processing
|
||||
#define VK_MBUTTON 0x04 // Middle mouse button
|
||||
#define VK_XBUTTON1 0x05 // X1 mouse button
|
||||
#define VK_XBUTTON2 0x06 // X2 mouse button
|
||||
// 0x07 Reserved
|
||||
#define VK_BACK 0x08 // Backspace key
|
||||
#define VK_TAB 0x09 // Tab key
|
||||
// 0x0A-0B Reserved
|
||||
#define VK_CLEAR 0x0C // Clear key
|
||||
#define VK_RETURN 0x0D // Enter key
|
||||
// 0x0E-0F Unassigned
|
||||
#define VK_SHIFT 0x10 // Shift key
|
||||
#define VK_CONTROL 0x11 // Ctrl key
|
||||
#define VK_MENU 0x12 // Alt key
|
||||
#define VK_PAUSE 0x13 // Pause key
|
||||
#define VK_CAPITAL 0x14 // Caps lock key
|
||||
#define VK_KANA 0x15 // IME Kana mode
|
||||
#define VK_HANGUL 0x15 // IME Hangul mode
|
||||
#define VK_IME_ON 0x16 // IME On
|
||||
#define VK_JUNJA 0x17 // IME Junja mode
|
||||
#define VK_FINAL 0x18 // IME final mode
|
||||
#define VK_HANJA 0x19 // IME Hanja mode
|
||||
#define VK_KANJI 0x19 // IME Kanji mode
|
||||
#define VK_IME_OFF 0x1A // IME Off
|
||||
#define VK_ESCAPE 0x1B // Esc key
|
||||
#define VK_CONVERT 0x1C // IME convert
|
||||
#define VK_NONCONVERT 0x1D // IME nonconvert
|
||||
#define VK_ACCEPT 0x1E // IME accept
|
||||
#define VK_MODECHANGE 0x1F // IME mode change request
|
||||
#define VK_SPACE 0x20 // Spacebar key
|
||||
#define VK_PRIOR 0x21 // Page up key
|
||||
#define VK_NEXT 0x22 // Page down key
|
||||
#define VK_END 0x23 // End key
|
||||
#define VK_HOME 0x24 // Home key
|
||||
#define VK_LEFT 0x25 // Left arrow key
|
||||
#define VK_UP 0x26 // Up arrow key
|
||||
#define VK_RIGHT 0x27 // Right arrow key
|
||||
#define VK_DOWN 0x28 // Down arrow key
|
||||
#define VK_SELECT 0x29 // Select key
|
||||
#define VK_PRINT 0x2A // Print key
|
||||
#define VK_EXECUTE 0x2B // Execute key
|
||||
#define VK_SNAPSHOT 0x2C // Print screen key
|
||||
#define VK_INSERT 0x2D // Insert key
|
||||
#define VK_DELETE 0x2E // Delete key
|
||||
#define VK_HELP 0x2F // Help key
|
||||
|
||||
#define VK_0 0x30 // 0 key
|
||||
#define VK_1 0x31 // 1 key
|
||||
#define VK_2 0x32 // 2 key
|
||||
#define VK_3 0x33 // 3 key
|
||||
#define VK_4 0x34 // 4 key
|
||||
#define VK_5 0x35 // 5 key
|
||||
#define VK_6 0x36 // 6 key
|
||||
#define VK_7 0x37 // 7 key
|
||||
#define VK_8 0x38 // 8 key
|
||||
#define VK_9 0x39 // 9 key
|
||||
// 0x3A-40 Undefined
|
||||
|
||||
#define VK_A 0x41 // A key
|
||||
#define VK_B 0x42 // B key
|
||||
#define VK_C 0x43 // C key
|
||||
#define VK_D 0x44 // D key
|
||||
#define VK_E 0x45 // E key
|
||||
#define VK_F 0x46 // F key
|
||||
#define VK_G 0x47 // G key
|
||||
#define VK_H 0x48 // H key
|
||||
#define VK_I 0x49 // I key
|
||||
#define VK_J 0x4A // J key
|
||||
#define VK_K 0x4B // K key
|
||||
#define VK_L 0x4C // L key
|
||||
#define VK_M 0x4D // M key
|
||||
#define VK_N 0x4E // N key
|
||||
#define VK_O 0x4F // O key
|
||||
#define VK_P 0x50 // P key
|
||||
#define VK_Q 0x51 // Q key
|
||||
#define VK_R 0x52 // R key
|
||||
#define VK_S 0x53 // S key
|
||||
#define VK_T 0x54 // T key
|
||||
#define VK_U 0x55 // U key
|
||||
#define VK_V 0x56 // V key
|
||||
#define VK_W 0x57 // W key
|
||||
#define VK_X 0x58 // X key
|
||||
#define VK_Y 0x59 // Y key
|
||||
#define VK_Z 0x5A // Z key
|
||||
|
||||
#define VK_LWIN 0x5B // Left Windows logo key
|
||||
#define VK_RWIN 0x5C // Right Windows logo key
|
||||
#define VK_APPS 0x5D // Application key
|
||||
// 0x5E Reserved
|
||||
#define VK_SLEEP 0x5F // Computer Sleep key
|
||||
|
||||
#define VK_NUMPAD0 0x60 // Numeric keypad 0 key
|
||||
#define VK_NUMPAD1 0x61 // Numeric keypad 1 key
|
||||
#define VK_NUMPAD2 0x62 // Numeric keypad 2 key
|
||||
#define VK_NUMPAD3 0x63 // Numeric keypad 3 key
|
||||
#define VK_NUMPAD4 0x64 // Numeric keypad 4 key
|
||||
#define VK_NUMPAD5 0x65 // Numeric keypad 5 key
|
||||
#define VK_NUMPAD6 0x66 // Numeric keypad 6 key
|
||||
#define VK_NUMPAD7 0x67 // Numeric keypad 7 key
|
||||
#define VK_NUMPAD8 0x68 // Numeric keypad 8 key
|
||||
#define VK_NUMPAD9 0x69 // Numeric keypad 9 key
|
||||
#define VK_MULTIPLY 0x6A // Multiply key
|
||||
#define VK_ADD 0x6B // Add key
|
||||
#define VK_SEPARATOR 0x6C // Separator key
|
||||
#define VK_SUBTRACT 0x6D // Subtract key
|
||||
#define VK_DECIMAL 0x6E // Decimal key
|
||||
#define VK_DIVIDE 0x6F // Divide key
|
||||
|
||||
#define VK_F1 0x70 // F1 key
|
||||
#define VK_F2 0x71 // F2 key
|
||||
#define VK_F3 0x72 // F3 key
|
||||
#define VK_F4 0x73 // F4 key
|
||||
#define VK_F5 0x74 // F5 key
|
||||
#define VK_F6 0x75 // F6 key
|
||||
#define VK_F7 0x76 // F7 key
|
||||
#define VK_F8 0x77 // F8 key
|
||||
#define VK_F9 0x78 // F9 key
|
||||
#define VK_F10 0x79 // F10 key
|
||||
#define VK_F11 0x7A // F11 key
|
||||
#define VK_F12 0x7B // F12 key
|
||||
#define VK_F13 0x7C // F13 key
|
||||
#define VK_F14 0x7D // F14 key
|
||||
#define VK_F15 0x7E // F15 key
|
||||
#define VK_F16 0x7F // F16 key
|
||||
#define VK_F17 0x80 // F17 key
|
||||
#define VK_F18 0x81 // F18 key
|
||||
#define VK_F19 0x82 // F19 key
|
||||
#define VK_F20 0x83 // F20 key
|
||||
#define VK_F21 0x84 // F21 key
|
||||
#define VK_F22 0x85 // F22 key
|
||||
#define VK_F23 0x86 // F23 key
|
||||
#define VK_F24 0x87 // F24 key
|
||||
// 0x88–0x8F Reserved
|
||||
|
||||
#define VK_NUMLOCK 0x90 // Num lock key
|
||||
#define VK_SCROLL 0x91 // Scroll lock key
|
||||
// 0x92–0x96 OEM specific
|
||||
// 0x97–0x9F Unassigned
|
||||
|
||||
#define VK_LSHIFT 0xA0 // Left Shift key
|
||||
#define VK_RSHIFT 0xA1 // Right Shift key
|
||||
#define VK_LCONTROL 0xA2 // Left Ctrl key
|
||||
#define VK_RCONTROL 0xA3 // Right Ctrl key
|
||||
#define VK_LMENU 0xA4 // Left Alt key
|
||||
#define VK_RMENU 0xA5 // Right Alt key
|
||||
|
||||
#define VK_BROWSER_BACK 0xA6 // Browser Back key
|
||||
#define VK_BROWSER_FORWARD 0xA7 // Browser Forward key
|
||||
#define VK_BROWSER_REFRESH 0xA8 // Browser Refresh key
|
||||
#define VK_BROWSER_STOP 0xA9 // Browser Stop key
|
||||
#define VK_BROWSER_SEARCH 0xAA // Browser Search key
|
||||
#define VK_BROWSER_FAVORITES 0xAB // Browser Favorites key
|
||||
#define VK_BROWSER_HOME 0xAC // Browser Start and Home key
|
||||
#define VK_VOLUME_MUTE 0xAD // Volume Mute key
|
||||
#define VK_VOLUME_DOWN 0xAE // Volume Down key
|
||||
#define VK_VOLUME_UP 0xAF // Volume Up key
|
||||
|
||||
#define VK_MEDIA_NEXT_TRACK 0xB0 // Next Track key
|
||||
#define VK_MEDIA_PREV_TRACK 0xB1 // Previous Track key
|
||||
#define VK_MEDIA_STOP 0xB2 // Stop Media key
|
||||
#define VK_MEDIA_PLAY_PAUSE 0xB3 // Play/Pause Media key
|
||||
#define VK_LAUNCH_MAIL 0xB4 // Start Mail key
|
||||
#define VK_LAUNCH_MEDIA_SELECT 0xB5 // Select Media key
|
||||
#define VK_LAUNCH_APP1 0xB6 // Start Application 1 key
|
||||
#define VK_LAUNCH_APP2 0xB7 // Start Application 2 key
|
||||
// 0xB8–0xB9 Reserved
|
||||
|
||||
#define VK_OEM_1 0xBA // For US: Semicolon/Colon key
|
||||
#define VK_OEM_PLUS 0xBB // Equals/Plus key
|
||||
#define VK_OEM_COMMA 0xBC // Comma/Less Than key
|
||||
#define VK_OEM_MINUS 0xBD // Dash/Underscore key
|
||||
#define VK_OEM_PERIOD 0xBE // Period/Greater Than key
|
||||
#define VK_OEM_2 0xBF // Slash/Question Mark key
|
||||
#define VK_OEM_3 0xC0 // Grave Accent/Tilde key
|
||||
// 0xC1–0xDA Reserved
|
||||
|
||||
#define VK_OEM_4 0xDB // Left Brace key
|
||||
#define VK_OEM_5 0xDC // Backslash/Pipe key
|
||||
#define VK_OEM_6 0xDD // Right Brace key
|
||||
#define VK_OEM_7 0xDE // Apostrophe/Quote key
|
||||
#define VK_OEM_8 0xDF // (Canadian CSA: Right Ctrl key)
|
||||
// 0xE0 Reserved
|
||||
#define VK_OEM_102 0xE2 // (European ISO: Backslash/Pipe key)
|
||||
// 0xE3–E4 OEM specific
|
||||
#define VK_PROCESSKEY 0xE5 // IME PROCESS key
|
||||
// 0xE6 OEM specific
|
||||
#define VK_PACKET 0xE7 // Unicode characters as keystrokes
|
||||
// 0xE8 Unassigned
|
||||
// 0xE9–F5 OEM specific
|
||||
#define VK_ATTN 0xF6 // Attn key
|
||||
#define VK_CRSEL 0xF7 // CrSel key
|
||||
#define VK_EXSEL 0xF8 // ExSel key
|
||||
#define VK_EREOF 0xF9 // Erase EOF key
|
||||
#define VK_PLAY 0xFA // Play key
|
||||
#define VK_ZOOM 0xFB // Zoom key
|
||||
#define VK_NONAME 0xFC // Reserved
|
||||
#define VK_PA1 0xFD // PA1 key
|
||||
#define VK_OEM_CLEAR 0xFE // Clear key
|
||||
|
||||
#endif // VIRTUAL_KEY_CODES_H
|
||||
2878
src/gui/assets/fonts/OPPOSans_Regular.h
Normal file
63
src/gui/assets/layouts/layout.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-06-14
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _LAYOUT_STYLE_H_
|
||||
#define _LAYOUT_STYLE_H_
|
||||
|
||||
#define MENU_WINDOW_WIDTH_CN 300
|
||||
#define MENU_WINDOW_HEIGHT_CN 280
|
||||
#define LOCAL_WINDOW_WIDTH_CN 300
|
||||
#define LOCAL_WINDOW_HEIGHT_CN 280
|
||||
#define REMOTE_WINDOW_WIDTH_CN 300
|
||||
#define REMOTE_WINDOW_HEIGHT_CN 280
|
||||
#define MENU_WINDOW_WIDTH_EN 190
|
||||
#define MENU_WINDOW_HEIGHT_EN 245
|
||||
#define IPUT_WINDOW_WIDTH 160
|
||||
#define INPUT_WINDOW_PADDING_CN 66
|
||||
#define INPUT_WINDOW_PADDING_EN 96
|
||||
#define SETTINGS_WINDOW_WIDTH_CN 202
|
||||
#define SETTINGS_WINDOW_WIDTH_EN 248
|
||||
#if _WIN32
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 345
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 345
|
||||
#else
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 315
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 315
|
||||
#endif
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 165
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 165
|
||||
#define LANGUAGE_SELECT_WINDOW_PADDING_CN 120
|
||||
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 167
|
||||
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120
|
||||
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167
|
||||
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 120
|
||||
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167
|
||||
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 120
|
||||
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167
|
||||
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_TURN_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_TURN_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_SRTP_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171
|
||||
#define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218
|
||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
|
||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN 137
|
||||
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90
|
||||
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN 137
|
||||
#define SETTINGS_SELECT_WINDOW_WIDTH 73
|
||||
#define SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH 130
|
||||
#define SETTINGS_OK_BUTTON_PADDING_CN 65
|
||||
#define SETTINGS_OK_BUTTON_PADDING_EN 83
|
||||
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78
|
||||
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91
|
||||
|
||||
#endif
|
||||
170
src/gui/assets/localization/localization.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
#ifndef _LOCALIZATION_H_
|
||||
#define _LOCALIZATION_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
namespace localization {
|
||||
|
||||
static std::vector<std::string> local_desktop = {
|
||||
reinterpret_cast<const char*>(u8"本桌面"), "Local Desktop"};
|
||||
static std::vector<std::string> local_id = {
|
||||
reinterpret_cast<const char*>(u8"本机ID"), "Local ID"};
|
||||
static std::vector<std::string> local_id_copied_to_clipboard = {
|
||||
reinterpret_cast<const char*>(u8"已复制到剪贴板"), "Copied to clipboard"};
|
||||
static std::vector<std::string> password = {
|
||||
reinterpret_cast<const char*>(u8"密码"), "Password"};
|
||||
static std::vector<std::string> max_password_len = {
|
||||
reinterpret_cast<const char*>(u8"最大6个字符"), "Max 6 chars"};
|
||||
|
||||
static std::vector<std::string> remote_desktop = {
|
||||
reinterpret_cast<const char*>(u8"控制远程桌面"), "Control Remote Desktop"};
|
||||
static std::vector<std::string> remote_id = {
|
||||
reinterpret_cast<const char*>(u8"对端ID"), "Remote ID"};
|
||||
static std::vector<std::string> connect = {
|
||||
reinterpret_cast<const char*>(u8"连接"), "Connect"};
|
||||
static std::vector<std::string> recent_connections = {
|
||||
reinterpret_cast<const char*>(u8"近期连接"), "Recent Connections"};
|
||||
static std::vector<std::string> disconnect = {
|
||||
reinterpret_cast<const char*>(u8"断开连接"), "Disconnect"};
|
||||
static std::vector<std::string> fullscreen = {
|
||||
reinterpret_cast<const char*>(u8"全屏"), " Fullscreen"};
|
||||
static std::vector<std::string> show_net_traffic_stats = {
|
||||
reinterpret_cast<const char*>(u8"显示流量统计"), "Show Net Traffic Stats"};
|
||||
static std::vector<std::string> hide_net_traffic_stats = {
|
||||
reinterpret_cast<const char*>(u8"隐藏流量统计"), "Hide Net Traffic Stats"};
|
||||
static std::vector<std::string> video = {
|
||||
reinterpret_cast<const char*>(u8"视频"), "Video"};
|
||||
static std::vector<std::string> audio = {
|
||||
reinterpret_cast<const char*>(u8"音频"), "Audio"};
|
||||
static std::vector<std::string> data = {reinterpret_cast<const char*>(u8"数据"),
|
||||
"Data"};
|
||||
static std::vector<std::string> total = {
|
||||
reinterpret_cast<const char*>(u8"总计"), "Total"};
|
||||
static std::vector<std::string> in = {reinterpret_cast<const char*>(u8"输入"),
|
||||
"In"};
|
||||
static std::vector<std::string> out = {reinterpret_cast<const char*>(u8"输出"),
|
||||
"Out"};
|
||||
static std::vector<std::string> loss_rate = {
|
||||
reinterpret_cast<const char*>(u8"丢包率"), "Loss Rate"};
|
||||
static std::vector<std::string> exit_fullscreen = {
|
||||
reinterpret_cast<const char*>(u8"退出全屏"), "Exit fullscreen"};
|
||||
static std::vector<std::string> control_mouse = {
|
||||
reinterpret_cast<const char*>(u8"控制"), "Control"};
|
||||
static std::vector<std::string> release_mouse = {
|
||||
reinterpret_cast<const char*>(u8"释放"), "Release"};
|
||||
static std::vector<std::string> audio_capture = {
|
||||
reinterpret_cast<const char*>(u8"声音"), "Audio"};
|
||||
static std::vector<std::string> mute = {
|
||||
reinterpret_cast<const char*>(u8" 静音"), " Mute"};
|
||||
static std::vector<std::string> settings = {
|
||||
reinterpret_cast<const char*>(u8"设置"), "Settings"};
|
||||
static std::vector<std::string> language = {
|
||||
reinterpret_cast<const char*>(u8"语言:"), "Language:"};
|
||||
static std::vector<std::string> language_zh = {
|
||||
reinterpret_cast<const char*>(u8"中文"), "Chinese"};
|
||||
static std::vector<std::string> language_en = {
|
||||
reinterpret_cast<const char*>(u8"英文"), "English"};
|
||||
static std::vector<std::string> video_quality = {
|
||||
reinterpret_cast<const char*>(u8"视频质量:"), "Video Quality:"};
|
||||
static std::vector<std::string> video_frame_rate = {
|
||||
reinterpret_cast<const char*>(u8"画面采集帧率:"),
|
||||
"Video Capture Frame Rate:"};
|
||||
static std::vector<std::string> video_quality_high = {
|
||||
reinterpret_cast<const char*>(u8"高"), "High"};
|
||||
static std::vector<std::string> video_quality_medium = {
|
||||
reinterpret_cast<const char*>(u8"中"), "Medium"};
|
||||
static std::vector<std::string> video_quality_low = {
|
||||
reinterpret_cast<const char*>(u8"低"), "Low"};
|
||||
static std::vector<std::string> video_encode_format = {
|
||||
reinterpret_cast<const char*>(u8"视频编码格式:"), "Video Encode Format:"};
|
||||
static std::vector<std::string> av1 = {reinterpret_cast<const char*>(u8"AV1"),
|
||||
"AV1"};
|
||||
static std::vector<std::string> h264 = {
|
||||
reinterpret_cast<const char*>(u8"H.264"), "H.264"};
|
||||
static std::vector<std::string> enable_hardware_video_codec = {
|
||||
reinterpret_cast<const char*>(u8"启用硬件编解码器:"),
|
||||
"Enable Hardware Video Codec:"};
|
||||
static std::vector<std::string> enable_turn = {
|
||||
reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"};
|
||||
static std::vector<std::string> enable_srtp = {
|
||||
reinterpret_cast<const char*>(u8"启用SRTP:"), "Enable SRTP:"};
|
||||
static std::vector<std::string> self_hosted_server_config = {
|
||||
reinterpret_cast<const char*>(u8"自托管服务器配置"),
|
||||
"Self-Hosted Server Config"};
|
||||
static std::vector<std::string> self_hosted_server_settings = {
|
||||
reinterpret_cast<const char*>(u8"自托管服务器设置"),
|
||||
"Self-Hosted Server Settings"};
|
||||
static std::vector<std::string> self_hosted_server_address = {
|
||||
reinterpret_cast<const char*>(u8"服务器地址:"), "Server Address:"};
|
||||
static std::vector<std::string> self_hosted_server_port = {
|
||||
reinterpret_cast<const char*>(u8"服务器端口:"), "Server Port:"};
|
||||
static std::vector<std::string> self_hosted_server_certificate_path = {
|
||||
reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"};
|
||||
static std::vector<std::string> select_a_file = {
|
||||
reinterpret_cast<const char*>(u8"请选择文件"), "Please select a file"};
|
||||
static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"),
|
||||
"OK"};
|
||||
static std::vector<std::string> cancel = {
|
||||
reinterpret_cast<const char*>(u8"取消"), "Cancel"};
|
||||
|
||||
static std::vector<std::string> new_password = {
|
||||
reinterpret_cast<const char*>(u8"请输入六位密码:"),
|
||||
"Please input a six-char password:"};
|
||||
|
||||
static std::vector<std::string> input_password = {
|
||||
reinterpret_cast<const char*>(u8"请输入密码:"), "Please input password:"};
|
||||
static std::vector<std::string> validate_password = {
|
||||
reinterpret_cast<const char*>(u8"验证密码中..."), "Validate password ..."};
|
||||
static std::vector<std::string> reinput_password = {
|
||||
reinterpret_cast<const char*>(u8"请重新输入密码"),
|
||||
"Please input password again"};
|
||||
|
||||
static std::vector<std::string> remember_password = {
|
||||
reinterpret_cast<const char*>(u8"记住密码"), "Remember password"};
|
||||
|
||||
static std::vector<std::string> signal_connected = {
|
||||
reinterpret_cast<const char*>(u8"已连接服务器"), "Connected"};
|
||||
static std::vector<std::string> signal_disconnected = {
|
||||
reinterpret_cast<const char*>(u8"未连接服务器"), "Disconnected"};
|
||||
|
||||
static std::vector<std::string> p2p_connected = {
|
||||
reinterpret_cast<const char*>(u8"对等连接已建立"), "P2P Connected"};
|
||||
static std::vector<std::string> p2p_disconnected = {
|
||||
reinterpret_cast<const char*>(u8"对等连接已断开"), "P2P Disconnected"};
|
||||
static std::vector<std::string> p2p_connecting = {
|
||||
reinterpret_cast<const char*>(u8"正在建立对等连接..."),
|
||||
"P2P Connecting ..."};
|
||||
static std::vector<std::string> p2p_failed = {
|
||||
reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"};
|
||||
static std::vector<std::string> p2p_closed = {
|
||||
reinterpret_cast<const char*>(u8"对等连接已关闭"), "P2P closed"};
|
||||
|
||||
static std::vector<std::string> no_such_id = {
|
||||
reinterpret_cast<const char*>(u8"无此ID"), "No such ID"};
|
||||
|
||||
static std::vector<std::string> about = {
|
||||
reinterpret_cast<const char*>(u8"关于"), "About"};
|
||||
static std::vector<std::string> version = {
|
||||
reinterpret_cast<const char*>(u8"版本"), "Version"};
|
||||
|
||||
static std::vector<std::string> confirm_delete_connection = {
|
||||
reinterpret_cast<const char*>(u8"确认删除此连接"),
|
||||
"Confirm to delete this connection"};
|
||||
#if _WIN32
|
||||
|
||||
static std::vector<std::string> minimize_to_tray = {
|
||||
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
|
||||
"Minimize to system tray when exit:"};
|
||||
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
|
||||
#endif
|
||||
} // namespace localization
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <random>
|
||||
|
||||
#include "layout_style.h"
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
@@ -74,7 +74,7 @@ int Render::LocalWindow() {
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0));
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
if (ImGui::Button(ICON_FA_COPY, ImVec2(35, 38))) {
|
||||
if (ImGui::Button(ICON_FA_COPY, ImVec2(22, 38))) {
|
||||
local_id_copied_ = true;
|
||||
ImGui::SetClipboardText(client_id_);
|
||||
copy_start_time_ = ImGui::GetTime();
|
||||
@@ -84,7 +84,7 @@ int Render::LocalWindow() {
|
||||
|
||||
double time_duration = ImGui::GetTime() - copy_start_time_;
|
||||
if (local_id_copied_ && time_duration < 1.0f) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
notification_window_width_) /
|
||||
@@ -135,28 +135,6 @@ int Render::LocalWindow() {
|
||||
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH);
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!password_inited_) {
|
||||
char a[] = {
|
||||
"123456789QWERTYUPASDFGHJKLZXCVBNMqwertyupasdfghijkzxcvbnm"};
|
||||
std::mt19937 generator((unsigned int)std::chrono::system_clock::now()
|
||||
.time_since_epoch()
|
||||
.count());
|
||||
std::uniform_int_distribution<int> distribution(0,
|
||||
(int)(strlen(a) - 1));
|
||||
|
||||
random_password_.clear();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
random_password_ += a[distribution(generator)];
|
||||
}
|
||||
password_inited_ = true;
|
||||
if (0 != strcmp(random_password_.c_str(), password_saved_)) {
|
||||
memcpy(password_saved_, random_password_.c_str(),
|
||||
sizeof(password_saved_));
|
||||
LOG_INFO("Generate new password and save into cache file");
|
||||
SaveSettingsIntoCacheFile();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
ImGui::InputTextWithHint(
|
||||
"##server_pwd",
|
||||
@@ -181,7 +159,7 @@ int Render::LocalWindow() {
|
||||
}
|
||||
|
||||
if (!show_password_) {
|
||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 12.5f),
|
||||
ImVec2(l_x + 20.3f, l_y + 26.5f),
|
||||
IM_COL32(239, 240, 242, 255), 2.0f);
|
||||
@@ -192,21 +170,6 @@ int Render::LocalWindow() {
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(
|
||||
regenerate_password_ ? ICON_FA_SPINNER : ICON_FA_ARROWS_ROTATE,
|
||||
ImVec2(22, 38))) {
|
||||
regenerate_password_ = true;
|
||||
password_inited_ = false;
|
||||
regenerate_password_start_time_ = ImGui::GetTime();
|
||||
LeaveConnection(peer_, client_id_);
|
||||
is_create_connection_ = false;
|
||||
}
|
||||
if (ImGui::GetTime() - regenerate_password_start_time_ > 0.3f) {
|
||||
regenerate_password_ = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_FA_PEN, ImVec2(22, 38))) {
|
||||
show_reset_password_window_ = true;
|
||||
}
|
||||
@@ -214,7 +177,7 @@ int Render::LocalWindow() {
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
if (show_reset_password_window_) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
@@ -281,12 +244,22 @@ int Render::LocalWindow() {
|
||||
focus_on_input_widget_ = true;
|
||||
} else {
|
||||
show_reset_password_window_ = false;
|
||||
LOG_INFO("Generate new password and save into cache file");
|
||||
memcpy(password_saved_, new_password_, sizeof(password_saved_));
|
||||
memset(new_password_, 0, sizeof(new_password_));
|
||||
memset(&password_saved_, 0, sizeof(password_saved_));
|
||||
strncpy(password_saved_, new_password_,
|
||||
sizeof(password_saved_) - 1);
|
||||
password_saved_[sizeof(password_saved_) - 1] = '\0';
|
||||
|
||||
std::string client_id_with_password =
|
||||
std::string(client_id_) + "@" + password_saved_;
|
||||
strncpy(client_id_with_password_, client_id_with_password.c_str(),
|
||||
sizeof(client_id_with_password_) - 1);
|
||||
client_id_with_password_[sizeof(client_id_with_password_) - 1] =
|
||||
'\0';
|
||||
|
||||
SaveSettingsIntoCacheFile();
|
||||
|
||||
LeaveConnection(peer_, client_id_);
|
||||
is_create_connection_ = false;
|
||||
DestroyPeer(&peer_);
|
||||
focus_on_input_widget_ = true;
|
||||
}
|
||||
}
|
||||
@@ -56,17 +56,15 @@ int Render::ShowRecentConnections() {
|
||||
ImGuiWindowFlags_NoScrollWithMouse);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
size_t recent_connections_count = recent_connection_textures_.size();
|
||||
size_t recent_connections_count = recent_connections_.size();
|
||||
int count = 0;
|
||||
float button_width = 22;
|
||||
float button_height = 22;
|
||||
for (auto it = recent_connection_textures_.begin();
|
||||
it != recent_connection_textures_.end(); ++it) {
|
||||
sub_containers_pos[it->first] = ImGui::GetCursorPos();
|
||||
for (auto& it : recent_connections_) {
|
||||
sub_containers_pos[it.first] = ImGui::GetCursorPos();
|
||||
std::string recent_connection_sub_window_name =
|
||||
"RecentConnectionsSubContainer" + it->first;
|
||||
"RecentConnectionsSubContainer" + it.first;
|
||||
// recent connections sub container
|
||||
|
||||
ImGui::BeginChild(recent_connection_sub_window_name.c_str(),
|
||||
ImVec2(recent_connection_sub_container_width,
|
||||
recent_connection_sub_container_height),
|
||||
@@ -76,24 +74,42 @@ int Render::ShowRecentConnections() {
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
std::string connection_info = it->first;
|
||||
std::string remote_id;
|
||||
std::string password;
|
||||
std::string host_name;
|
||||
std::string connection_info = it.first;
|
||||
|
||||
// remote id length is 9
|
||||
// password length is 6
|
||||
// connection_info -> remote_id + 'Y' + password + host_name
|
||||
// connection_info -> remote_id + 'Y' + host_name + '@' + password
|
||||
// -> remote_id + 'N' + host_name
|
||||
if ('Y' == connection_info[9] && connection_info.size() >= 16) {
|
||||
remote_id = connection_info.substr(0, 9);
|
||||
password = connection_info.substr(10, 6);
|
||||
host_name = connection_info.substr(16);
|
||||
size_t pos_y = connection_info.find('Y');
|
||||
size_t pos_at = connection_info.find('@');
|
||||
|
||||
if (pos_y == std::string::npos || pos_at == std::string::npos ||
|
||||
pos_y >= pos_at) {
|
||||
LOG_ERROR("Invalid filename");
|
||||
continue;
|
||||
}
|
||||
|
||||
it.second.remote_id = connection_info.substr(0, pos_y);
|
||||
it.second.remote_host_name =
|
||||
connection_info.substr(pos_y + 1, pos_at - pos_y - 1);
|
||||
it.second.password = connection_info.substr(pos_at + 1);
|
||||
it.second.remember_password = true;
|
||||
} else if ('N' == connection_info[9] && connection_info.size() >= 10) {
|
||||
remote_id = connection_info.substr(0, 9);
|
||||
host_name = connection_info.substr(10);
|
||||
size_t pos_n = connection_info.find('N');
|
||||
size_t pos_at = connection_info.find('@');
|
||||
|
||||
if (pos_n == std::string::npos) {
|
||||
LOG_ERROR("Invalid filename");
|
||||
continue;
|
||||
}
|
||||
|
||||
it.second.remote_id = connection_info.substr(0, pos_n);
|
||||
it.second.remote_host_name = connection_info.substr(pos_n + 1);
|
||||
it.second.password = "";
|
||||
it.second.remember_password = false;
|
||||
} else {
|
||||
host_name = "unknown";
|
||||
it.second.remote_host_name = "unknown";
|
||||
}
|
||||
|
||||
ImVec2 image_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x + 5.0f,
|
||||
@@ -101,7 +117,7 @@ int Render::ShowRecentConnections() {
|
||||
ImVec2 image_pos =
|
||||
ImVec2(ImGui::GetCursorPosX() + 5.0f, ImGui::GetCursorPosY() + 5.0f);
|
||||
ImGui::SetCursorPos(image_pos);
|
||||
ImGui::Image((ImTextureID)(intptr_t)it->second,
|
||||
ImGui::Image((ImTextureID)(intptr_t)it.second.texture,
|
||||
ImVec2((float)recent_connection_image_width_,
|
||||
(float)recent_connection_image_height_));
|
||||
|
||||
@@ -113,7 +129,7 @@ int Render::ShowRecentConnections() {
|
||||
|
||||
ImVec2 dummy_button_pos =
|
||||
ImVec2(image_pos.x, image_pos.y + recent_connection_image_height_);
|
||||
std::string dummy_button_name = "##DummyButton" + remote_id;
|
||||
std::string dummy_button_name = "##DummyButton" + it.second.remote_id;
|
||||
ImGui::SetCursorPos(dummy_button_pos);
|
||||
ImGui::SetWindowFontScale(0.6f);
|
||||
ImGui::Button(dummy_button_name.c_str(),
|
||||
@@ -123,14 +139,14 @@ int Render::ShowRecentConnections() {
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(dummy_button_pos.x + 2.0f, dummy_button_pos.y + 1.0f));
|
||||
ImGui::SetWindowFontScale(0.65f);
|
||||
ImGui::Text("%s", remote_id.c_str());
|
||||
ImGui::Text("%s", it.second.remote_id.c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::Text("%s", host_name.c_str());
|
||||
ImGui::Text("%s", it.second.remote_host_name.c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
@@ -155,10 +171,11 @@ int Render::ShowRecentConnections() {
|
||||
if (ImGui::Button(recent_connection_delete_button_name.c_str(),
|
||||
ImVec2(button_width, button_height))) {
|
||||
show_confirm_delete_connection_ = true;
|
||||
delete_connection_name_ = it.first;
|
||||
}
|
||||
|
||||
if (delete_connection_) {
|
||||
if (!thumbnail_->DeleteThumbnail(it->first)) {
|
||||
if (delete_connection_ && delete_connection_name_ == it.first) {
|
||||
if (!thumbnail_->DeleteThumbnail(it.first)) {
|
||||
reload_recent_connections_ = true;
|
||||
delete_connection_ = false;
|
||||
}
|
||||
@@ -173,15 +190,11 @@ int Render::ShowRecentConnections() {
|
||||
ImGui::SetCursorPos(connect_button_pos);
|
||||
std::string connect = ICON_FA_ARROW_RIGHT_LONG;
|
||||
std::string connect_to_this_connection_button_name =
|
||||
connect + "##ConnectionTo" + it->first;
|
||||
connect + "##ConnectionTo" + it.first;
|
||||
if (ImGui::Button(connect_to_this_connection_button_name.c_str(),
|
||||
ImVec2(button_width, button_height))) {
|
||||
remote_id_ = remote_id;
|
||||
if (!password.empty() && password.size() == 6) {
|
||||
remember_password_ = true;
|
||||
}
|
||||
memcpy(remote_password_, password.c_str(), 6);
|
||||
ConnectTo();
|
||||
ConnectTo(it.second.remote_id, it.second.password.c_str(),
|
||||
it.second.remember_password);
|
||||
}
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
@@ -196,7 +209,7 @@ int Render::ShowRecentConnections() {
|
||||
ImVec2 line_end = ImVec2(
|
||||
image_screen_pos.x + recent_connection_image_width_ + 20.0f,
|
||||
image_screen_pos.y + recent_connection_image_height_ + button_height);
|
||||
ImGui::GetForegroundDrawList()->AddLine(line_start, line_end,
|
||||
ImGui::GetWindowDrawList()->AddLine(line_start, line_end,
|
||||
IM_COL32(0, 0, 0, 122), 1.0f);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "layout_style.h"
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
@@ -68,18 +68,45 @@ int Render::RemoteWindow() {
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::SameLine();
|
||||
|
||||
std::string remote_id = remote_id_display_;
|
||||
remote_id.erase(remove_if(remote_id.begin(), remote_id.end(),
|
||||
static_cast<int (*)(int)>(&isspace)),
|
||||
remote_id.end());
|
||||
if (ImGui::Button(ICON_FA_ARROW_RIGHT_LONG, ImVec2(55, 38)) ||
|
||||
enter_pressed) {
|
||||
connect_button_pressed_ = true;
|
||||
remote_id_ = remote_id_display_;
|
||||
remote_id_.erase(remove_if(remote_id_.begin(), remote_id_.end(),
|
||||
static_cast<int (*)(int)>(&isspace)),
|
||||
remote_id_.end());
|
||||
ConnectTo();
|
||||
bool found = false;
|
||||
for (auto &[id, props] : recent_connections_) {
|
||||
if (id.find(remote_id) != std::string::npos) {
|
||||
found = true;
|
||||
if (client_properties_.find(remote_id) !=
|
||||
client_properties_.end()) {
|
||||
if (!client_properties_[remote_id]->connection_established_) {
|
||||
ConnectTo(props.remote_id, props.password.c_str(), false);
|
||||
} else {
|
||||
// todo: show warning message
|
||||
LOG_INFO("Already connected to [{}]", remote_id);
|
||||
}
|
||||
} else {
|
||||
ConnectTo(props.remote_id, props.password.c_str(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rejoin_) {
|
||||
ConnectTo();
|
||||
if (!found) {
|
||||
ConnectTo(remote_id, "", false);
|
||||
}
|
||||
}
|
||||
|
||||
if (need_to_rejoin_) {
|
||||
need_to_rejoin_ = false;
|
||||
for (const auto &[_, props] : client_properties_) {
|
||||
if (props->rejoin_) {
|
||||
ConnectTo(props->remote_id_, props->remote_password_,
|
||||
props->remember_password_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
@@ -102,32 +129,51 @@ static int InputTextCallback(ImGuiInputTextCallbackData *data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ConnectTo() {
|
||||
connection_status_ = ConnectionStatus::Connecting;
|
||||
int ret = -1;
|
||||
if (signal_connected_) {
|
||||
if (!connection_established_) {
|
||||
if (0 == strcmp(remote_id_.c_str(), client_id_) && !peer_reserved_) {
|
||||
peer_reserved_ = CreatePeer(¶ms_);
|
||||
if (peer_reserved_) {
|
||||
LOG_INFO("Create peer[reserved] instance successful");
|
||||
std::string client_id = "C-";
|
||||
client_id += client_id_;
|
||||
Init(peer_reserved_, client_id.c_str());
|
||||
LOG_INFO("Peer[reserved] init finish");
|
||||
int Render::ConnectTo(const std::string &remote_id, const char *password,
|
||||
bool remember_password) {
|
||||
LOG_INFO("Connect to [{}]", remote_id);
|
||||
focused_remote_id_ = remote_id;
|
||||
|
||||
if (client_properties_.find(remote_id) == client_properties_.end()) {
|
||||
client_properties_[remote_id] =
|
||||
std::make_shared<SubStreamWindowProperties>();
|
||||
auto props = client_properties_[remote_id];
|
||||
props->local_id_ = "C-" + std::string(client_id_);
|
||||
props->remote_id_ = remote_id;
|
||||
memcpy(&props->params_, ¶ms_, 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 (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[reserved] instance failed");
|
||||
}
|
||||
LOG_INFO("Create peer [{}] instance failed", props->local_id_);
|
||||
}
|
||||
|
||||
ret = JoinConnection(peer_reserved_ ? peer_reserved_ : peer_,
|
||||
remote_id_.c_str(), remote_password_);
|
||||
if (0 == ret) {
|
||||
is_client_mode_ = true;
|
||||
rejoin_ = false;
|
||||
} else {
|
||||
rejoin_ = true;
|
||||
props->connection_status_ = ConnectionStatus::Connecting;
|
||||
}
|
||||
int ret = -1;
|
||||
auto props = client_properties_[remote_id];
|
||||
if (!props->connection_established_) {
|
||||
props->remember_password_ = remember_password;
|
||||
if (strcmp(password, "") != 0 &&
|
||||
strcmp(password, props->remote_password_) != 0) {
|
||||
strncpy(props->remote_password_, password,
|
||||
sizeof(props->remote_password_) - 1);
|
||||
props->remote_password_[sizeof(props->remote_password_) - 1] = '\0';
|
||||
}
|
||||
|
||||
std::string remote_id_with_pwd = remote_id + "@" + password;
|
||||
ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str());
|
||||
if (0 == ret) {
|
||||
props->rejoin_ = false;
|
||||
} else {
|
||||
props->rejoin_ = true;
|
||||
need_to_rejoin_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
1415
src/gui/render.cpp
Normal file
470
src/gui/render.h
Normal file
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MAIN_WINDOW_H_
|
||||
#define _MAIN_WINDOW_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "IconsFontAwesome6.h"
|
||||
#include "config_center.h"
|
||||
#include "device_controller_factory.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
#include "imgui_impl_sdlrenderer3.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "minirtc.h"
|
||||
#include "path_manager.h"
|
||||
#include "screen_capturer_factory.h"
|
||||
#include "speaker_capturer_factory.h"
|
||||
#include "thumbnail.h"
|
||||
#if _WIN32
|
||||
#include "win_tray.h"
|
||||
#endif
|
||||
|
||||
class Render {
|
||||
public:
|
||||
struct SubStreamWindowProperties {
|
||||
Params params_;
|
||||
PeerPtr* peer_ = nullptr;
|
||||
std::string audio_label_ = "control_audio";
|
||||
std::string data_label_ = "control_data";
|
||||
std::string local_id_ = "";
|
||||
std::string remote_id_ = "";
|
||||
bool exit_ = false;
|
||||
bool signal_connected_ = false;
|
||||
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
||||
bool connection_established_ = false;
|
||||
bool rejoin_ = false;
|
||||
bool net_traffic_stats_button_pressed_ = false;
|
||||
bool mouse_control_button_pressed_ = false;
|
||||
bool mouse_controller_is_started_ = false;
|
||||
bool audio_capture_button_pressed_ = false;
|
||||
bool control_mouse_ = false;
|
||||
bool streaming_ = false;
|
||||
bool is_control_bar_in_left_ = true;
|
||||
bool control_bar_hovered_ = false;
|
||||
bool display_selectable_hovered_ = false;
|
||||
bool control_bar_expand_ = true;
|
||||
bool reset_control_bar_pos_ = false;
|
||||
bool control_window_width_is_changing_ = false;
|
||||
bool control_window_height_is_changing_ = false;
|
||||
bool p2p_mode_ = true;
|
||||
bool remember_password_ = false;
|
||||
char remote_password_[7] = "";
|
||||
float sub_stream_window_width_ = 1280;
|
||||
float sub_stream_window_height_ = 720;
|
||||
float control_window_min_width_ = 20;
|
||||
float control_window_max_width_ = 230;
|
||||
float control_window_min_height_ = 40;
|
||||
float control_window_max_height_ = 170;
|
||||
float control_window_width_ = 230;
|
||||
float control_window_height_ = 40;
|
||||
float control_bar_pos_x_ = 0;
|
||||
float control_bar_pos_y_ = 30;
|
||||
float mouse_diff_control_bar_pos_x_ = 0;
|
||||
float mouse_diff_control_bar_pos_y_ = 0;
|
||||
double control_bar_button_pressed_time_ = 0;
|
||||
double net_traffic_stats_button_pressed_time_ = 0;
|
||||
unsigned char* dst_buffer_ = nullptr;
|
||||
size_t dst_buffer_capacity_ = 0;
|
||||
float mouse_pos_x_ = 0;
|
||||
float mouse_pos_y_ = 0;
|
||||
float mouse_pos_x_last_ = 0;
|
||||
float mouse_pos_y_last_ = 0;
|
||||
int texture_width_ = 1280;
|
||||
int texture_height_ = 720;
|
||||
int video_width_ = 0;
|
||||
int video_height_ = 0;
|
||||
int video_width_last_ = 0;
|
||||
int video_height_last_ = 0;
|
||||
int selected_display_ = 0;
|
||||
size_t video_size_ = 0;
|
||||
bool tab_selected_ = false;
|
||||
bool tab_opened_ = true;
|
||||
std::optional<float> pos_x_before_docked_;
|
||||
std::optional<float> pos_y_before_docked_;
|
||||
float render_window_x_ = 0;
|
||||
float render_window_y_ = 0;
|
||||
float render_window_width_ = 0;
|
||||
float render_window_height_ = 0;
|
||||
std::string fullscreen_button_label_ = "Fullscreen";
|
||||
std::string net_traffic_stats_button_label_ = "Show Net Traffic Stats";
|
||||
std::string mouse_control_button_label_ = "Mouse Control";
|
||||
std::string audio_capture_button_label_ = "Audio Capture";
|
||||
std::string remote_host_name_ = "";
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
SDL_Texture* stream_texture_ = nullptr;
|
||||
uint8_t* argb_buffer_ = nullptr;
|
||||
int argb_buffer_size_ = 0;
|
||||
SDL_Rect stream_render_rect_;
|
||||
SDL_Rect stream_render_rect_last_;
|
||||
ImVec2 control_window_pos_;
|
||||
ConnectionStatus connection_status_ = ConnectionStatus::Closed;
|
||||
TraversalMode traversal_mode_ = TraversalMode::UnknownMode;
|
||||
int fps_ = 0;
|
||||
int frame_count_ = 0;
|
||||
std::chrono::steady_clock::time_point last_time_;
|
||||
XNetTrafficStats net_traffic_stats_;
|
||||
};
|
||||
|
||||
public:
|
||||
Render();
|
||||
~Render();
|
||||
|
||||
public:
|
||||
int Run();
|
||||
|
||||
private:
|
||||
void InitializeLogger();
|
||||
void InitializeSettings();
|
||||
void InitializeSDL();
|
||||
void InitializeModules();
|
||||
void InitializeMainWindow();
|
||||
void MainLoop();
|
||||
void UpdateLabels();
|
||||
void UpdateInteractions();
|
||||
void HandleRecentConnections();
|
||||
void HandleStreamWindow();
|
||||
void Cleanup();
|
||||
void CleanupFactories();
|
||||
void CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props);
|
||||
void CleanupPeers();
|
||||
void CleanSubStreamWindowProperties(
|
||||
std::shared_ptr<SubStreamWindowProperties> props);
|
||||
void UpdateRenderRect();
|
||||
void ProcessSdlEvent(const SDL_Event& event);
|
||||
|
||||
private:
|
||||
int CreateStreamRenderWindow();
|
||||
int TitleBar(bool main_window);
|
||||
int MainWindow();
|
||||
int StreamWindow();
|
||||
int LocalWindow();
|
||||
int RemoteWindow();
|
||||
int RecentConnectionsWindow();
|
||||
int SettingWindow();
|
||||
int SelfHostedServerWindow();
|
||||
int ShowSimpleFileBrowser();
|
||||
int ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
int ControlBar(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
int AboutWindow();
|
||||
int StatusBar();
|
||||
bool ConnectionStatusWindow(
|
||||
std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
int ShowRecentConnections();
|
||||
|
||||
private:
|
||||
int ConnectTo(const std::string& remote_id, const char* password,
|
||||
bool remember_password);
|
||||
int CreateMainWindow();
|
||||
int DestroyMainWindow();
|
||||
int CreateStreamWindow();
|
||||
int DestroyStreamWindow();
|
||||
int SetupFontAndStyle();
|
||||
int SetupMainWindow();
|
||||
int DestroyMainWindowContext();
|
||||
int SetupStreamWindow();
|
||||
int DestroyStreamWindowContext();
|
||||
int DrawMainWindow();
|
||||
int DrawStreamWindow();
|
||||
int ConfirmDeleteConnection();
|
||||
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
void DrawConnectionStatusText(
|
||||
std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
|
||||
public:
|
||||
static void OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static void OnReceiveAudioBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static void OnReceiveDataBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static void OnSignalStatusCb(SignalStatus status, const char* user_id,
|
||||
size_t user_id_size, void* user_data);
|
||||
|
||||
static void OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||
size_t user_id_size, void* user_data);
|
||||
|
||||
static void NetStatusReport(const char* client_id, size_t client_id_size,
|
||||
TraversalMode mode,
|
||||
const XNetTrafficStats* net_traffic_stats,
|
||||
const char* user_id, const size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static SDL_HitTestResult HitTestCallback(SDL_Window* window,
|
||||
const SDL_Point* area, void* data);
|
||||
|
||||
static std::vector<char> SerializeRemoteAction(const RemoteAction& action);
|
||||
|
||||
static bool DeserializeRemoteAction(const char* data, size_t size,
|
||||
RemoteAction& out);
|
||||
|
||||
static void FreeRemoteAction(RemoteAction& action);
|
||||
|
||||
private:
|
||||
int SendKeyCommand(int key_code, bool is_down);
|
||||
int ProcessMouseEvent(const SDL_Event& event);
|
||||
|
||||
static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len);
|
||||
static void SdlCaptureAudioOut(void* userdata, Uint8* stream, int len);
|
||||
|
||||
private:
|
||||
int SaveSettingsIntoCacheFile();
|
||||
int LoadSettingsFromCacheFile();
|
||||
|
||||
int ScreenCapturerInit();
|
||||
int StartScreenCapturer();
|
||||
int StopScreenCapturer();
|
||||
|
||||
int StartSpeakerCapturer();
|
||||
int StopSpeakerCapturer();
|
||||
|
||||
int StartMouseController();
|
||||
int StopMouseController();
|
||||
|
||||
int StartKeyboardCapturer();
|
||||
int StopKeyboardCapturer();
|
||||
|
||||
int CreateConnectionPeer();
|
||||
|
||||
int AudioDeviceInit();
|
||||
int AudioDeviceDestroy();
|
||||
|
||||
private:
|
||||
struct CDCache {
|
||||
char client_id_with_password[17];
|
||||
int language;
|
||||
int video_quality;
|
||||
int video_frame_rate;
|
||||
int video_encode_format;
|
||||
bool enable_hardware_video_codec;
|
||||
bool enable_turn;
|
||||
bool enable_srtp;
|
||||
|
||||
unsigned char key[16];
|
||||
unsigned char iv[16];
|
||||
};
|
||||
|
||||
private:
|
||||
CDCache cd_cache_;
|
||||
std::mutex cd_cache_mutex_;
|
||||
std::unique_ptr<ConfigCenter> config_center_;
|
||||
ConfigCenter::LANGUAGE localization_language_ =
|
||||
ConfigCenter::LANGUAGE::CHINESE;
|
||||
std::unique_ptr<PathManager> path_manager_;
|
||||
std::string cert_path_;
|
||||
std::string exec_log_path_;
|
||||
std::string dll_log_path_;
|
||||
std::string cache_path_;
|
||||
int localization_language_index_ = -1;
|
||||
int localization_language_index_last_ = -1;
|
||||
bool modules_inited_ = false;
|
||||
/* ------ all windows property start ------ */
|
||||
float title_bar_width_ = 640;
|
||||
float title_bar_height_ = 30;
|
||||
/* ------ all windows property end ------ */
|
||||
|
||||
/* ------ main window property start ------ */
|
||||
// thumbnail
|
||||
unsigned char aes128_key_[16];
|
||||
unsigned char aes128_iv_[16];
|
||||
std::unique_ptr<Thumbnail> thumbnail_;
|
||||
|
||||
// recent connections
|
||||
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>
|
||||
recent_connections_;
|
||||
int recent_connection_image_width_ = 160;
|
||||
int recent_connection_image_height_ = 90;
|
||||
uint32_t recent_connection_image_save_time_ = 0;
|
||||
|
||||
// main window render
|
||||
SDL_Window* main_window_ = nullptr;
|
||||
SDL_Renderer* main_renderer_ = nullptr;
|
||||
ImGuiContext* main_ctx_ = nullptr;
|
||||
bool exit_ = false;
|
||||
const int sdl_refresh_ms_ = 16; // ~60 FPS
|
||||
#if _WIN32
|
||||
std::unique_ptr<WinTray> tray_;
|
||||
#endif
|
||||
|
||||
// main window properties
|
||||
bool start_mouse_controller_ = false;
|
||||
bool mouse_controller_is_started_ = false;
|
||||
bool start_screen_capturer_ = false;
|
||||
bool screen_capturer_is_started_ = false;
|
||||
bool start_keyboard_capturer_ = false;
|
||||
bool keyboard_capturer_is_started_ = false;
|
||||
bool foucs_on_main_window_ = false;
|
||||
bool foucs_on_stream_window_ = false;
|
||||
bool audio_capture_ = false;
|
||||
int main_window_width_real_ = 720;
|
||||
int main_window_height_real_ = 540;
|
||||
float main_window_dpi_scaling_w_ = 1.0f;
|
||||
float main_window_dpi_scaling_h_ = 1.0f;
|
||||
float main_window_width_default_ = 640;
|
||||
float main_window_height_default_ = 480;
|
||||
float main_window_width_ = 640;
|
||||
float main_window_height_ = 480;
|
||||
float main_window_width_last_ = 640;
|
||||
float main_window_height_last_ = 480;
|
||||
float local_window_width_ = 320;
|
||||
float local_window_height_ = 235;
|
||||
float remote_window_width_ = 320;
|
||||
float remote_window_height_ = 235;
|
||||
float local_child_window_width_ = 266;
|
||||
float local_child_window_height_ = 180;
|
||||
float remote_child_window_width_ = 266;
|
||||
float remote_child_window_height_ = 180;
|
||||
float main_window_text_y_padding_ = 10;
|
||||
float main_child_window_x_padding_ = 27;
|
||||
float main_child_window_y_padding_ = 45;
|
||||
float status_bar_height_ = 22;
|
||||
float connection_status_window_width_ = 200;
|
||||
float connection_status_window_height_ = 150;
|
||||
float notification_window_width_ = 200;
|
||||
float notification_window_height_ = 80;
|
||||
float about_window_width_ = 300;
|
||||
float about_window_height_ = 170;
|
||||
int screen_width_ = 1280;
|
||||
int screen_height_ = 720;
|
||||
int selected_display_ = 0;
|
||||
std::string connect_button_label_ = "Connect";
|
||||
char input_password_tmp_[7] = "";
|
||||
char input_password_[7] = "";
|
||||
std::string random_password_ = "";
|
||||
char new_password_[7] = "";
|
||||
char remote_id_display_[12] = "";
|
||||
unsigned char audio_buffer_[720];
|
||||
int audio_len_ = 0;
|
||||
bool audio_buffer_fresh_ = false;
|
||||
bool need_to_rejoin_ = false;
|
||||
bool just_created_ = false;
|
||||
std::string controlled_remote_id_ = "";
|
||||
std::string focused_remote_id_ = "";
|
||||
bool need_to_send_host_info_ = false;
|
||||
SDL_Event last_mouse_event;
|
||||
SDL_AudioStream* output_stream_;
|
||||
uint32_t STREAM_REFRESH_EVENT = 0;
|
||||
|
||||
// stream window render
|
||||
SDL_Window* stream_window_ = nullptr;
|
||||
SDL_Renderer* stream_renderer_ = nullptr;
|
||||
ImGuiContext* stream_ctx_ = nullptr;
|
||||
|
||||
// stream window properties
|
||||
bool need_to_create_stream_window_ = false;
|
||||
bool stream_window_created_ = false;
|
||||
bool stream_window_inited_ = false;
|
||||
bool window_maximized_ = false;
|
||||
bool stream_window_grabbed_ = false;
|
||||
bool control_mouse_ = false;
|
||||
int stream_window_width_default_ = 1280;
|
||||
int stream_window_height_default_ = 720;
|
||||
float stream_window_width_ = 1280;
|
||||
float stream_window_height_ = 720;
|
||||
SDL_PixelFormat stream_pixformat_ = SDL_PIXELFORMAT_NV12;
|
||||
int stream_window_width_real_ = 1280;
|
||||
int stream_window_height_real_ = 720;
|
||||
float stream_window_dpi_scaling_w_ = 1.0f;
|
||||
float stream_window_dpi_scaling_h_ = 1.0f;
|
||||
|
||||
bool label_inited_ = false;
|
||||
bool connect_button_pressed_ = false;
|
||||
bool password_validating_ = false;
|
||||
uint32_t password_validating_time_ = 0;
|
||||
bool show_settings_window_ = false;
|
||||
bool show_self_hosted_server_config_window_ = false;
|
||||
bool rejoin_ = false;
|
||||
bool local_id_copied_ = false;
|
||||
bool show_password_ = true;
|
||||
bool show_about_window_ = false;
|
||||
bool show_connection_status_window_ = false;
|
||||
bool show_reset_password_window_ = false;
|
||||
bool fullscreen_button_pressed_ = false;
|
||||
bool focus_on_input_widget_ = true;
|
||||
bool is_client_mode_ = false;
|
||||
bool reload_recent_connections_ = true;
|
||||
bool show_confirm_delete_connection_ = false;
|
||||
bool delete_connection_ = false;
|
||||
bool is_tab_bar_hovered_ = false;
|
||||
std::string delete_connection_name_ = "";
|
||||
bool re_enter_remote_id_ = false;
|
||||
double copy_start_time_ = 0;
|
||||
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
||||
std::string signal_status_str_ = "";
|
||||
bool signal_connected_ = false;
|
||||
PeerPtr* peer_ = nullptr;
|
||||
PeerPtr* peer_reserved_ = nullptr;
|
||||
std::string video_primary_label_ = "primary_display";
|
||||
std::string video_secondary_label_ = "secondary_display";
|
||||
std::string audio_label_ = "audio";
|
||||
std::string data_label_ = "data";
|
||||
Params params_;
|
||||
SDL_AudioDeviceID input_dev_;
|
||||
SDL_AudioDeviceID output_dev_;
|
||||
ScreenCapturerFactory* screen_capturer_factory_ = nullptr;
|
||||
ScreenCapturer* screen_capturer_ = nullptr;
|
||||
SpeakerCapturerFactory* speaker_capturer_factory_ = nullptr;
|
||||
SpeakerCapturer* speaker_capturer_ = nullptr;
|
||||
DeviceControllerFactory* device_controller_factory_ = nullptr;
|
||||
MouseController* mouse_controller_ = nullptr;
|
||||
KeyboardCapturer* keyboard_capturer_ = nullptr;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
uint64_t last_frame_time_;
|
||||
char client_id_[10] = "";
|
||||
char client_id_display_[12] = "";
|
||||
char client_id_with_password_[17] = "";
|
||||
char password_saved_[7] = "";
|
||||
int language_button_value_ = 0;
|
||||
int video_quality_button_value_ = 0;
|
||||
int video_frame_rate_button_value_ = 0;
|
||||
int video_encode_format_button_value_ = 0;
|
||||
bool enable_hardware_video_codec_ = false;
|
||||
bool enable_turn_ = false;
|
||||
bool enable_srtp_ = false;
|
||||
char signal_server_ip_[256] = "api.crossdesk.cn";
|
||||
char signal_server_port_[6] = "9099";
|
||||
char cert_file_path_[256] = "";
|
||||
bool enable_self_hosted_server_ = false;
|
||||
int language_button_value_last_ = 0;
|
||||
int video_quality_button_value_last_ = 0;
|
||||
int video_encode_format_button_value_last_ = 0;
|
||||
bool enable_hardware_video_codec_last_ = false;
|
||||
bool enable_turn_last_ = false;
|
||||
bool enable_srtp_last_ = false;
|
||||
bool enable_minimize_to_tray_ = false;
|
||||
bool enable_minimize_to_tray_last_ = false;
|
||||
char signal_server_ip_tmp_[256] = "api.crossdesk.cn";
|
||||
char signal_server_port_tmp_[6] = "9099";
|
||||
bool settings_window_pos_reset_ = true;
|
||||
bool self_hosted_server_config_window_pos_reset_ = true;
|
||||
std::string selected_current_file_path_ = "";
|
||||
std::string selected_file_ = "";
|
||||
/* ------ main window property end ------ */
|
||||
|
||||
/* ------ sub stream window property start ------ */
|
||||
std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>>
|
||||
client_properties_;
|
||||
void CloseTab(decltype(client_properties_)::iterator& it);
|
||||
/* ------ stream window property end ------ */
|
||||
};
|
||||
|
||||
#endif
|
||||
545
src/gui/render_callback.cpp
Normal file
@@ -0,0 +1,545 @@
|
||||
#include "device_controller.h"
|
||||
#include "localization.h"
|
||||
#include "platform.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
|
||||
#ifdef DESK_PORT_DEBUG
|
||||
#else
|
||||
#define MOUSE_CONTROL 1
|
||||
#endif
|
||||
|
||||
int Render::SendKeyCommand(int key_code, bool is_down) {
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::keyboard;
|
||||
if (is_down) {
|
||||
remote_action.k.flag = KeyFlag::key_down;
|
||||
} else {
|
||||
remote_action.k.flag = KeyFlag::key_up;
|
||||
}
|
||||
remote_action.k.key_value = key_code;
|
||||
|
||||
if (!controlled_remote_id_.empty()) {
|
||||
if (client_properties_.find(controlled_remote_id_) !=
|
||||
client_properties_.end()) {
|
||||
auto props = client_properties_[controlled_remote_id_];
|
||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ProcessMouseEvent(const SDL_Event& event) {
|
||||
controlled_remote_id_ = "";
|
||||
int video_width, video_height = 0;
|
||||
int render_width, render_height = 0;
|
||||
float ratio_x, ratio_y = 0;
|
||||
RemoteAction remote_action;
|
||||
|
||||
for (auto& it : client_properties_) {
|
||||
auto props = it.second;
|
||||
if (!props->control_mouse_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.button.x >= props->stream_render_rect_.x &&
|
||||
event.button.x <=
|
||||
props->stream_render_rect_.x + props->stream_render_rect_.w &&
|
||||
event.button.y >= props->stream_render_rect_.y &&
|
||||
event.button.y <=
|
||||
props->stream_render_rect_.y + props->stream_render_rect_.h) {
|
||||
controlled_remote_id_ = it.first;
|
||||
render_width = props->stream_render_rect_.w;
|
||||
render_height = props->stream_render_rect_.h;
|
||||
last_mouse_event.button.x = event.button.x;
|
||||
last_mouse_event.button.y = event.button.y;
|
||||
|
||||
remote_action.m.x =
|
||||
(float)(event.button.x - props->stream_render_rect_.x) / render_width;
|
||||
remote_action.m.y =
|
||||
(float)(event.button.y - props->stream_render_rect_.y) /
|
||||
render_height;
|
||||
|
||||
if (SDL_EVENT_MOUSE_BUTTON_DOWN == event.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (SDL_BUTTON_LEFT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::left_down;
|
||||
} else if (SDL_BUTTON_RIGHT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::right_down;
|
||||
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::middle_down;
|
||||
}
|
||||
} else if (SDL_EVENT_MOUSE_BUTTON_UP == event.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (SDL_BUTTON_LEFT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::left_up;
|
||||
} else if (SDL_BUTTON_RIGHT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::right_up;
|
||||
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::middle_up;
|
||||
}
|
||||
} else if (SDL_EVENT_MOUSE_MOTION == event.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
remote_action.m.flag = MouseFlag::move;
|
||||
}
|
||||
|
||||
if (props->control_bar_hovered_ || props->display_selectable_hovered_) {
|
||||
remote_action.m.flag = MouseFlag::move;
|
||||
}
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
} 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 +
|
||||
props->stream_render_rect_.w &&
|
||||
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) {
|
||||
int scroll_x = event.wheel.x;
|
||||
int scroll_y = event.wheel.y;
|
||||
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
|
||||
scroll_x = -scroll_x;
|
||||
scroll_y = -scroll_y;
|
||||
}
|
||||
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (scroll_x == 0) {
|
||||
remote_action.m.flag = MouseFlag::wheel_vertical;
|
||||
remote_action.m.s = scroll_y;
|
||||
} else if (scroll_y == 0) {
|
||||
remote_action.m.flag = MouseFlag::wheel_horizontal;
|
||||
remote_action.m.s = scroll_x;
|
||||
}
|
||||
|
||||
render_width = props->stream_render_rect_.w;
|
||||
render_height = props->stream_render_rect_.h;
|
||||
remote_action.m.x =
|
||||
(float)(event.button.x - props->stream_render_rect_.x) / render_width;
|
||||
remote_action.m.y =
|
||||
(float)(event.button.y - props->stream_render_rect_.y) /
|
||||
render_height;
|
||||
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Render::SdlCaptureAudioIn(void* userdata, Uint8* stream, int len) {
|
||||
Render* render = (Render*)userdata;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (1) {
|
||||
for (auto it : render->client_properties_) {
|
||||
auto props = it.second;
|
||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
||||
SendAudioFrame(props->peer_, (const char*)stream, len,
|
||||
render->audio_label_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
memcpy(render->audio_buffer_, stream, len);
|
||||
render->audio_len_ = len;
|
||||
SDL_Delay(10);
|
||||
render->audio_buffer_fresh_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Render::SdlCaptureAudioOut([[maybe_unused]] void* userdata,
|
||||
[[maybe_unused]] Uint8* stream,
|
||||
[[maybe_unused]] int len) {
|
||||
// Render *render = (Render *)userdata;
|
||||
// for (auto it : render->client_properties_) {
|
||||
// auto props = it.second;
|
||||
// if (props->connection_status_ == SignalStatus::SignalConnected) {
|
||||
// SendAudioFrame(props->peer_, (const char *)stream, len);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!render->audio_buffer_fresh_) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// SDL_memset(stream, 0, len);
|
||||
|
||||
// if (render->audio_len_ == 0) {
|
||||
// return;
|
||||
// } else {
|
||||
// }
|
||||
|
||||
// len = (len > render->audio_len_ ? render->audio_len_ : len);
|
||||
// SDL_MixAudioFormat(stream, render->audio_buffer_, AUDIO_S16LSB, len,
|
||||
// SDL_MIX_MAXVOLUME);
|
||||
// render->audio_buffer_fresh_ = false;
|
||||
}
|
||||
|
||||
void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
if (render->client_properties_.find(remote_id) ==
|
||||
render->client_properties_.end()) {
|
||||
return;
|
||||
}
|
||||
SubStreamWindowProperties* props =
|
||||
render->client_properties_.find(remote_id)->second.get();
|
||||
|
||||
if (props->connection_established_) {
|
||||
if (!props->dst_buffer_) {
|
||||
props->dst_buffer_capacity_ = video_frame->size;
|
||||
props->dst_buffer_ = new unsigned char[video_frame->size];
|
||||
}
|
||||
|
||||
if (props->dst_buffer_capacity_ < video_frame->size) {
|
||||
delete props->dst_buffer_;
|
||||
props->dst_buffer_capacity_ = video_frame->size;
|
||||
props->dst_buffer_ = new unsigned char[video_frame->size];
|
||||
}
|
||||
|
||||
memcpy(props->dst_buffer_, video_frame->data, video_frame->size);
|
||||
bool need_to_update_render_rect = false;
|
||||
if (props->video_width_ != props->video_width_last_ ||
|
||||
props->video_height_ != props->video_height_last_) {
|
||||
need_to_update_render_rect = true;
|
||||
props->video_width_last_ = props->video_width_;
|
||||
props->video_height_last_ = props->video_height_;
|
||||
}
|
||||
props->video_width_ = video_frame->width;
|
||||
props->video_height_ = video_frame->height;
|
||||
props->video_size_ = video_frame->size;
|
||||
|
||||
if (need_to_update_render_rect) {
|
||||
render->UpdateRenderRect();
|
||||
}
|
||||
|
||||
SDL_Event event;
|
||||
event.type = render->STREAM_REFRESH_EVENT;
|
||||
event.user.data1 = props;
|
||||
SDL_PushEvent(&event);
|
||||
props->streaming_ = true;
|
||||
|
||||
if (props->net_traffic_stats_button_pressed_) {
|
||||
props->frame_count_++;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - props->last_time_)
|
||||
.count();
|
||||
|
||||
if (elapsed >= 1000) {
|
||||
props->fps_ = props->frame_count_ * 1000 / elapsed;
|
||||
props->frame_count_ = 0;
|
||||
props->last_time_ = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnReceiveAudioBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
render->audio_buffer_fresh_ = true;
|
||||
|
||||
if (render->output_stream_) {
|
||||
int pushed = SDL_PutAudioStreamData(
|
||||
render->output_stream_, (const Uint8*)data, static_cast<int>(size));
|
||||
if (pushed < 0) {
|
||||
LOG_ERROR("Failed to push audio data: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteAction remote_action;
|
||||
memcpy(&remote_action, data, size);
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
if (render->client_properties_.find(remote_id) !=
|
||||
render->client_properties_.end()) {
|
||||
// local
|
||||
auto props = render->client_properties_.find(remote_id)->second;
|
||||
RemoteAction host_info;
|
||||
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,
|
||||
remote_action.i.host_name_size);
|
||||
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
|
||||
LOG_ERROR("No remote display detected");
|
||||
}
|
||||
FreeRemoteAction(host_info);
|
||||
} else {
|
||||
// remote
|
||||
if (ControlType::mouse == remote_action.type && render->mouse_controller_) {
|
||||
render->mouse_controller_->SendMouseCommand(remote_action,
|
||||
render->selected_display_);
|
||||
} else if (ControlType::audio_capture == remote_action.type) {
|
||||
if (remote_action.a) {
|
||||
render->StartSpeakerCapturer();
|
||||
render->audio_capture_ = true;
|
||||
} else {
|
||||
render->StopSpeakerCapturer();
|
||||
render->audio_capture_ = false;
|
||||
}
|
||||
} else if (ControlType::keyboard == remote_action.type &&
|
||||
render->keyboard_capturer_) {
|
||||
render->keyboard_capturer_->SendKeyboardCommand(
|
||||
(int)remote_action.k.key_value,
|
||||
remote_action.k.flag == KeyFlag::key_down);
|
||||
} else if (ControlType::display_id == remote_action.type) {
|
||||
if (render->screen_capturer_) {
|
||||
render->selected_display_ = remote_action.d;
|
||||
render->screen_capturer_->SwitchTo(remote_action.d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
|
||||
size_t user_id_size, void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string client_id(user_id, user_id_size);
|
||||
if (client_id == render->client_id_) {
|
||||
render->signal_status_ = status;
|
||||
if (SignalStatus::SignalConnecting == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalConnected == status) {
|
||||
render->signal_connected_ = true;
|
||||
LOG_INFO("[{}] connected to signal server", client_id);
|
||||
} else if (SignalStatus::SignalFailed == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalClosed == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalReconnecting == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalServerClosed == status) {
|
||||
render->signal_connected_ = false;
|
||||
}
|
||||
} else {
|
||||
if (client_id.rfind("C-", 0) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string remote_id(client_id.begin() + 2, client_id.end());
|
||||
if (render->client_properties_.find(remote_id) ==
|
||||
render->client_properties_.end()) {
|
||||
return;
|
||||
}
|
||||
auto props = render->client_properties_.find(remote_id)->second;
|
||||
props->signal_status_ = status;
|
||||
if (SignalStatus::SignalConnecting == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalConnected == status) {
|
||||
props->signal_connected_ = true;
|
||||
LOG_INFO("[{}] connected to signal server", remote_id);
|
||||
} else if (SignalStatus::SignalFailed == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalClosed == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalReconnecting == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalServerClosed == status) {
|
||||
props->signal_connected_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||
const size_t user_id_size, void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) return;
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
auto it = render->client_properties_.find(remote_id);
|
||||
auto props = (it != render->client_properties_.end()) ? it->second : nullptr;
|
||||
|
||||
if (props) {
|
||||
render->is_client_mode_ = true;
|
||||
render->show_connection_status_window_ = true;
|
||||
props->connection_status_ = status;
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatus::Connected:
|
||||
if (!render->need_to_create_stream_window_ &&
|
||||
!render->client_properties_.empty()) {
|
||||
render->need_to_create_stream_window_ = true;
|
||||
}
|
||||
props->connection_established_ = true;
|
||||
props->stream_render_rect_ = {
|
||||
0, (int)render->title_bar_height_,
|
||||
(int)render->stream_window_width_,
|
||||
(int)(render->stream_window_height_ - render->title_bar_height_)};
|
||||
break;
|
||||
case ConnectionStatus::Disconnected:
|
||||
case ConnectionStatus::Failed:
|
||||
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->mouse_control_button_pressed_ = false;
|
||||
if (props->dst_buffer_) {
|
||||
memset(props->dst_buffer_, 0, props->dst_buffer_capacity_);
|
||||
SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_,
|
||||
props->texture_width_);
|
||||
}
|
||||
render->CleanSubStreamWindowProperties(props);
|
||||
break;
|
||||
case ConnectionStatus::IncorrectPassword:
|
||||
render->password_validating_ = false;
|
||||
render->password_validating_time_++;
|
||||
if (render->connect_button_pressed_) {
|
||||
render->connect_button_pressed_ = false;
|
||||
props->connection_established_ = false;
|
||||
render->connect_button_label_ =
|
||||
localization::connect[render->localization_language_index_];
|
||||
}
|
||||
break;
|
||||
case ConnectionStatus::NoSuchTransmissionId:
|
||||
if (render->connect_button_pressed_) {
|
||||
props->connection_established_ = false;
|
||||
render->connect_button_label_ =
|
||||
localization::connect[render->localization_language_index_];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
render->is_client_mode_ = false;
|
||||
render->show_connection_status_window_ = true;
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatus::Connected:
|
||||
render->need_to_send_host_info_ = true;
|
||||
render->start_screen_capturer_ = true;
|
||||
render->start_mouse_controller_ = true;
|
||||
break;
|
||||
case ConnectionStatus::Closed:
|
||||
render->start_screen_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;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::NetStatusReport(const char* client_id, size_t client_id_size,
|
||||
TraversalMode mode,
|
||||
const XNetTrafficStats* net_traffic_stats,
|
||||
const char* user_id, const size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) {
|
||||
std::string id, password;
|
||||
const char* at_pos = strchr(client_id, '@');
|
||||
if (at_pos == nullptr) {
|
||||
id = client_id;
|
||||
password.clear();
|
||||
} else {
|
||||
id.assign(client_id, at_pos - client_id);
|
||||
password = at_pos + 1;
|
||||
}
|
||||
|
||||
memset(&render->client_id_, 0, sizeof(render->client_id_));
|
||||
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
|
||||
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
|
||||
|
||||
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
|
||||
strncpy(render->password_saved_, password.c_str(),
|
||||
sizeof(render->password_saved_) - 1);
|
||||
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
|
||||
|
||||
memset(&render->client_id_with_password_, 0,
|
||||
sizeof(render->client_id_with_password_));
|
||||
strncpy(render->client_id_with_password_, client_id,
|
||||
sizeof(render->client_id_with_password_) - 1);
|
||||
render->client_id_with_password_[sizeof(render->client_id_with_password_) -
|
||||
1] = '\0';
|
||||
|
||||
LOG_INFO("Use client id [{}] and save id into cache file", id);
|
||||
render->SaveSettingsIntoCacheFile();
|
||||
}
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
if (render->client_properties_.find(remote_id) ==
|
||||
render->client_properties_.end()) {
|
||||
return;
|
||||
}
|
||||
auto props = render->client_properties_.find(remote_id)->second;
|
||||
if (props->traversal_mode_ != mode) {
|
||||
props->traversal_mode_ = mode;
|
||||
LOG_INFO("Net mode: [{}]", int(props->traversal_mode_));
|
||||
}
|
||||
|
||||
if (!net_traffic_stats) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only display client side net status if connected to itself
|
||||
if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) {
|
||||
props->net_traffic_stats_ = *net_traffic_stats;
|
||||
}
|
||||
}
|
||||
326
src/gui/toolbars/control_bar.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int CountDigits(int number) {
|
||||
if (number == 0) return 1;
|
||||
return (int)std::floor(std::log10(std::abs(number))) + 1;
|
||||
}
|
||||
|
||||
int BitrateDisplay(int bitrate) {
|
||||
int num_of_digits = CountDigits(bitrate);
|
||||
if (num_of_digits <= 3) {
|
||||
ImGui::Text("%d bps", bitrate);
|
||||
} else if (num_of_digits > 3 && num_of_digits <= 6) {
|
||||
ImGui::Text("%d kbps", bitrate / 1000);
|
||||
} else {
|
||||
ImGui::Text("%.1f mbps", bitrate / 1000000.0f);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LossRateDisplay(float loss_rate) {
|
||||
if (loss_rate < 0.01f) {
|
||||
ImGui::Text("0%%");
|
||||
} else {
|
||||
ImGui::Text("%.0f%%", loss_rate * 100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
if (props->control_bar_expand_) {
|
||||
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
||||
? (props->control_window_width_ + 5.0f)
|
||||
: 38.0f);
|
||||
// mouse control button
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
if (props->is_control_bar_in_left_) {
|
||||
draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f),
|
||||
ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f +
|
||||
props->control_window_height_),
|
||||
IM_COL32(178, 178, 178, 255), 1.0f);
|
||||
}
|
||||
|
||||
std::string display = ICON_FA_DISPLAY;
|
||||
if (ImGui::Button(display.c_str(), ImVec2(25, 25))) {
|
||||
ImGui::OpenPopup("display");
|
||||
}
|
||||
|
||||
ImVec2 btn_min = ImGui::GetItemRectMin();
|
||||
ImVec2 btn_size_actual = ImGui::GetItemRectSize();
|
||||
|
||||
if (ImGui::BeginPopup("display")) {
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
for (int i = 0; i < props->display_info_list_.size(); i++) {
|
||||
if (ImGui::Selectable(props->display_info_list_[i].name.c_str())) {
|
||||
props->selected_display_ = i;
|
||||
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::display_id;
|
||||
remote_action.d = i;
|
||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
props->display_selectable_hovered_ = ImGui::IsWindowHovered();
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(0.6f);
|
||||
ImVec2 text_size = ImGui::CalcTextSize(
|
||||
std::to_string(props->selected_display_ + 1).c_str());
|
||||
ImVec2 text_pos =
|
||||
ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.5f,
|
||||
btn_min.y + (btn_size_actual.y - text_size.y) * 0.5f - 2.0f);
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
text_pos, IM_COL32(0, 0, 0, 255),
|
||||
std::to_string(props->selected_display_ + 1).c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::SameLine();
|
||||
float disable_mouse_x = ImGui::GetCursorScreenPos().x + 4.0f;
|
||||
float disable_mouse_y = ImGui::GetCursorScreenPos().y + 4.0f;
|
||||
std::string mouse = props->mouse_control_button_pressed_
|
||||
? ICON_FA_COMPUTER_MOUSE
|
||||
: ICON_FA_COMPUTER_MOUSE;
|
||||
if (ImGui::Button(mouse.c_str(), ImVec2(25, 25))) {
|
||||
if (props->connection_established_) {
|
||||
start_keyboard_capturer_ = !start_keyboard_capturer_;
|
||||
props->control_mouse_ = !props->control_mouse_;
|
||||
props->mouse_control_button_pressed_ =
|
||||
!props->mouse_control_button_pressed_;
|
||||
props->mouse_control_button_label_ =
|
||||
props->mouse_control_button_pressed_
|
||||
? localization::release_mouse[localization_language_index_]
|
||||
: localization::control_mouse[localization_language_index_];
|
||||
}
|
||||
}
|
||||
if (!props->mouse_control_button_pressed_) {
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_mouse_x, disable_mouse_y),
|
||||
ImVec2(disable_mouse_x + 16.0f, disable_mouse_y + 14.2f),
|
||||
IM_COL32(0, 0, 0, 255), 2.0f);
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_mouse_x - 1.2f, disable_mouse_y + 1.2f),
|
||||
ImVec2(disable_mouse_x + 15.3f, disable_mouse_y + 15.4f),
|
||||
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255)
|
||||
: IM_COL32(179, 213, 253, 255),
|
||||
2.0f);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// audio capture button
|
||||
float disable_audio_x = ImGui::GetCursorScreenPos().x + 4;
|
||||
float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f;
|
||||
// std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH
|
||||
// :
|
||||
// ICON_FA_VOLUME_XMARK;
|
||||
std::string audio = props->audio_capture_button_pressed_
|
||||
? ICON_FA_VOLUME_HIGH
|
||||
: ICON_FA_VOLUME_HIGH;
|
||||
if (ImGui::Button(audio.c_str(), ImVec2(25, 25))) {
|
||||
if (props->connection_established_) {
|
||||
props->audio_capture_button_pressed_ =
|
||||
!props->audio_capture_button_pressed_;
|
||||
props->audio_capture_button_label_ =
|
||||
props->audio_capture_button_pressed_
|
||||
? localization::audio_capture[localization_language_index_]
|
||||
: localization::mute[localization_language_index_];
|
||||
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::audio_capture;
|
||||
remote_action.a = props->audio_capture_button_pressed_;
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
if (!props->audio_capture_button_pressed_) {
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_audio_x, disable_audio_y),
|
||||
ImVec2(disable_audio_x + 16.0f, disable_audio_y + 14.2f),
|
||||
IM_COL32(0, 0, 0, 255), 2.0f);
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_audio_x - 1.2f, disable_audio_y + 1.2f),
|
||||
ImVec2(disable_audio_x + 15.3f, disable_audio_y + 15.4f),
|
||||
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255)
|
||||
: IM_COL32(179, 213, 253, 255),
|
||||
2.0f);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// net traffic stats button
|
||||
bool button_color_style_pushed = false;
|
||||
if (props->net_traffic_stats_button_pressed_) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(66 / 255.0f, 150 / 255.0f,
|
||||
250 / 255.0f, 1.0f));
|
||||
button_color_style_pushed = true;
|
||||
}
|
||||
std::string net_traffic_stats = ICON_FA_SIGNAL;
|
||||
if (ImGui::Button(net_traffic_stats.c_str(), ImVec2(25, 25))) {
|
||||
props->net_traffic_stats_button_pressed_ =
|
||||
!props->net_traffic_stats_button_pressed_;
|
||||
props->control_window_height_is_changing_ = true;
|
||||
props->net_traffic_stats_button_pressed_time_ = ImGui::GetTime();
|
||||
props->net_traffic_stats_button_label_ =
|
||||
props->net_traffic_stats_button_pressed_
|
||||
? localization::hide_net_traffic_stats
|
||||
[localization_language_index_]
|
||||
: localization::show_net_traffic_stats
|
||||
[localization_language_index_];
|
||||
}
|
||||
if (button_color_style_pushed) {
|
||||
ImGui::PopStyleColor();
|
||||
button_color_style_pushed = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// fullscreen button
|
||||
std::string fullscreen =
|
||||
fullscreen_button_pressed_ ? ICON_FA_COMPRESS : ICON_FA_EXPAND;
|
||||
if (ImGui::Button(fullscreen.c_str(), ImVec2(25, 25))) {
|
||||
fullscreen_button_pressed_ = !fullscreen_button_pressed_;
|
||||
props->fullscreen_button_label_ =
|
||||
fullscreen_button_pressed_
|
||||
? localization::exit_fullscreen[localization_language_index_]
|
||||
: localization::fullscreen[localization_language_index_];
|
||||
|
||||
if (fullscreen_button_pressed_) {
|
||||
SDL_SetWindowFullscreen(stream_window_, true);
|
||||
} else {
|
||||
SDL_SetWindowFullscreen(stream_window_, false);
|
||||
}
|
||||
props->reset_control_bar_pos_ = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// close button
|
||||
std::string close_button = ICON_FA_XMARK;
|
||||
if (ImGui::Button(close_button.c_str(), ImVec2(25, 25))) {
|
||||
CleanupPeer(props);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (!props->is_control_bar_in_left_) {
|
||||
draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f),
|
||||
ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f +
|
||||
props->control_window_height_),
|
||||
IM_COL32(178, 178, 178, 255), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
||||
? (props->control_window_width_ * 2 - 20.0f)
|
||||
: 5.0f);
|
||||
|
||||
std::string control_bar =
|
||||
props->control_bar_expand_
|
||||
? (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_LEFT
|
||||
: ICON_FA_ANGLE_RIGHT)
|
||||
: (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT
|
||||
: ICON_FA_ANGLE_LEFT);
|
||||
if (ImGui::Button(control_bar.c_str(), ImVec2(15, 25))) {
|
||||
props->control_bar_expand_ = !props->control_bar_expand_;
|
||||
props->control_bar_button_pressed_time_ = ImGui::GetTime();
|
||||
props->control_window_width_is_changing_ = true;
|
||||
|
||||
if (!props->control_bar_expand_) {
|
||||
props->control_window_height_ = 40;
|
||||
props->net_traffic_stats_button_pressed_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) {
|
||||
NetTrafficStats(props);
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||
ImGui::SetCursorPos(ImVec2(props->is_control_bar_in_left_
|
||||
? (props->control_window_width_ + 5.0f)
|
||||
: 5.0f,
|
||||
40.0f));
|
||||
|
||||
if (ImGui::BeginTable("NetTrafficStats", 4, ImGuiTableFlags_BordersH,
|
||||
ImVec2(props->control_window_max_width_ - 10.0f,
|
||||
props->control_window_max_height_ - 60.0f))) {
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(" ");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", localization::in[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", localization::out[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::loss_rate[localization_language_index_].c_str());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::video[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.video_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.video_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.video_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::audio[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.audio_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.audio_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.audio_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", localization::data[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.data_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.data_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.data_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::total[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.total_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.total_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("FPS");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", props->fps_);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
#define BUTTON_PADDING 36.0f
|
||||
@@ -56,6 +57,7 @@ int Render::TitleBar(bool main_window) {
|
||||
|
||||
{
|
||||
SettingWindow();
|
||||
SelfHostedServerWindow();
|
||||
AboutWindow();
|
||||
}
|
||||
}
|
||||
@@ -137,9 +139,17 @@ int Render::TitleBar(bool main_window) {
|
||||
float xmark_size = 12.0f;
|
||||
std::string close_button = "##xmark"; // ICON_FA_XMARK;
|
||||
if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) {
|
||||
#if _WIN32
|
||||
if (enable_minimize_to_tray_) {
|
||||
tray_->MinimizeToTray();
|
||||
} else {
|
||||
#endif
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
#if _WIN32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
|
||||
xmark_pos_y - xmark_size / 2 + 0.75f),
|
||||
112
src/gui/tray/win_tray.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "win_tray.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "localization.h"
|
||||
|
||||
// callback for the message-only window that handles tray icon messages
|
||||
static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
WinTray* tray =
|
||||
reinterpret_cast<WinTray*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
if (!tray) {
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
if (msg == WM_TRAY_CALLBACK) {
|
||||
MSG tmpMsg = {};
|
||||
tmpMsg.message = msg;
|
||||
tmpMsg.wParam = wParam;
|
||||
tmpMsg.lParam = lParam;
|
||||
tray->HandleTrayMessage(&tmpMsg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
WinTray::WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
|
||||
int language_index)
|
||||
: app_hwnd_(app_hwnd),
|
||||
icon_(icon),
|
||||
tip_(tooltip),
|
||||
hwnd_message_only_(nullptr),
|
||||
language_index_(language_index) {
|
||||
WNDCLASS wc = {};
|
||||
wc.lpfnWndProc = MsgWndProc;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = L"TrayMessageWindow";
|
||||
RegisterClass(&wc);
|
||||
|
||||
// create a message-only window to receive tray messages
|
||||
hwnd_message_only_ =
|
||||
CreateWindowEx(0, wc.lpszClassName, L"TrayMsg", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, nullptr, wc.hInstance, nullptr);
|
||||
|
||||
// store pointer to this WinTray instance in window data
|
||||
SetWindowLongPtr(hwnd_message_only_, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(this));
|
||||
|
||||
// initialize NOTIFYICONDATA structure
|
||||
ZeroMemory(&nid_, sizeof(nid_));
|
||||
nid_.cbSize = sizeof(nid_);
|
||||
nid_.hWnd = hwnd_message_only_;
|
||||
nid_.uID = 1;
|
||||
nid_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
nid_.uCallbackMessage = WM_TRAY_CALLBACK;
|
||||
nid_.hIcon = icon_;
|
||||
wcsncpy_s(nid_.szTip, tip_.c_str(), _TRUNCATE);
|
||||
}
|
||||
|
||||
WinTray::~WinTray() {
|
||||
RemoveTrayIcon();
|
||||
if (hwnd_message_only_) DestroyWindow(hwnd_message_only_);
|
||||
}
|
||||
|
||||
void WinTray::MinimizeToTray() {
|
||||
Shell_NotifyIcon(NIM_ADD, &nid_);
|
||||
// hide application window
|
||||
ShowWindow(app_hwnd_, SW_HIDE);
|
||||
}
|
||||
|
||||
void WinTray::RemoveTrayIcon() { Shell_NotifyIcon(NIM_DELETE, &nid_); }
|
||||
|
||||
bool WinTray::HandleTrayMessage(MSG* msg) {
|
||||
if (!msg || msg->message != WM_TRAY_CALLBACK) return false;
|
||||
|
||||
switch (LOWORD(msg->lParam)) {
|
||||
case WM_LBUTTONDBLCLK:
|
||||
case WM_LBUTTONUP: {
|
||||
ShowWindow(app_hwnd_, SW_SHOW);
|
||||
SetForegroundWindow(app_hwnd_);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_RBUTTONUP: {
|
||||
POINT pt;
|
||||
GetCursorPos(&pt);
|
||||
HMENU menu = CreatePopupMenu();
|
||||
AppendMenuW(menu, MF_STRING, 1001,
|
||||
localization::exit_program[language_index_]);
|
||||
|
||||
SetForegroundWindow(hwnd_message_only_);
|
||||
int cmd =
|
||||
TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_LEFTALIGN,
|
||||
pt.x, pt.y, 0, hwnd_message_only_, nullptr);
|
||||
DestroyMenu(menu);
|
||||
|
||||
// handle menu command
|
||||
if (cmd == 1001) {
|
||||
// exit application
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
} else if (cmd == 1002) {
|
||||
ShowWindow(app_hwnd_, SW_SHOW); // show main window
|
||||
SetForegroundWindow(app_hwnd_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
36
src/gui/tray/win_tray.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-10-22
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _WIN_TRAY_H_
|
||||
#define _WIN_TRAY_H_
|
||||
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define WM_TRAY_CALLBACK (WM_USER + 1)
|
||||
|
||||
class WinTray {
|
||||
public:
|
||||
WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
|
||||
int language_index);
|
||||
~WinTray();
|
||||
|
||||
void MinimizeToTray();
|
||||
void RemoveTrayIcon();
|
||||
bool HandleTrayMessage(MSG* msg);
|
||||
|
||||
private:
|
||||
HWND app_hwnd_;
|
||||
HWND hwnd_message_only_;
|
||||
HICON icon_;
|
||||
std::wstring tip_;
|
||||
int language_index_;
|
||||
NOTIFYICONDATA nid_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "layout_style.h"
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
@@ -27,15 +27,21 @@ int Render::AboutWindow() {
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
|
||||
std::string version;
|
||||
#ifdef RD_VERSION
|
||||
version = RD_VERSION;
|
||||
#ifdef CROSSDESK_VERSION
|
||||
version = CROSSDESK_VERSION;
|
||||
#else
|
||||
version = "Unknown";
|
||||
#endif
|
||||
|
||||
std::string text =
|
||||
localization::version[localization_language_index_] + ": " + version;
|
||||
std::string text = localization::version[localization_language_index_] +
|
||||
": CrossDesk v" + version;
|
||||
ImGui::Text("%s", text.c_str());
|
||||
ImGui::Text("");
|
||||
|
||||
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
|
||||
std::string license_text = "Licensed under GNU LGPL v3.";
|
||||
ImGui::Text("%s", copyright_text.c_str());
|
||||
ImGui::Text("%s", license_text.c_str());
|
||||
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
|
||||
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
|
||||
171
src/gui/windows/connection_status_window.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
bool Render::ConnectionStatusWindow(
|
||||
std::shared_ptr<SubStreamWindowProperties> &props) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
bool ret_flag = false;
|
||||
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
connection_status_window_width_) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
connection_status_window_height_) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_,
|
||||
connection_status_window_height_));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
|
||||
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
std::string text;
|
||||
|
||||
if (ConnectionStatus::Connecting == props->connection_status_) {
|
||||
text = localization::p2p_connecting[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
} else if (ConnectionStatus::Connected == props->connection_status_) {
|
||||
text = localization::p2p_connected[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::Disconnected == props->connection_status_) {
|
||||
text = localization::p2p_disconnected[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::Failed == props->connection_status_) {
|
||||
text = localization::p2p_failed[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::Closed == props->connection_status_) {
|
||||
text = localization::p2p_closed[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::IncorrectPassword == props->connection_status_) {
|
||||
if (!password_validating_) {
|
||||
if (password_validating_time_ == 1) {
|
||||
text = localization::input_password[localization_language_index_];
|
||||
} else {
|
||||
text = localization::reinput_password[localization_language_index_];
|
||||
}
|
||||
|
||||
auto window_width = ImGui::GetWindowSize().x;
|
||||
auto window_height = ImGui::GetWindowSize().y;
|
||||
ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f);
|
||||
ImGui::SetCursorPosY(window_height * 0.4f);
|
||||
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
|
||||
if (focus_on_input_widget_) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
focus_on_input_widget_ = false;
|
||||
}
|
||||
|
||||
ImGui::InputText("##password", props->remote_password_,
|
||||
IM_ARRAYSIZE(props->remote_password_),
|
||||
ImGuiInputTextFlags_CharsNoBlank);
|
||||
|
||||
ImGui::SetWindowFontScale(0.4f);
|
||||
|
||||
ImVec2 text_size = ImGui::CalcTextSize(
|
||||
localization::remember_password[localization_language_index_]
|
||||
.c_str());
|
||||
ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f);
|
||||
ImGui::Checkbox(
|
||||
localization::remember_password[localization_language_index_].c_str(),
|
||||
&(props->remember_password_));
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SetCursorPosX(window_width * 0.315f);
|
||||
ImGui::SetCursorPosY(window_height * 0.75f);
|
||||
// ok
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
|
||||
show_connection_status_window_ = true;
|
||||
password_validating_ = true;
|
||||
props->rejoin_ = true;
|
||||
need_to_rejoin_ = true;
|
||||
focus_on_input_widget_ = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
memset(props->remote_password_, 0, sizeof(props->remote_password_));
|
||||
show_connection_status_window_ = false;
|
||||
focus_on_input_widget_ = true;
|
||||
}
|
||||
} else if (password_validating_) {
|
||||
text = localization::validate_password[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
}
|
||||
} else if (ConnectionStatus::NoSuchTransmissionId ==
|
||||
props->connection_status_) {
|
||||
text = localization::no_such_id[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
|
||||
show_connection_status_window_ = false;
|
||||
re_enter_remote_id_ = true;
|
||||
DestroyPeer(&props->peer_);
|
||||
ret_flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto window_width = ImGui::GetWindowSize().x;
|
||||
auto window_height = ImGui::GetWindowSize().y;
|
||||
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
|
||||
ImGui::SetCursorPosY(window_height * 0.2f);
|
||||
ImGui::Text("%s", text.c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return ret_flag;
|
||||
}
|
||||
223
src/gui/windows/control_window.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) {
|
||||
double time_duration =
|
||||
ImGui::GetTime() - props->control_bar_button_pressed_time_;
|
||||
if (props->control_window_width_is_changing_) {
|
||||
if (props->control_bar_expand_) {
|
||||
props->control_window_width_ =
|
||||
(float)(props->control_window_min_width_ +
|
||||
(props->control_window_max_width_ -
|
||||
props->control_window_min_width_) *
|
||||
4 * time_duration);
|
||||
} else {
|
||||
props->control_window_width_ =
|
||||
(float)(props->control_window_max_width_ -
|
||||
(props->control_window_max_width_ -
|
||||
props->control_window_min_width_) *
|
||||
4 * time_duration);
|
||||
}
|
||||
}
|
||||
|
||||
time_duration =
|
||||
ImGui::GetTime() - props->net_traffic_stats_button_pressed_time_;
|
||||
if (props->control_window_height_is_changing_) {
|
||||
if (props->control_bar_expand_ &&
|
||||
props->net_traffic_stats_button_pressed_) {
|
||||
props->control_window_height_ =
|
||||
(float)(props->control_window_min_height_ +
|
||||
(props->control_window_max_height_ -
|
||||
props->control_window_min_height_) *
|
||||
4 * time_duration);
|
||||
} else if (props->control_bar_expand_ &&
|
||||
!props->net_traffic_stats_button_pressed_) {
|
||||
props->control_window_height_ =
|
||||
(float)(props->control_window_max_height_ -
|
||||
(props->control_window_max_height_ -
|
||||
props->control_window_min_height_) *
|
||||
4 * time_duration);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 1));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(props->control_window_width_, props->control_window_height_),
|
||||
ImGuiCond_Always);
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_ + 1), ImGuiCond_Once);
|
||||
|
||||
float pos_x = 0;
|
||||
float pos_y = 0;
|
||||
float y_boundary = fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1);
|
||||
|
||||
if (props->reset_control_bar_pos_) {
|
||||
float new_cursor_pos_x = 0;
|
||||
float new_cursor_pos_y = 0;
|
||||
|
||||
// set control window pos
|
||||
if (props->control_window_pos_.y + props->control_window_height_ >
|
||||
stream_window_height_) {
|
||||
pos_y = stream_window_height_ - props->control_window_height_;
|
||||
} else if (props->control_window_pos_.y < y_boundary) {
|
||||
pos_y = y_boundary;
|
||||
} else {
|
||||
pos_y = props->control_window_pos_.y;
|
||||
}
|
||||
|
||||
if (props->is_control_bar_in_left_) {
|
||||
pos_x = 0;
|
||||
} else {
|
||||
pos_x = stream_window_width_ - props->control_window_width_;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
|
||||
|
||||
if (0 != props->mouse_diff_control_bar_pos_x_ &&
|
||||
0 != props->mouse_diff_control_bar_pos_y_) {
|
||||
// set cursor pos
|
||||
new_cursor_pos_x = pos_x + props->mouse_diff_control_bar_pos_x_;
|
||||
new_cursor_pos_y = pos_y + props->mouse_diff_control_bar_pos_y_;
|
||||
|
||||
SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x,
|
||||
(int)new_cursor_pos_y);
|
||||
}
|
||||
props->reset_control_bar_pos_ = false;
|
||||
} else if (!props->reset_control_bar_pos_ &&
|
||||
ImGui::IsMouseReleased(ImGuiMouseButton_Left) ||
|
||||
props->control_window_width_is_changing_) {
|
||||
if (props->control_window_pos_.x <= stream_window_width_ / 2) {
|
||||
if (props->control_window_pos_.y + props->control_window_height_ >
|
||||
stream_window_height_) {
|
||||
pos_y = stream_window_height_ - props->control_window_height_;
|
||||
} else {
|
||||
pos_y = props->control_window_pos_.y;
|
||||
}
|
||||
|
||||
if (props->control_bar_expand_) {
|
||||
if (props->control_window_width_ >= props->control_window_max_width_) {
|
||||
props->control_window_width_ = props->control_window_max_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
}
|
||||
} else {
|
||||
if (props->control_window_width_ <= props->control_window_min_width_) {
|
||||
props->control_window_width_ = props->control_window_min_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
}
|
||||
}
|
||||
props->is_control_bar_in_left_ = true;
|
||||
} else if (props->control_window_pos_.x > stream_window_width_ / 2) {
|
||||
pos_x = 0;
|
||||
pos_y =
|
||||
(props->control_window_pos_.y >= y_boundary &&
|
||||
props->control_window_pos_.y <=
|
||||
stream_window_height_ - props->control_window_height_)
|
||||
? props->control_window_pos_.y
|
||||
: (props->control_window_pos_.y < (fullscreen_button_pressed_
|
||||
? 0
|
||||
: (title_bar_height_ + 1))
|
||||
? (fullscreen_button_pressed_ ? 0
|
||||
: (title_bar_height_ + 1))
|
||||
: (stream_window_height_ - props->control_window_height_));
|
||||
|
||||
if (props->control_bar_expand_) {
|
||||
if (props->control_window_width_ >= props->control_window_max_width_) {
|
||||
props->control_window_width_ = props->control_window_max_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
pos_x = stream_window_width_ - props->control_window_max_width_;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
pos_x = stream_window_width_ - props->control_window_width_;
|
||||
}
|
||||
} else {
|
||||
if (props->control_window_width_ <= props->control_window_min_width_) {
|
||||
props->control_window_width_ = props->control_window_min_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
pos_x = stream_window_width_ - props->control_window_min_width_;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
pos_x = stream_window_width_ - props->control_window_width_;
|
||||
}
|
||||
}
|
||||
props->is_control_bar_in_left_ = false;
|
||||
}
|
||||
|
||||
if (props->control_window_pos_.y + props->control_window_height_ >
|
||||
stream_window_height_) {
|
||||
pos_y = stream_window_height_ - props->control_window_height_;
|
||||
} else if (props->control_window_pos_.y < y_boundary) {
|
||||
pos_y = y_boundary;
|
||||
}
|
||||
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
|
||||
}
|
||||
|
||||
if (props->control_bar_expand_ && props->control_window_height_is_changing_) {
|
||||
if (props->net_traffic_stats_button_pressed_) {
|
||||
if (props->control_window_height_ >= props->control_window_max_height_) {
|
||||
props->control_window_height_ = props->control_window_max_height_;
|
||||
props->control_window_height_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_height_is_changing_ = true;
|
||||
}
|
||||
} else {
|
||||
if (props->control_window_height_ <= props->control_window_min_height_) {
|
||||
props->control_window_height_ = props->control_window_min_height_;
|
||||
props->control_window_height_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_height_is_changing_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string control_window_title = props->remote_id_ + "ControlWindow";
|
||||
ImGui::Begin(control_window_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
props->control_window_pos_ = ImGui::GetWindowPos();
|
||||
SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_);
|
||||
props->mouse_diff_control_bar_pos_x_ =
|
||||
props->mouse_pos_x_ - props->control_window_pos_.x;
|
||||
props->mouse_diff_control_bar_pos_y_ =
|
||||
props->mouse_pos_y_ - props->control_window_pos_.y;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
static bool a, b, c, d, e;
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(props->is_control_bar_in_left_
|
||||
? props->control_window_pos_.x - props->control_window_width_
|
||||
: props->control_window_pos_.x,
|
||||
props->control_window_pos_.y),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
|
||||
std::string control_child_window_title =
|
||||
props->remote_id_ + "ControlChildWindow";
|
||||
ImGui::BeginChild(
|
||||
control_child_window_title.c_str(),
|
||||
ImVec2(props->control_window_width_ * 2, props->control_window_height_),
|
||||
ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ControlBar(props);
|
||||
props->control_bar_hovered_ = ImGui::IsWindowHovered();
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(4);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "layout_style.h"
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
@@ -6,7 +6,7 @@
|
||||
int Render::SettingWindow() {
|
||||
if (show_settings_window_) {
|
||||
if (settings_window_pos_reset_) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
@@ -36,6 +36,9 @@ int Render::SettingWindow() {
|
||||
|
||||
// Settings
|
||||
{
|
||||
static int settings_items_padding = 30;
|
||||
int settings_items_offset = 0;
|
||||
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
@@ -49,11 +52,12 @@ int Render::SettingWindow() {
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
{
|
||||
const char *language_items[] = {
|
||||
const char* language_items[] = {
|
||||
localization::language_zh[localization_language_index_].c_str(),
|
||||
localization::language_en[localization_language_index_].c_str()};
|
||||
|
||||
ImGui::SetCursorPosY(32);
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s", localization::language[localization_language_index_].c_str());
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
@@ -61,7 +65,7 @@ int Render::SettingWindow() {
|
||||
} else {
|
||||
ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(30);
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo("##language", &language_button_value_, language_items,
|
||||
@@ -70,12 +74,12 @@ int Render::SettingWindow() {
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (streaming_) {
|
||||
if (stream_window_inited_) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
{
|
||||
const char *video_quality_items[] = {
|
||||
const char* video_quality_items[] = {
|
||||
localization::video_quality_high[localization_language_index_]
|
||||
.c_str(),
|
||||
localization::video_quality_medium[localization_language_index_]
|
||||
@@ -83,7 +87,8 @@ int Render::SettingWindow() {
|
||||
localization::video_quality_low[localization_language_index_]
|
||||
.c_str()};
|
||||
|
||||
ImGui::SetCursorPosY(62);
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::video_quality[localization_language_index_].c_str());
|
||||
@@ -93,7 +98,7 @@ int Render::SettingWindow() {
|
||||
} else {
|
||||
ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(60);
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo("##video_quality", &video_quality_button_value_,
|
||||
@@ -103,11 +108,36 @@ int Render::SettingWindow() {
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
const char *video_encode_format_items[] = {
|
||||
localization::av1[localization_language_index_].c_str(),
|
||||
localization::h264[localization_language_index_].c_str()};
|
||||
const char* video_frame_rate_items[] = {"30 fps", "60 fps"};
|
||||
|
||||
ImGui::SetCursorPosY(92);
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text("%s",
|
||||
localization::video_frame_rate[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo("##video_frame_rate", &video_frame_rate_button_value_,
|
||||
video_frame_rate_items,
|
||||
IM_ARRAYSIZE(video_frame_rate_items));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
const char* video_encode_format_items[] = {
|
||||
localization::h264[localization_language_index_].c_str(),
|
||||
localization::av1[localization_language_index_].c_str()};
|
||||
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::video_encode_format[localization_language_index_]
|
||||
@@ -118,7 +148,7 @@ int Render::SettingWindow() {
|
||||
} else {
|
||||
ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(90);
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo(
|
||||
@@ -129,7 +159,8 @@ int Render::SettingWindow() {
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
ImGui::SetCursorPosY(122);
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text("%s", localization::enable_hardware_video_codec
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
@@ -139,7 +170,7 @@ int Render::SettingWindow() {
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(120);
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_hardware_video_codec",
|
||||
&enable_hardware_video_codec_);
|
||||
}
|
||||
@@ -147,7 +178,8 @@ int Render::SettingWindow() {
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
ImGui::SetCursorPosY(152);
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::enable_turn[localization_language_index_].c_str());
|
||||
@@ -157,11 +189,71 @@ int Render::SettingWindow() {
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(150);
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_turn", &enable_turn_);
|
||||
}
|
||||
|
||||
if (streaming_) {
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::enable_srtp[localization_language_index_].c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_srtp", &enable_srtp_);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 1);
|
||||
|
||||
if (ImGui::Button(localization::self_hosted_server_config
|
||||
[localization_language_index_]
|
||||
.c_str())) {
|
||||
show_self_hosted_server_config_window_ = true;
|
||||
}
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_self_hosted_server",
|
||||
&enable_self_hosted_server_);
|
||||
}
|
||||
#if _WIN32
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
|
||||
ImGui::Text("%s",
|
||||
localization::minimize_to_tray[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_minimize_to_tray_",
|
||||
&enable_minimize_to_tray_);
|
||||
}
|
||||
#endif
|
||||
if (stream_window_inited_) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
@@ -170,19 +262,22 @@ int Render::SettingWindow() {
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(190.0f);
|
||||
|
||||
settings_items_offset += settings_items_padding + 10;
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// OK
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = false;
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
|
||||
// Language
|
||||
if (language_button_value_ == 0) {
|
||||
config_center_.SetLanguage(ConfigCenter::LANGUAGE::CHINESE);
|
||||
config_center_->SetLanguage(ConfigCenter::LANGUAGE::CHINESE);
|
||||
} else {
|
||||
config_center_.SetLanguage(ConfigCenter::LANGUAGE::ENGLISH);
|
||||
config_center_->SetLanguage(ConfigCenter::LANGUAGE::ENGLISH);
|
||||
}
|
||||
language_button_value_last_ = language_button_value_;
|
||||
localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_;
|
||||
@@ -192,52 +287,64 @@ int Render::SettingWindow() {
|
||||
|
||||
// Video quality
|
||||
if (video_quality_button_value_ == 0) {
|
||||
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
|
||||
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
|
||||
} else if (video_quality_button_value_ == 1) {
|
||||
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
|
||||
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
|
||||
} else {
|
||||
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
|
||||
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
|
||||
}
|
||||
video_quality_button_value_last_ = video_quality_button_value_;
|
||||
|
||||
// Video encode format
|
||||
if (video_encode_format_button_value_ == 0) {
|
||||
config_center_.SetVideoEncodeFormat(
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT::AV1);
|
||||
} else if (video_encode_format_button_value_ == 1) {
|
||||
config_center_.SetVideoEncodeFormat(
|
||||
config_center_->SetVideoEncodeFormat(
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT::H264);
|
||||
} else if (video_encode_format_button_value_ == 1) {
|
||||
config_center_->SetVideoEncodeFormat(
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT::AV1);
|
||||
}
|
||||
video_encode_format_button_value_last_ =
|
||||
video_encode_format_button_value_;
|
||||
|
||||
// Hardware video codec
|
||||
if (enable_hardware_video_codec_) {
|
||||
config_center_.SetHardwareVideoCodec(true);
|
||||
config_center_->SetHardwareVideoCodec(true);
|
||||
} else {
|
||||
config_center_.SetHardwareVideoCodec(false);
|
||||
config_center_->SetHardwareVideoCodec(false);
|
||||
}
|
||||
enable_hardware_video_codec_last_ = enable_hardware_video_codec_;
|
||||
|
||||
// TURN mode
|
||||
if (enable_turn_) {
|
||||
config_center_.SetTurn(true);
|
||||
config_center_->SetTurn(true);
|
||||
} else {
|
||||
config_center_.SetTurn(false);
|
||||
config_center_->SetTurn(false);
|
||||
}
|
||||
enable_turn_last_ = enable_turn_;
|
||||
|
||||
SaveSettingsIntoCacheFile();
|
||||
// SRTP
|
||||
if (enable_srtp_) {
|
||||
config_center_->SetSrtp(true);
|
||||
} else {
|
||||
config_center_->SetSrtp(false);
|
||||
}
|
||||
enable_srtp_last_ = enable_srtp_;
|
||||
|
||||
if (enable_self_hosted_server_) {
|
||||
config_center_->SetSelfHosted(true);
|
||||
} else {
|
||||
config_center_->SetSelfHosted(false);
|
||||
}
|
||||
|
||||
settings_window_pos_reset_ = true;
|
||||
|
||||
// Recreate peer instance
|
||||
LoadSettingsFromCacheFile();
|
||||
|
||||
// Recreate peer instance
|
||||
if (!streaming_) {
|
||||
if (!stream_window_inited_) {
|
||||
LOG_INFO("Recreate peer instance");
|
||||
DestroyPeer(&peer_);
|
||||
is_create_connection_ = false;
|
||||
CleanupPeers();
|
||||
CreateConnectionPeer();
|
||||
}
|
||||
}
|
||||
@@ -247,6 +354,8 @@ int Render::SettingWindow() {
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = false;
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
|
||||
if (language_button_value_ != language_button_value_last_) {
|
||||
language_button_value_ = language_button_value_last_;
|
||||
}
|
||||
@@ -28,7 +28,22 @@ int Render::MainWindow() {
|
||||
|
||||
RecentConnectionsWindow();
|
||||
StatusBar();
|
||||
ConnectionStatusWindow();
|
||||
|
||||
if (show_connection_status_window_) {
|
||||
for (auto it = client_properties_.begin();
|
||||
it != client_properties_.end();) {
|
||||
auto& props = it->second;
|
||||
if (focused_remote_id_ == props->remote_id_) {
|
||||
if (ConnectionStatusWindow(props)) {
|
||||
it = client_properties_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
270
src/gui/windows/server_settings_window.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
std::vector<std::string> GetRootEntries() {
|
||||
std::vector<std::string> roots;
|
||||
#ifdef _WIN32
|
||||
DWORD mask = GetLogicalDrives();
|
||||
for (char letter = 'A'; letter <= 'Z'; ++letter) {
|
||||
if (mask & 1) {
|
||||
roots.push_back(std::string(1, letter) + ":\\");
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
#else
|
||||
roots.push_back("/");
|
||||
#endif
|
||||
return roots;
|
||||
}
|
||||
|
||||
int Render::ShowSimpleFileBrowser() {
|
||||
std::string display_text;
|
||||
if (!selected_file_.empty()) {
|
||||
display_text = std::filesystem::path(selected_file_).filename().string();
|
||||
} else if (selected_current_file_path_ != "Root") {
|
||||
display_text =
|
||||
std::filesystem::path(selected_current_file_path_).filename().string();
|
||||
if (display_text.empty()) {
|
||||
display_text = selected_current_file_path_;
|
||||
}
|
||||
}
|
||||
|
||||
if (display_text.empty()) {
|
||||
display_text =
|
||||
localization::select_a_file[localization_language_index_].c_str();
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo("##select_a_file", display_text.c_str())) {
|
||||
if (selected_current_file_path_ == "Root" ||
|
||||
!std::filesystem::exists(selected_current_file_path_) ||
|
||||
!std::filesystem::is_directory(selected_current_file_path_)) {
|
||||
auto roots = GetRootEntries();
|
||||
for (const auto& root : roots) {
|
||||
if (ImGui::Selectable(root.c_str())) {
|
||||
selected_current_file_path_ = root;
|
||||
selected_file_.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::filesystem::path p(selected_current_file_path_);
|
||||
|
||||
if (ImGui::Selectable("..")) {
|
||||
if (p.has_parent_path() && p != p.root_path())
|
||||
selected_current_file_path_ = p.parent_path().string();
|
||||
else
|
||||
selected_current_file_path_ = "Root";
|
||||
selected_file_.clear();
|
||||
}
|
||||
|
||||
try {
|
||||
for (const auto& entry :
|
||||
std::filesystem::directory_iterator(selected_current_file_path_)) {
|
||||
std::string name = entry.path().filename().string();
|
||||
if (entry.is_directory()) {
|
||||
if (ImGui::Selectable(name.c_str())) {
|
||||
selected_current_file_path_ = entry.path().string();
|
||||
selected_file_.clear();
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Selectable(name.c_str())) {
|
||||
selected_file_ = entry.path().string();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::SelfHostedServerWindow() {
|
||||
if (show_self_hosted_server_config_window_) {
|
||||
if (self_hosted_server_config_window_pos_reset_) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN,
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN));
|
||||
} else {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN,
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN));
|
||||
}
|
||||
|
||||
self_hosted_server_config_window_pos_reset_ = false;
|
||||
}
|
||||
|
||||
// Settings
|
||||
{
|
||||
static int settings_items_padding = 30;
|
||||
int settings_items_offset = 0;
|
||||
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::Begin(localization::self_hosted_server_settings
|
||||
[localization_language_index_]
|
||||
.c_str(),
|
||||
nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text("%s", localization::self_hosted_server_address
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::InputText("##signal_server_ip_tmp_", signal_server_ip_tmp_,
|
||||
IM_ARRAYSIZE(signal_server_ip_tmp_),
|
||||
ImGuiInputTextFlags_AlwaysOverwrite);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::self_hosted_server_port[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::InputText("##signal_server_port_tmp_", signal_server_port_tmp_,
|
||||
IM_ARRAYSIZE(signal_server_port_tmp_));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text("%s", localization::self_hosted_server_certificate_path
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ShowSimpleFileBrowser();
|
||||
}
|
||||
|
||||
if (stream_window_inited_) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN);
|
||||
}
|
||||
|
||||
settings_items_offset += settings_items_padding + 10;
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// OK
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str())) {
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
|
||||
config_center_->SetServerHost(signal_server_ip_tmp_);
|
||||
config_center_->SetServerPort(atoi(signal_server_port_tmp_));
|
||||
config_center_->SetCertFilePath(selected_file_);
|
||||
strncpy(signal_server_ip_, signal_server_ip_tmp_,
|
||||
sizeof(signal_server_ip_) - 1);
|
||||
signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0';
|
||||
strncpy(signal_server_port_, signal_server_port_tmp_,
|
||||
sizeof(signal_server_port_) - 1);
|
||||
signal_server_port_[sizeof(signal_server_port_) - 1] = '\0';
|
||||
strncpy(cert_file_path_, selected_file_.c_str(),
|
||||
sizeof(cert_file_path_) - 1);
|
||||
cert_file_path_[sizeof(cert_file_path_) - 1] = '\0';
|
||||
|
||||
self_hosted_server_config_window_pos_reset_ = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// Cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str())) {
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
self_hosted_server_config_window_pos_reset_ = true;
|
||||
|
||||
strncpy(signal_server_ip_tmp_, signal_server_ip_,
|
||||
sizeof(signal_server_ip_tmp_) - 1);
|
||||
signal_server_ip_tmp_[sizeof(signal_server_ip_tmp_) - 1] = '\0';
|
||||
strncpy(signal_server_port_tmp_, signal_server_port_,
|
||||
sizeof(signal_server_port_tmp_) - 1);
|
||||
signal_server_port_tmp_[sizeof(signal_server_port_tmp_) - 1] = '\0';
|
||||
config_center_->SetServerHost(signal_server_ip_tmp_);
|
||||
config_center_->SetServerPort(atoi(signal_server_port_tmp_));
|
||||
selected_file_.clear();
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
202
src/gui/windows/stream_window.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
void Render::DrawConnectionStatusText(
|
||||
std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||
std::string text;
|
||||
switch (props->connection_status_) {
|
||||
case ConnectionStatus::Disconnected:
|
||||
text = localization::p2p_disconnected[localization_language_index_];
|
||||
break;
|
||||
case ConnectionStatus::Failed:
|
||||
text = localization::p2p_failed[localization_language_index_];
|
||||
break;
|
||||
case ConnectionStatus::Closed:
|
||||
text = localization::p2p_closed[localization_language_index_];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!text.empty()) {
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2((size.x - text_size.x) * 0.5f,
|
||||
(size.y - text_size.y - title_bar_height_) * 0.5f));
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Render::CloseTab(decltype(client_properties_)::iterator& it) {
|
||||
CleanupPeer(it->second);
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
}
|
||||
|
||||
int Render::StreamWindow() {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
|
||||
ImGui::Begin("VideoBg", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoDocking);
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGuiWindowFlags stream_window_flag =
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove;
|
||||
|
||||
if (!fullscreen_button_pressed_) {
|
||||
ImGui::SetNextWindowPos(ImVec2(20, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(0, 20), ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 8.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
|
||||
ImGui::Begin("TabBar", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoDocking);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
if (ImGui::BeginTabBar("StreamTabBar",
|
||||
ImGuiTabBarFlags_Reorderable |
|
||||
ImGuiTabBarFlags_AutoSelectNewTabs)) {
|
||||
is_tab_bar_hovered_ = ImGui::IsWindowHovered();
|
||||
|
||||
for (auto it = client_properties_.begin();
|
||||
it != client_properties_.end();) {
|
||||
auto& props = it->second;
|
||||
if (!props->tab_opened_) {
|
||||
CloseTab(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(0.6f);
|
||||
std::string tab_label =
|
||||
enable_srtp_
|
||||
? std::string(ICON_FA_SHIELD_HALVED) + " " + props->remote_id_
|
||||
: props->remote_id_;
|
||||
if (ImGui::BeginTabItem(tab_label.c_str(), &props->tab_opened_)) {
|
||||
props->tab_selected_ = true;
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(stream_window_width_, stream_window_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
|
||||
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImVec2 pos = ImGui::GetWindowPos();
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
props->render_window_x_ = pos.x;
|
||||
props->render_window_y_ = pos.y;
|
||||
props->render_window_width_ = size.x;
|
||||
props->render_window_height_ = size.y;
|
||||
UpdateRenderRect();
|
||||
|
||||
ControlWindow(props);
|
||||
|
||||
focused_remote_id_ = props->remote_id_;
|
||||
|
||||
if (!props->peer_) {
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
} else {
|
||||
DrawConnectionStatusText(props);
|
||||
++it;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::EndTabItem();
|
||||
} else {
|
||||
props->tab_selected_ = false;
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::End(); // End TabBar
|
||||
} else {
|
||||
for (auto it = client_properties_.begin();
|
||||
it != client_properties_.end();) {
|
||||
auto& props = it->second;
|
||||
if (!props->tab_opened_) {
|
||||
CloseTab(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (props->tab_selected_) {
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(stream_window_width_, stream_window_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
|
||||
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImVec2 pos = ImGui::GetWindowPos();
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
props->render_window_x_ = pos.x;
|
||||
props->render_window_y_ = pos.y;
|
||||
props->render_window_width_ = size.x;
|
||||
props->render_window_height_ = size.y;
|
||||
UpdateRenderRect();
|
||||
|
||||
ControlWindow(props);
|
||||
ImGui::End();
|
||||
|
||||
if (!props->peer_) {
|
||||
fullscreen_button_pressed_ = false;
|
||||
SDL_SetWindowFullscreen(stream_window_, false);
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
} else {
|
||||
DrawConnectionStatusText(props);
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRenderRect();
|
||||
ImGui::End(); // End VideoBg
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
#ifndef _LOCALIZATION_H_
|
||||
#define _LOCALIZATION_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace localization {
|
||||
|
||||
static std::vector<std::string> local_desktop = {u8"本桌面", "Local Desktop"};
|
||||
static std::vector<std::string> local_id = {u8"本机ID", "Local ID"};
|
||||
static std::vector<std::string> local_id_copied_to_clipboard = {
|
||||
u8"已复制到剪贴板", "Copied to clipboard"};
|
||||
static std::vector<std::string> password = {u8"密码", "Password"};
|
||||
static std::vector<std::string> max_password_len = {u8"最大6个字符",
|
||||
"Max 6 chars"};
|
||||
|
||||
static std::vector<std::string> remote_desktop = {u8"控制远程桌面",
|
||||
"Control Remote Desktop"};
|
||||
static std::vector<std::string> remote_id = {u8"对端ID", "Remote ID"};
|
||||
static std::vector<std::string> connect = {u8"连接", "Connect"};
|
||||
static std::vector<std::string> recent_connections = {u8"近期连接",
|
||||
"Recent Connections"};
|
||||
static std::vector<std::string> disconnect = {u8"断开连接", "Disconnect"};
|
||||
static std::vector<std::string> fullscreen = {u8"全屏", " Fullscreen"};
|
||||
static std::vector<std::string> show_net_traffic_stats = {
|
||||
u8"显示流量统计", "Show Net Traffic Stats"};
|
||||
static std::vector<std::string> hide_net_traffic_stats = {
|
||||
u8"隐藏流量统计", "Hide Net Traffic Stats"};
|
||||
static std::vector<std::string> video = {u8"视频", "Video"};
|
||||
static std::vector<std::string> audio = {u8"音频", "Audio"};
|
||||
static std::vector<std::string> data = {u8"数据", "Data"};
|
||||
static std::vector<std::string> total = {u8"总计", "Total"};
|
||||
static std::vector<std::string> in = {u8"输入", "In"};
|
||||
static std::vector<std::string> out = {u8"输出", "Out"};
|
||||
static std::vector<std::string> loss_rate = {u8"丢包率", "Loss Rate"};
|
||||
static std::vector<std::string> exit_fullscreen = {u8"退出全屏",
|
||||
"Exit fullscreen"};
|
||||
static std::vector<std::string> control_mouse = {u8"控制", "Control"};
|
||||
static std::vector<std::string> release_mouse = {u8"释放", "Release"};
|
||||
static std::vector<std::string> audio_capture = {u8"声音", "Audio"};
|
||||
static std::vector<std::string> mute = {u8" 静音", " Mute"};
|
||||
static std::vector<std::string> settings = {u8"设置", "Settings"};
|
||||
static std::vector<std::string> language = {u8"语言:", "Language:"};
|
||||
static std::vector<std::string> language_zh = {u8"中文", "Chinese"};
|
||||
static std::vector<std::string> language_en = {u8"英文", "English"};
|
||||
static std::vector<std::string> video_quality = {u8"视频质量:",
|
||||
"Video Quality:"};
|
||||
static std::vector<std::string> video_quality_high = {u8"高", "High"};
|
||||
static std::vector<std::string> video_quality_medium = {u8"中", "Medium"};
|
||||
static std::vector<std::string> video_quality_low = {u8"低", "Low"};
|
||||
static std::vector<std::string> video_encode_format = {u8"视频编码格式:",
|
||||
"Video Encode Format:"};
|
||||
static std::vector<std::string> av1 = {u8"AV1", "AV1"};
|
||||
static std::vector<std::string> h264 = {u8"H.264", "H.264"};
|
||||
static std::vector<std::string> enable_hardware_video_codec = {
|
||||
u8"启用硬件编解码器:", "Enable Hardware Video Codec:"};
|
||||
static std::vector<std::string> enable_turn = {u8"启用中继服务:",
|
||||
"Enable TURN Service:"};
|
||||
|
||||
static std::vector<std::string> ok = {u8"确认", "OK"};
|
||||
static std::vector<std::string> cancel = {u8"取消", "Cancel"};
|
||||
|
||||
static std::vector<std::string> new_password = {
|
||||
u8"请输入六位密码:", "Please input a six-char password:"};
|
||||
|
||||
static std::vector<std::string> input_password = {u8"请输入密码:",
|
||||
"Please input password:"};
|
||||
static std::vector<std::string> validate_password = {u8"验证密码中...",
|
||||
"Validate password ..."};
|
||||
static std::vector<std::string> reinput_password = {
|
||||
u8"请重新输入密码", "Please input password again"};
|
||||
|
||||
static std::vector<std::string> remember_password = {u8"记住密码",
|
||||
"Remember password"};
|
||||
|
||||
static std::vector<std::string> signal_connected = {u8"已连接服务器",
|
||||
"Connected"};
|
||||
static std::vector<std::string> signal_disconnected = {u8"未连接服务器",
|
||||
"Disconnected"};
|
||||
|
||||
static std::vector<std::string> p2p_connected = {u8"对等连接已建立",
|
||||
"P2P Connected"};
|
||||
static std::vector<std::string> p2p_disconnected = {u8"对等连接已断开",
|
||||
"P2P Disconnected"};
|
||||
static std::vector<std::string> p2p_connecting = {u8"正在建立对等连接...",
|
||||
"P2P Connecting ..."};
|
||||
static std::vector<std::string> p2p_failed = {u8"对等连接失败", "P2P Failed"};
|
||||
static std::vector<std::string> p2p_closed = {u8"对等连接已关闭", "P2P closed"};
|
||||
|
||||
static std::vector<std::string> no_such_id = {u8"无此ID", "No such ID"};
|
||||
|
||||
static std::vector<std::string> about = {u8"关于", "About"};
|
||||
static std::vector<std::string> version = {u8"版本", "Version"};
|
||||
|
||||
static std::vector<std::string> confirm_delete_connection = {
|
||||
u8"确认删除此连接", "Confirm to delete this connection"};
|
||||
} // namespace localization
|
||||
|
||||
#endif
|
||||
@@ -1,15 +1,40 @@
|
||||
#include "rd_log.h"
|
||||
|
||||
std::shared_ptr<spdlog::logger> get_logger() {
|
||||
if (auto logger = spdlog::get(LOGGER_NAME)) {
|
||||
return logger;
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string g_log_dir = "logs";
|
||||
std::once_flag g_logger_once_flag;
|
||||
std::shared_ptr<spdlog::logger> g_logger;
|
||||
std::atomic<bool> g_logger_created{false};
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitLogger(const std::string& log_dir) {
|
||||
if (g_logger_created.load()) {
|
||||
LOG_WARN(
|
||||
"InitLogger called after logger initialized. Ignoring log_dir: {}, "
|
||||
"using previous log_dir: {}",
|
||||
log_dir, g_log_dir);
|
||||
return;
|
||||
}
|
||||
|
||||
g_log_dir = log_dir;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger> get_logger() {
|
||||
std::call_once(g_logger_once_flag, []() {
|
||||
g_logger_created.store(true);
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(g_log_dir, ec);
|
||||
|
||||
auto now = std::chrono::system_clock::now() + std::chrono::hours(8);
|
||||
auto now_time = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::tm tm_info;
|
||||
|
||||
#ifdef _WIN32
|
||||
gmtime_s(&tm_info, &now_time);
|
||||
#else
|
||||
@@ -17,21 +42,21 @@ std::shared_ptr<spdlog::logger> get_logger() {
|
||||
#endif
|
||||
|
||||
std::stringstream ss;
|
||||
std::string filename;
|
||||
ss << LOGGER_NAME;
|
||||
ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log");
|
||||
ss >> filename;
|
||||
|
||||
std::string path = "logs/" + filename;
|
||||
std::string filename = g_log_dir + "/" + ss.str();
|
||||
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
|
||||
sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
path, 1048576 * 5, 3));
|
||||
filename, 5 * 1024 * 1024, 3));
|
||||
|
||||
auto combined_logger =
|
||||
std::make_shared<spdlog::logger>(LOGGER_NAME, begin(sinks), end(sinks));
|
||||
combined_logger->flush_on(spdlog::level::info);
|
||||
spdlog::register_logger(combined_logger);
|
||||
g_logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(),
|
||||
sinks.end());
|
||||
g_logger->flush_on(spdlog::level::info);
|
||||
spdlog::register_logger(g_logger);
|
||||
});
|
||||
|
||||
return combined_logger;
|
||||
return g_logger;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-26
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
* @Date: 2025-07-21
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _RD_LOG_H_
|
||||
@@ -10,8 +10,11 @@
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "spdlog/common.h"
|
||||
#include "spdlog/logger.h"
|
||||
@@ -20,20 +23,17 @@
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
|
||||
|
||||
constexpr auto LOGGER_NAME = "rd";
|
||||
constexpr auto LOGGER_NAME = "crossdesk";
|
||||
|
||||
void InitLogger(const std::string& log_dir);
|
||||
|
||||
std::shared_ptr<spdlog::logger> get_logger();
|
||||
|
||||
#define LOG_INFO(...) SPDLOG_LOGGER_INFO(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_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__);
|
||||
#define LOG_INFO(...) SPDLOG_LOGGER_INFO(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_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__)
|
||||
|
||||
#endif
|
||||
91
src/path_manager/path_manager.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "path_manager.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {}
|
||||
|
||||
std::filesystem::path PathManager::GetConfigPath() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_;
|
||||
#elif __APPLE__
|
||||
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_;
|
||||
#else
|
||||
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetCachePath() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache";
|
||||
#elif __APPLE__
|
||||
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
|
||||
#else
|
||||
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetLogPath() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs";
|
||||
#elif __APPLE__
|
||||
return GetHome() + "/Library/Logs/" + app_name_;
|
||||
#else
|
||||
return GetCachePath() / app_name_ / "logs";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetCertPath() {
|
||||
#ifdef _WIN32
|
||||
// %APPDATA%\AppName\Certs
|
||||
return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_ / "certs";
|
||||
#elif __APPLE__
|
||||
// $HOME/Library/Application Support/AppName/certs
|
||||
return GetHome() + "/Library/Application Support/" + app_name_ + "/certs";
|
||||
#else
|
||||
// $XDG_CONFIG_HOME/AppName/certs
|
||||
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") /
|
||||
app_name_ / "certs";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PathManager::CreateDirectories(const std::filesystem::path& p) {
|
||||
std::error_code ec;
|
||||
bool created = std::filesystem::create_directories(p, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
return created || std::filesystem::exists(p);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) {
|
||||
PWSTR path = NULL;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(id, 0, NULL, &path))) {
|
||||
std::wstring wpath(path);
|
||||
CoTaskMemFree(path);
|
||||
return std::filesystem::path(wpath);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string PathManager::GetHome() {
|
||||
if (const char* home = getenv("HOME")) {
|
||||
return std::string(home);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path)))
|
||||
return std::string(path);
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var,
|
||||
const std::string& def) {
|
||||
if (const char* val = getenv(env_var)) {
|
||||
return std::filesystem::path(val);
|
||||
}
|
||||
|
||||
return std::filesystem::path(def);
|
||||
}
|
||||
44
src/path_manager/path_manager.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-07-16
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _PATH_MANAGER_H_
|
||||
#define _PATH_MANAGER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class PathManager {
|
||||
public:
|
||||
explicit PathManager(const std::string& app_name);
|
||||
|
||||
std::filesystem::path GetConfigPath();
|
||||
|
||||
std::filesystem::path GetCachePath();
|
||||
|
||||
std::filesystem::path GetLogPath();
|
||||
|
||||
std::filesystem::path GetCertPath();
|
||||
|
||||
bool CreateDirectories(const std::filesystem::path& p);
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id);
|
||||
#endif
|
||||
|
||||
std::string GetHome();
|
||||
std::filesystem::path GetEnvOrDefault(const char* env_var,
|
||||
const std::string& def);
|
||||
|
||||
private:
|
||||
std::string app_name_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,152 +1,174 @@
|
||||
#include "screen_capturer_x11.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
ScreenCapturerX11::ScreenCapturerX11() {}
|
||||
|
||||
ScreenCapturerX11::~ScreenCapturerX11() {
|
||||
if (inited_ && capture_thread_.joinable()) {
|
||||
capture_thread_.join();
|
||||
inited_ = false;
|
||||
}
|
||||
}
|
||||
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
|
||||
|
||||
int ScreenCapturerX11::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
_on_data = cb;
|
||||
int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
|
||||
display_ = XOpenDisplay(nullptr);
|
||||
if (!display_) {
|
||||
LOG_ERROR("Cannot connect to X server");
|
||||
return -1;
|
||||
}
|
||||
|
||||
root_ = DefaultRootWindow(display_);
|
||||
screen_res_ = XRRGetScreenResources(display_, root_);
|
||||
if (!screen_res_) {
|
||||
LOG_ERROR("Failed to get screen resources");
|
||||
XCloseDisplay(display_);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < screen_res_->noutput; ++i) {
|
||||
RROutput output = screen_res_->outputs[i];
|
||||
XRROutputInfo* output_info =
|
||||
XRRGetOutputInfo(display_, screen_res_, output);
|
||||
|
||||
if (output_info->connection == RR_Connected && output_info->crtc != 0) {
|
||||
XRRCrtcInfo* crtc_info =
|
||||
XRRGetCrtcInfo(display_, screen_res_, output_info->crtc);
|
||||
|
||||
display_info_list_.push_back(
|
||||
DisplayInfo((void*)display_, output_info->name, true, crtc_info->x,
|
||||
crtc_info->y, crtc_info->width, crtc_info->height));
|
||||
|
||||
XRRFreeCrtcInfo(crtc_info);
|
||||
}
|
||||
|
||||
if (output_info) {
|
||||
XRRFreeOutputInfo(output_info);
|
||||
}
|
||||
}
|
||||
|
||||
XWindowAttributes attr;
|
||||
XGetWindowAttributes(display_, root_, &attr);
|
||||
|
||||
width_ = attr.width;
|
||||
height_ = attr.height;
|
||||
|
||||
if (width_ % 2 != 0 || height_ % 2 != 0) {
|
||||
LOG_ERROR("Width and height must be even numbers");
|
||||
return -2;
|
||||
}
|
||||
|
||||
fps_ = fps;
|
||||
callback_ = cb;
|
||||
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
|
||||
pFormatCtx_ = avformat_alloc_context();
|
||||
|
||||
avdevice_register_all();
|
||||
|
||||
// grabbing frame rate
|
||||
av_dict_set(&options_, "framerate", "30", 0);
|
||||
// show remote cursor
|
||||
av_dict_set(&options_, "capture_cursor", "0", 0);
|
||||
// Make the grabbed area follow the mouse
|
||||
// av_dict_set(&options_, "follow_mouse", "centered", 0);
|
||||
// Video frame size. The default is to capture the full screen
|
||||
// av_dict_set(&options_, "video_size", "1280x720", 0);
|
||||
std::string capture_method = "x11grab";
|
||||
ifmt_ = (AVInputFormat *)av_find_input_format(capture_method.c_str());
|
||||
if (!ifmt_) {
|
||||
LOG_ERROR("Couldn't find_input_format [{}]", capture_method.c_str());
|
||||
}
|
||||
|
||||
// Grab at position 10,20
|
||||
if (avformat_open_input(&pFormatCtx_, ":0.0", ifmt_, &options_) != 0) {
|
||||
printf("Couldn't open input stream.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
|
||||
printf("Couldn't find stream information.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
videoindex_ = -1;
|
||||
for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++)
|
||||
if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
videoindex_ = i_;
|
||||
break;
|
||||
}
|
||||
if (videoindex_ == -1) {
|
||||
printf("Didn't find a video stream.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar;
|
||||
|
||||
pCodecCtx_ = avcodec_alloc_context3(NULL);
|
||||
avcodec_parameters_to_context(pCodecCtx_, pCodecParam_);
|
||||
|
||||
pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id));
|
||||
if (pCodec_ == NULL) {
|
||||
printf("Codec not found.\n");
|
||||
return -1;
|
||||
}
|
||||
if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) {
|
||||
printf("Could not open codec.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width;
|
||||
const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height;
|
||||
|
||||
pFrame_ = av_frame_alloc();
|
||||
pFrameNv12_ = av_frame_alloc();
|
||||
|
||||
pFrame_->width = screen_w;
|
||||
pFrame_->height = screen_h;
|
||||
pFrameNv12_->width = 1280;
|
||||
pFrameNv12_->height = 720;
|
||||
|
||||
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
|
||||
|
||||
img_convert_ctx_ = sws_getContext(
|
||||
pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width,
|
||||
pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
|
||||
|
||||
inited_ = true;
|
||||
y_plane_.resize(width_ * height_);
|
||||
uv_plane_.resize((width_ / 2) * (height_ / 2) * 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Destroy() {
|
||||
running_ = false;
|
||||
Stop();
|
||||
|
||||
y_plane_.clear();
|
||||
uv_plane_.clear();
|
||||
|
||||
if (screen_res_) {
|
||||
XRRFreeScreenResources(screen_res_);
|
||||
screen_res_ = nullptr;
|
||||
}
|
||||
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
display_ = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Start() {
|
||||
capture_thread_ = std::thread([this]() {
|
||||
if (running_) return 0;
|
||||
running_ = true;
|
||||
paused_ = false;
|
||||
thread_ = std::thread([this]() {
|
||||
while (running_) {
|
||||
if (av_read_frame(pFormatCtx_, packet_) >= 0) {
|
||||
if (packet_->stream_index == videoindex_) {
|
||||
avcodec_send_packet(pCodecCtx_, packet_);
|
||||
av_packet_unref(packet_);
|
||||
got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_);
|
||||
|
||||
if (!got_picture_) {
|
||||
av_image_fill_arrays(pFrameNv12_->data, pFrameNv12_->linesize,
|
||||
nv12_buffer_, AV_PIX_FMT_NV12,
|
||||
pFrameNv12_->width, pFrameNv12_->height, 1);
|
||||
|
||||
sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0,
|
||||
pFrame_->height, pFrameNv12_->data,
|
||||
pFrameNv12_->linesize);
|
||||
|
||||
_on_data((unsigned char *)nv12_buffer_,
|
||||
pFrameNv12_->width * pFrameNv12_->height * 3 / 2,
|
||||
pFrameNv12_->width, pFrameNv12_->height);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!paused_) OnFrame();
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Stop() {
|
||||
if (!running_) return 0;
|
||||
running_ = false;
|
||||
if (thread_.joinable()) thread_.join();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Pause() { return 0; }
|
||||
int ScreenCapturerX11::Pause(int monitor_index) {
|
||||
paused_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Resume() { return 0; }
|
||||
int ScreenCapturerX11::Resume(int monitor_index) {
|
||||
paused_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::OnFrame() {}
|
||||
int ScreenCapturerX11::SwitchTo(int monitor_index) {
|
||||
monitor_index_ = monitor_index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::CleanUp() {}
|
||||
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
|
||||
return display_info_list_;
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::OnFrame() {
|
||||
if (!display_) {
|
||||
LOG_ERROR("Display is not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (monitor_index_ < 0 || monitor_index_ >= display_info_list_.size()) {
|
||||
LOG_ERROR("Invalid monitor index: {}", monitor_index_.load());
|
||||
return;
|
||||
}
|
||||
|
||||
left_ = display_info_list_[monitor_index_].left;
|
||||
top_ = display_info_list_[monitor_index_].top;
|
||||
width_ = display_info_list_[monitor_index_].width;
|
||||
height_ = display_info_list_[monitor_index_].height;
|
||||
|
||||
XImage* image = XGetImage(display_, root_, left_, top_, width_, height_,
|
||||
AllPlanes, ZPixmap);
|
||||
if (!image) return;
|
||||
|
||||
bool needs_copy = image->bytes_per_line != width_ * 4;
|
||||
std::vector<uint8_t> argb_buf;
|
||||
uint8_t* src_argb = nullptr;
|
||||
|
||||
if (needs_copy) {
|
||||
argb_buf.resize(width_ * height_ * 4);
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line,
|
||||
width_ * 4);
|
||||
}
|
||||
src_argb = argb_buf.data();
|
||||
} else {
|
||||
src_argb = reinterpret_cast<uint8_t*>(image->data);
|
||||
}
|
||||
|
||||
libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_,
|
||||
uv_plane_.data(), width_, width_, height_);
|
||||
|
||||
std::vector<uint8_t> nv12;
|
||||
nv12.reserve(y_plane_.size() + uv_plane_.size());
|
||||
nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end());
|
||||
nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end());
|
||||
|
||||
if (callback_) {
|
||||
callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_,
|
||||
display_info_list_[monitor_index_].name.c_str());
|
||||
}
|
||||
|
||||
XDestroyImage(image);
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-05-07
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_X11_H_
|
||||
#define _SCREEN_CAPTURER_X11_H_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
class ScreenCapturerX11 : public ScreenCapturer {
|
||||
public:
|
||||
@@ -25,60 +26,39 @@ class ScreenCapturerX11 : public ScreenCapturer {
|
||||
~ScreenCapturerX11();
|
||||
|
||||
public:
|
||||
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb);
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Stop() override;
|
||||
|
||||
virtual int Destroy();
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
virtual int Start();
|
||||
int SwitchTo(int monitor_index) override;
|
||||
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
std::atomic_bool _running;
|
||||
std::atomic_bool _paused;
|
||||
std::atomic_bool _inited;
|
||||
Display* display_ = nullptr;
|
||||
Window root_ = 0;
|
||||
XRRScreenResources* screen_res_ = nullptr;
|
||||
int left_ = 0;
|
||||
int top_ = 0;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
std::thread thread_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> paused_{false};
|
||||
std::atomic<int> monitor_index_{0};
|
||||
int fps_ = 60;
|
||||
cb_desktop_data callback_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
|
||||
std::thread _thread;
|
||||
|
||||
std::string _device_name;
|
||||
|
||||
RECORD_DESKTOP_RECT _rect;
|
||||
|
||||
int _fps;
|
||||
|
||||
cb_desktop_data _on_data;
|
||||
|
||||
private:
|
||||
int i_ = 0;
|
||||
int videoindex_ = 0;
|
||||
int got_picture_ = 0;
|
||||
int fps_ = 0;
|
||||
bool inited_ = false;
|
||||
|
||||
// ffmpeg
|
||||
AVFormatContext *pFormatCtx_ = nullptr;
|
||||
AVCodecContext *pCodecCtx_ = nullptr;
|
||||
AVCodec *pCodec_ = nullptr;
|
||||
AVCodecParameters *pCodecParam_ = nullptr;
|
||||
AVDictionary *options_ = nullptr;
|
||||
AVInputFormat *ifmt_ = nullptr;
|
||||
AVFrame *pFrame_ = nullptr;
|
||||
AVFrame *pFrameNv12_ = nullptr;
|
||||
AVPacket *packet_ = nullptr;
|
||||
struct SwsContext *img_convert_ctx_ = nullptr;
|
||||
|
||||
// thread
|
||||
std::thread capture_thread_;
|
||||
std::atomic_bool running_;
|
||||
// 缓冲区
|
||||
std::vector<uint8_t> y_plane_;
|
||||
std::vector<uint8_t> uv_plane_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef _X11_SESSION_H_
|
||||
#define _X11_SESSION_H_
|
||||
|
||||
class X11Session {
|
||||
public:
|
||||
struct x11_session_frame {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int row_pitch;
|
||||
|
||||
const unsigned char *data;
|
||||
};
|
||||
|
||||
class x11_session_observer {
|
||||
public:
|
||||
virtual ~x11_session_observer() {}
|
||||
virtual void OnFrame(const x11_session_frame &frame) = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
virtual void Release() = 0;
|
||||
|
||||
virtual int Initialize() = 0;
|
||||
|
||||
virtual void RegisterObserver(x11_session_observer *observer) = 0;
|
||||
|
||||
virtual int Start() = 0;
|
||||
virtual int Stop() = 0;
|
||||
|
||||
virtual int Pause() = 0;
|
||||
virtual int Resume() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~X11Session(){};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,49 +0,0 @@
|
||||
#include "x11_session_impl.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#define CHECK_INIT \
|
||||
if (!is_initialized_) { \
|
||||
std::cout << "AE_NEED_INIT" << std::endl; \
|
||||
return 4; \
|
||||
}
|
||||
|
||||
X11SessionImpl::X11SessionImpl() {}
|
||||
|
||||
X11SessionImpl::~X11SessionImpl() {
|
||||
Stop();
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
void X11SessionImpl::Release() { delete this; }
|
||||
|
||||
int X11SessionImpl::Initialize() { return 0; }
|
||||
|
||||
void X11SessionImpl::RegisterObserver(x11_session_observer *observer) {
|
||||
observer_ = observer;
|
||||
}
|
||||
|
||||
int X11SessionImpl::Start() {
|
||||
if (is_running_) return 0;
|
||||
|
||||
int error = 1;
|
||||
|
||||
CHECK_INIT;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int X11SessionImpl::Stop() { return 0; }
|
||||
|
||||
int X11SessionImpl::Pause() { return 0; }
|
||||
|
||||
int X11SessionImpl::Resume() { return 0; }
|
||||
|
||||
void X11SessionImpl::OnFrame() {}
|
||||
|
||||
void X11SessionImpl::OnClosed() {}
|
||||
|
||||
void X11SessionImpl::CleanUp() {}
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef _WGC_SESSION_IMPL_H_
|
||||
#define _WGC_SESSION_IMPL_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "x11_session.h"
|
||||
|
||||
class X11SessionImpl : public X11Session {
|
||||
public:
|
||||
X11SessionImpl();
|
||||
~X11SessionImpl() override;
|
||||
|
||||
public:
|
||||
void Release() override;
|
||||
|
||||
int Initialize() override;
|
||||
|
||||
void RegisterObserver(x11_session_observer *observer) override;
|
||||
|
||||
int Start() override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause() override;
|
||||
int Resume() override;
|
||||
|
||||
private:
|
||||
void OnFrame();
|
||||
void OnClosed();
|
||||
|
||||
void CleanUp();
|
||||
|
||||
// void message_func();
|
||||
|
||||
private:
|
||||
std::mutex lock_;
|
||||
bool is_initialized_ = false;
|
||||
bool is_running_ = false;
|
||||
bool is_paused_ = false;
|
||||
|
||||
x11_session_observer *observer_ = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,212 +0,0 @@
|
||||
#include "screen_capturer_avf.h"
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
#define USE_SCALE_FACTOR 0
|
||||
|
||||
ScreenCapturerAvf::ScreenCapturerAvf() {}
|
||||
|
||||
ScreenCapturerAvf::~ScreenCapturerAvf() {
|
||||
if (inited_ && capture_thread_.joinable()) {
|
||||
capture_thread_.join();
|
||||
inited_ = false;
|
||||
}
|
||||
|
||||
if (nv12_frame_) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
}
|
||||
|
||||
if (pFormatCtx_) {
|
||||
avformat_close_input(&pFormatCtx_);
|
||||
pFormatCtx_ = nullptr;
|
||||
}
|
||||
|
||||
if (pCodecCtx_) {
|
||||
avcodec_free_context(&pCodecCtx_);
|
||||
pCodecCtx_ = nullptr;
|
||||
}
|
||||
|
||||
if (options_) {
|
||||
av_dict_free(&options_);
|
||||
options_ = nullptr;
|
||||
}
|
||||
|
||||
if (pFrame_) {
|
||||
av_frame_free(&pFrame_);
|
||||
pFrame_ = nullptr;
|
||||
}
|
||||
|
||||
if (packet_) {
|
||||
av_packet_free(&packet_);
|
||||
packet_ = nullptr;
|
||||
}
|
||||
|
||||
#if USE_SCALE_FACTOR
|
||||
if (img_convert_ctx_) {
|
||||
sws_freeContext(img_convert_ctx_);
|
||||
img_convert_ctx_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Init(const int fps, cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
_on_data = cb;
|
||||
}
|
||||
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
|
||||
pFormatCtx_ = avformat_alloc_context();
|
||||
|
||||
avdevice_register_all();
|
||||
|
||||
// grabbing frame rate
|
||||
av_dict_set(&options_, "framerate", "60", 0);
|
||||
av_dict_set(&options_, "pixel_format", "nv12", 0);
|
||||
// show remote cursor
|
||||
av_dict_set(&options_, "capture_cursor", "0", 0);
|
||||
// Make the grabbed area follow the mouse
|
||||
// av_dict_set(&options_, "follow_mouse", "centered", 0);
|
||||
// Video frame size. The default is to capture the full screen
|
||||
// av_dict_set(&options_, "video_size", "1440x900", 0);
|
||||
ifmt_ = (AVInputFormat *)av_find_input_format("avfoundation");
|
||||
if (!ifmt_) {
|
||||
printf("Couldn't find_input_format\n");
|
||||
}
|
||||
|
||||
// Grab at position 10,20
|
||||
if (avformat_open_input(&pFormatCtx_, "Capture screen 0", ifmt_, &options_) !=
|
||||
0) {
|
||||
printf("Couldn't open input stream.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
|
||||
printf("Couldn't find stream information.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
videoindex_ = -1;
|
||||
for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++)
|
||||
if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
videoindex_ = i_;
|
||||
break;
|
||||
}
|
||||
if (videoindex_ == -1) {
|
||||
printf("Didn't find a video stream.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar;
|
||||
|
||||
pCodecCtx_ = avcodec_alloc_context3(NULL);
|
||||
avcodec_parameters_to_context(pCodecCtx_, pCodecParam_);
|
||||
|
||||
pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id));
|
||||
if (pCodec_ == NULL) {
|
||||
printf("Codec not found.\n");
|
||||
return -1;
|
||||
}
|
||||
if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) {
|
||||
printf("Could not open codec.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width;
|
||||
const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height;
|
||||
|
||||
pFrame_ = av_frame_alloc();
|
||||
pFrame_->width = screen_w;
|
||||
pFrame_->height = screen_h;
|
||||
|
||||
#if USE_SCALE_FACTOR
|
||||
pFrame_resized_ = av_frame_alloc();
|
||||
pFrame_resized_->width = CGDisplayPixelsWide(CGMainDisplayID());
|
||||
pFrame_resized_->height = CGDisplayPixelsHigh(CGMainDisplayID());
|
||||
|
||||
img_convert_ctx_ =
|
||||
sws_getContext(pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt,
|
||||
pFrame_resized_->width, pFrame_resized_->height,
|
||||
AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
|
||||
#endif
|
||||
|
||||
if (!nv12_frame_) {
|
||||
nv12_frame_ = new unsigned char[screen_w * screen_h * 3 / 2];
|
||||
}
|
||||
|
||||
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
|
||||
|
||||
inited_ = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Destroy() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Start() {
|
||||
if (running_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
capture_thread_ = std::thread([this]() {
|
||||
while (running_) {
|
||||
if (av_read_frame(pFormatCtx_, packet_) >= 0) {
|
||||
if (packet_->stream_index == videoindex_) {
|
||||
avcodec_send_packet(pCodecCtx_, packet_);
|
||||
av_packet_unref(packet_);
|
||||
got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_);
|
||||
|
||||
if (!got_picture_) {
|
||||
#if USE_SCALE_FACTOR
|
||||
av_image_fill_arrays(pFrame_resized_->data,
|
||||
pFrame_resized_->linesize, nv12_frame_,
|
||||
AV_PIX_FMT_NV12, pFrame_resized_->width,
|
||||
pFrame_resized_->height, 1);
|
||||
|
||||
sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0,
|
||||
pFrame_->height, pFrame_resized_->data,
|
||||
pFrame_resized_->linesize);
|
||||
|
||||
_on_data((unsigned char *)nv12_frame_,
|
||||
pFrame_resized_->width * pFrame_resized_->height * 3 / 2,
|
||||
pFrame_resized_->width, pFrame_resized_->height);
|
||||
#else
|
||||
memcpy(nv12_frame_, pFrame_->data[0],
|
||||
pFrame_->linesize[0] * pFrame_->height);
|
||||
memcpy(nv12_frame_ + pFrame_->linesize[0] * pFrame_->height,
|
||||
pFrame_->data[1],
|
||||
pFrame_->linesize[1] * pFrame_->height / 2);
|
||||
_on_data((unsigned char *)nv12_frame_,
|
||||
pFrame_->width * pFrame_->height * 3 / 2, pFrame_->width,
|
||||
pFrame_->height);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Stop() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Pause() { return 0; }
|
||||
|
||||
int ScreenCapturerAvf::Resume() { return 0; }
|
||||
|
||||
void ScreenCapturerAvf::OnFrame() {}
|
||||
|
||||
void ScreenCapturerAvf::CleanUp() {}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-01
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_AVF_H_
|
||||
#define _SCREEN_CAPTURER_AVF_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
class ScreenCapturerAvf : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerAvf();
|
||||
~ScreenCapturerAvf();
|
||||
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb);
|
||||
|
||||
virtual int Destroy();
|
||||
|
||||
virtual int Start();
|
||||
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
std::atomic_bool _paused;
|
||||
std::atomic_bool _inited;
|
||||
|
||||
std::thread _thread;
|
||||
|
||||
std::string _device_name;
|
||||
|
||||
int _fps;
|
||||
|
||||
cb_desktop_data _on_data;
|
||||
|
||||
private:
|
||||
int i_ = 0;
|
||||
int videoindex_ = 0;
|
||||
int got_picture_ = 0;
|
||||
bool inited_ = false;
|
||||
|
||||
// ffmpeg
|
||||
AVFormatContext *pFormatCtx_ = nullptr;
|
||||
AVCodecContext *pCodecCtx_ = nullptr;
|
||||
AVCodec *pCodec_ = nullptr;
|
||||
AVCodecParameters *pCodecParam_ = nullptr;
|
||||
AVDictionary *options_ = nullptr;
|
||||
AVInputFormat *ifmt_ = nullptr;
|
||||
AVFrame *pFrame_ = nullptr;
|
||||
AVFrame *pFrame_resized_ = nullptr;
|
||||
AVPacket *packet_ = nullptr;
|
||||
struct SwsContext *img_convert_ctx_ = nullptr;
|
||||
unsigned char *nv12_frame_ = nullptr;
|
||||
|
||||
// thread
|
||||
std::thread capture_thread_;
|
||||
std::atomic_bool running_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,104 +0,0 @@
|
||||
#include <IOSurface/IOSurface.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "screen_capturer_cgd.h"
|
||||
|
||||
ScreenCapturerCg::ScreenCapturerCg() {}
|
||||
|
||||
ScreenCapturerCg::~ScreenCapturerCg() {}
|
||||
|
||||
int ScreenCapturerCg::Init(const int fps, cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
_on_data = cb;
|
||||
}
|
||||
|
||||
size_t pixel_width = 1280;
|
||||
size_t pixel_height = 720;
|
||||
CGDirectDisplayID display_id = 0;
|
||||
|
||||
CGDisplayStreamFrameAvailableHandler handler =
|
||||
^(CGDisplayStreamFrameStatus status, uint64_t display_time,
|
||||
IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef updateRef) {
|
||||
if (status == kCGDisplayStreamFrameStatusStopped) return;
|
||||
// Only pay attention to frame updates.
|
||||
if (status != kCGDisplayStreamFrameStatusFrameComplete) return;
|
||||
|
||||
// size_t count = 0;
|
||||
// const CGRect* rects = CGDisplayStreamUpdateGetRects(
|
||||
// updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
|
||||
|
||||
// 获取帧数据
|
||||
void* frameData = IOSurfaceGetBaseAddressOfPlane(frame_surface, 0);
|
||||
size_t width = IOSurfaceGetWidthOfPlane(frame_surface, 0);
|
||||
size_t height = IOSurfaceGetHeightOfPlane(frame_surface, 0);
|
||||
};
|
||||
|
||||
CFDictionaryRef properties_dictionary = CFDictionaryCreate(
|
||||
kCFAllocatorDefault, (const void*[]){kCGDisplayStreamShowCursor},
|
||||
(const void*[]){kCFBooleanFalse}, 1, &kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
CGDisplayStreamRef display_stream =
|
||||
CGDisplayStreamCreate(display_id, pixel_width, pixel_height, 'BGRA',
|
||||
properties_dictionary, handler);
|
||||
|
||||
if (display_stream) {
|
||||
CGError error = CGDisplayStreamStart(display_stream);
|
||||
if (error != kCGErrorSuccess) return -1;
|
||||
|
||||
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(display_stream);
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
||||
display_streams_.push_back(display_stream);
|
||||
}
|
||||
|
||||
CFRelease(properties_dictionary);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Destroy() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Start() {
|
||||
if (_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
capture_thread_ = std::thread([this]() {
|
||||
while (running_) {
|
||||
CFRunLoopRun();
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Stop() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Pause() { return 0; }
|
||||
|
||||
int ScreenCapturerCg::Resume() { return 0; }
|
||||
|
||||
void ScreenCapturerCg::OnFrame() {}
|
||||
|
||||
void ScreenCapturerCg::CleanUp() {}
|
||||
|
||||
//
|
||||
|
||||
void ScreenCapturerCg::UnregisterRefreshAndMoveHandlers() {
|
||||
for (CGDisplayStreamRef stream : display_streams_) {
|
||||
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(stream);
|
||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
||||
CGDisplayStreamStop(stream);
|
||||
CFRelease(stream);
|
||||
}
|
||||
display_streams_.clear();
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-10-16
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_CGD_H_
|
||||
#define _SCREEN_CAPTURER_CGD_H_
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
class ScreenCapturerCg : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerCg();
|
||||
~ScreenCapturerCg();
|
||||
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb);
|
||||
|
||||
virtual int Destroy();
|
||||
|
||||
virtual int Start();
|
||||
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
|
||||
int Resume();
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
int _fps;
|
||||
cb_desktop_data _on_data;
|
||||
|
||||
// thread
|
||||
std::thread capture_thread_;
|
||||
std::atomic_bool running_;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
ScreenCapturerSck::ScreenCapturerSck() {}
|
||||
ScreenCapturerSck::~ScreenCapturerSck() {
|
||||
// if (inited_ && capture_thread_.joinable()) {
|
||||
// capture_thread_.join();
|
||||
// inited_ = false;
|
||||
// }
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
on_data_ = cb;
|
||||
} else {
|
||||
LOG_ERROR("cb is null");
|
||||
return -1;
|
||||
}
|
||||
|
||||
screen_capturer_sck_impl_ = CreateScreenCapturerSck();
|
||||
screen_capturer_sck_impl_->Init(fps, on_data_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Destroy() { return 0; }
|
||||
|
||||
int ScreenCapturerSck::Start() {
|
||||
// if (running_) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// running_ = true;
|
||||
// capture_thread_ = std::thread([this]() {
|
||||
// while (running_) {
|
||||
// }
|
||||
// });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Stop() { return 0; }
|
||||
|
||||
int ScreenCapturerSck::Pause() { return 0; }
|
||||
|
||||
int ScreenCapturerSck::Resume() { return 0; }
|
||||
|
||||
void ScreenCapturerSck::OnFrame() {}
|
||||
|
||||
void ScreenCapturerSck::CleanUp() {}
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <IOSurface/IOSurface.h>
|
||||
#include <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
class ScreenCapturerSckImpl;
|
||||
|
||||
// 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.
|
||||
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
||||
// introduced in macOS 14.
|
||||
API_AVAILABLE(macos(14.0))
|
||||
@interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput>
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer;
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content;
|
||||
|
||||
// Called just before the capturer is destroyed. This avoids a dangling pointer,
|
||||
// and prevents any new calls into a deleted capturer. If any method-call on the
|
||||
// capturer is currently running on a different thread, this blocks until it
|
||||
// completes.
|
||||
- (void)releaseCapturer;
|
||||
@end
|
||||
|
||||
class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
||||
public:
|
||||
explicit ScreenCapturerSckImpl();
|
||||
|
||||
ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete;
|
||||
ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete;
|
||||
~ScreenCapturerSckImpl();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb);
|
||||
void OnReceiveContent(SCShareableContent *content);
|
||||
void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment);
|
||||
|
||||
virtual int Destroy() { return 0; }
|
||||
|
||||
virtual int Start() { return 0; }
|
||||
|
||||
virtual int Stop() { return 0; }
|
||||
|
||||
private:
|
||||
SckHelper *__strong helper_;
|
||||
SCStream *__strong stream_;
|
||||
|
||||
cb_desktop_data _on_data;
|
||||
unsigned char *nv12_frame_ = nullptr;
|
||||
bool permanent_error_ = false;
|
||||
CGDirectDisplayID current_display_ = -1;
|
||||
std::mutex mtx_;
|
||||
};
|
||||
|
||||
@implementation SckHelper {
|
||||
// This lock is to prevent the capturer being destroyed while an instance
|
||||
// method is still running on another thread.
|
||||
std::mutex helper_mtx_;
|
||||
ScreenCapturerSckImpl *_capturer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_capturer = capturer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content {
|
||||
std::lock_guard<std::mutex> lock(helper_mtx_);
|
||||
if (_capturer) {
|
||||
_capturer->OnReceiveContent(content);
|
||||
} else {
|
||||
LOG_ERROR("Invalid capturer");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stream:(SCStream *)stream
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
ofType:(SCStreamOutputType)type {
|
||||
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (!pixelBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
IOSurfaceRef ioSurface = CVPixelBufferGetIOSurface(pixelBuffer);
|
||||
if (!ioSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(
|
||||
sampleBuffer, /*createIfNecessary=*/false);
|
||||
if (!attachmentsArray || CFArrayGetCount(attachmentsArray) <= 0) {
|
||||
LOG_ERROR("Discarding frame with no attachments");
|
||||
return;
|
||||
}
|
||||
|
||||
CFDictionaryRef attachment =
|
||||
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0));
|
||||
|
||||
std::lock_guard<std::mutex> lock(helper_mtx_);
|
||||
if (_capturer) {
|
||||
_capturer->OnNewIOSurface(ioSurface, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)releaseCapturer {
|
||||
std::lock_guard<std::mutex> lock(helper_mtx_);
|
||||
_capturer = nullptr;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
|
||||
helper_ = [[SckHelper alloc] initWithCapturer:this];
|
||||
}
|
||||
|
||||
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
[helper_ releaseCapturer];
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||
_on_data = cb;
|
||||
|
||||
SckHelper *local_helper = helper_;
|
||||
auto handler = ^(SCShareableContent *content, NSError *error) {
|
||||
[local_helper onShareableContentCreated:content];
|
||||
};
|
||||
|
||||
[SCShareableContent getShareableContentWithCompletionHandler:handler];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnReceiveContent(SCShareableContent *content) {
|
||||
if (!content) {
|
||||
LOG_ERROR("getShareableContent failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.displays.count) {
|
||||
LOG_ERROR("getShareableContent returned no displays");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SCDisplay *captured_display;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
for (SCDisplay *display in content.displays) {
|
||||
if (current_display_ == display.displayID) {
|
||||
captured_display = display;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!captured_display) {
|
||||
if (-1 == current_display_) {
|
||||
LOG_ERROR("Full screen capture is not supported, falling back to first "
|
||||
"display");
|
||||
} else {
|
||||
LOG_ERROR("Display [{}] not found, falling back to first display",
|
||||
current_display_);
|
||||
}
|
||||
captured_display = content.displays.firstObject;
|
||||
}
|
||||
}
|
||||
|
||||
SCContentFilter *filter =
|
||||
[[SCContentFilter alloc] initWithDisplay:captured_display
|
||||
excludingWindows:@[]];
|
||||
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
|
||||
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
config.showsCursor = false;
|
||||
config.width = filter.contentRect.size.width * filter.pointPixelScale;
|
||||
config.height = filter.contentRect.size.height * filter.pointPixelScale;
|
||||
config.captureResolution = SCCaptureResolutionNominal;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
|
||||
if (stream_) {
|
||||
LOG_INFO("Updating stream configuration");
|
||||
[stream_ updateContentFilter:filter completionHandler:nil];
|
||||
[stream_ updateConfiguration:config completionHandler:nil];
|
||||
} else {
|
||||
stream_ = [[SCStream alloc] initWithFilter:filter
|
||||
configuration:config
|
||||
delegate:helper_];
|
||||
|
||||
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
|
||||
// best performance.
|
||||
NSError *add_stream_output_error;
|
||||
bool add_stream_output_result =
|
||||
[stream_ addStreamOutput:helper_
|
||||
type:SCStreamOutputTypeScreen
|
||||
sampleHandlerQueue:nil
|
||||
error:&add_stream_output_error];
|
||||
if (!add_stream_output_result) {
|
||||
stream_ = nil;
|
||||
LOG_ERROR("addStreamOutput failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = ^(NSError *error) {
|
||||
if (error) {
|
||||
// It should be safe to access `this` here, because the C++ destructor
|
||||
// calls stopCaptureWithCompletionHandler on the stream, which cancels
|
||||
// this handler.
|
||||
permanent_error_ = true;
|
||||
LOG_ERROR("startCaptureWithCompletionHandler failed");
|
||||
} else {
|
||||
LOG_INFO("Capture started");
|
||||
}
|
||||
};
|
||||
|
||||
[stream_ startCaptureWithCompletionHandler:handler];
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnNewIOSurface(IOSurfaceRef io_surface,
|
||||
CFDictionaryRef attachment) {
|
||||
size_t width = IOSurfaceGetWidth(io_surface);
|
||||
size_t height = IOSurfaceGetHeight(io_surface);
|
||||
|
||||
uint32_t aseed;
|
||||
IOSurfaceLock(io_surface, kIOSurfaceLockReadOnly, &aseed);
|
||||
|
||||
nv12_frame_ =
|
||||
static_cast<unsigned char *>(IOSurfaceGetBaseAddress(io_surface));
|
||||
|
||||
_on_data(nv12_frame_, width * height * 3 / 2, width, height);
|
||||
|
||||
IOSurfaceUnlock(io_surface, kIOSurfaceLockReadOnly, &aseed);
|
||||
}
|
||||
|
||||
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
|
||||
return std::make_unique<ScreenCapturerSckImpl>();
|
||||
}
|
||||
73
src/screen_capturer/macosx/screen_capturer_sck.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
ScreenCapturerSck::ScreenCapturerSck() {}
|
||||
ScreenCapturerSck::~ScreenCapturerSck() {}
|
||||
|
||||
int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
on_data_ = cb;
|
||||
} else {
|
||||
LOG_ERROR("cb is null");
|
||||
return -1;
|
||||
}
|
||||
|
||||
screen_capturer_sck_impl_ = CreateScreenCapturerSck();
|
||||
screen_capturer_sck_impl_->Init(fps, on_data_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Destroy() {
|
||||
if (screen_capturer_sck_impl_) {
|
||||
screen_capturer_sck_impl_->Destroy();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Start() {
|
||||
screen_capturer_sck_impl_->Start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Stop() {
|
||||
if (screen_capturer_sck_impl_) {
|
||||
screen_capturer_sck_impl_->Stop();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Pause(int monitor_index) {
|
||||
if (screen_capturer_sck_impl_) {
|
||||
return screen_capturer_sck_impl_->Pause(monitor_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Resume(int monitor_index) {
|
||||
if (screen_capturer_sck_impl_) {
|
||||
return screen_capturer_sck_impl_->Resume(monitor_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::SwitchTo(int monitor_index) {
|
||||
if (screen_capturer_sck_impl_) {
|
||||
return screen_capturer_sck_impl_->SwitchTo(monitor_index);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
|
||||
if (screen_capturer_sck_impl_) {
|
||||
return screen_capturer_sck_impl_->GetDisplayInfoList();
|
||||
}
|
||||
|
||||
return std::vector<DisplayInfo>();
|
||||
}
|
||||
|
||||
void ScreenCapturerSck::OnFrame() {}
|
||||
|
||||
void ScreenCapturerSck::CleanUp() {}
|
||||
@@ -22,17 +22,17 @@ class ScreenCapturerSck : public ScreenCapturer {
|
||||
~ScreenCapturerSck();
|
||||
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb);
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Stop() override;
|
||||
|
||||
virtual int Destroy();
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
virtual int Start();
|
||||
int SwitchTo(int monitor_index) override;
|
||||
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
|
||||
int Resume();
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||
|
||||
void OnFrame();
|
||||
|
||||
488
src/screen_capturer/macosx/screen_capturer_sck_impl.mm
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/graphics/IOGraphicsLib.h>
|
||||
#include <IOSurface/IOSurface.h>
|
||||
#include <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "display_info.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
static const int kFullDesktopScreenId = -1;
|
||||
class ScreenCapturerSckImpl;
|
||||
|
||||
// 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.
|
||||
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
||||
// introduced in macOS 14.
|
||||
API_AVAILABLE(macos(14.0))
|
||||
@interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput>
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer;
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content;
|
||||
|
||||
// Called just before the capturer is destroyed. This avoids a dangling pointer,
|
||||
// and prevents any new calls into a deleted capturer. If any method-call on the
|
||||
// capturer is currently running on a different thread, this blocks until it
|
||||
// completes.
|
||||
- (void)releaseCapturer;
|
||||
@end
|
||||
|
||||
class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
||||
public:
|
||||
explicit ScreenCapturerSckImpl();
|
||||
|
||||
ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete;
|
||||
ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete;
|
||||
~ScreenCapturerSckImpl();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
|
||||
int Start() override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
|
||||
int Destroy() override;
|
||||
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override { return 0; }
|
||||
|
||||
int Resume(int monitor_index) override { return 0; }
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; }
|
||||
|
||||
private:
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
std::map<int, CGDirectDisplayID> display_id_map_;
|
||||
std::map<CGDirectDisplayID, int> display_id_map_reverse_;
|
||||
std::map<CGDirectDisplayID, std::string> display_id_name_map_;
|
||||
unsigned char *nv12_frame_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int fps_ = 60;
|
||||
|
||||
public:
|
||||
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
|
||||
// nil if an error occurred. May run on an arbitrary thread.
|
||||
void OnShareableContentCreated(SCShareableContent *content);
|
||||
// Called by SckHelper to notify of a newly captured frame. May run on an arbitrary thread.
|
||||
// void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment);
|
||||
void OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, CFDictionaryRef attachment);
|
||||
|
||||
private:
|
||||
// Called when starting the capturer or the configuration has changed (either from a
|
||||
// SwitchTo() call, or the screen-resolution has changed). This tells SCK to fetch new
|
||||
// shareable content, and the completion-handler will either start a new stream, or reconfigure
|
||||
// the existing stream. Runs on the caller's thread.
|
||||
void StartOrReconfigureCapturer();
|
||||
// Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++
|
||||
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
|
||||
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
|
||||
SckHelper *__strong helper_;
|
||||
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
|
||||
// thread.
|
||||
cb_desktop_data _on_data = nullptr;
|
||||
// Signals that a permanent error occurred. This may be set on any thread, and is read by
|
||||
// CaptureFrame() which runs on the caller's thread.
|
||||
std::atomic<bool> permanent_error_ = false;
|
||||
// Guards some variables that may be accessed on different threads.
|
||||
std::mutex lock_;
|
||||
// Provides captured desktop frames.
|
||||
SCStream *__strong stream_;
|
||||
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
|
||||
// support full-desktop capture, and will fall back to the first display.
|
||||
CGDirectDisplayID current_display_ = 0;
|
||||
};
|
||||
|
||||
std::string GetDisplayName(CGDirectDisplayID display_id) {
|
||||
io_iterator_t iter;
|
||||
io_service_t serv = 0, matched_serv = 0;
|
||||
|
||||
CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");
|
||||
if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) {
|
||||
return "";
|
||||
}
|
||||
|
||||
while ((serv = IOIteratorNext(iter)) != 0) {
|
||||
CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
|
||||
if (info) {
|
||||
CFNumberRef vendorID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
|
||||
CFNumberRef productID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
|
||||
uint32_t vID = 0, pID = 0;
|
||||
if (vendorID && productID && CFNumberGetValue(vendorID, kCFNumberIntType, &vID) &&
|
||||
CFNumberGetValue(productID, kCFNumberIntType, &pID) &&
|
||||
vID == CGDisplayVendorNumber(display_id) && pID == CGDisplayModelNumber(display_id)) {
|
||||
matched_serv = serv;
|
||||
CFRelease(info);
|
||||
break;
|
||||
}
|
||||
CFRelease(info);
|
||||
}
|
||||
IOObjectRelease(serv);
|
||||
}
|
||||
IOObjectRelease(iter);
|
||||
|
||||
if (!matched_serv) return "";
|
||||
|
||||
CFDictionaryRef display_info =
|
||||
IODisplayCreateInfoDictionary(matched_serv, kIODisplayOnlyPreferredName);
|
||||
IOObjectRelease(matched_serv);
|
||||
if (!display_info) return "";
|
||||
|
||||
CFDictionaryRef product_name_dict =
|
||||
(CFDictionaryRef)CFDictionaryGetValue(display_info, CFSTR(kDisplayProductName));
|
||||
std::string result;
|
||||
if (product_name_dict) {
|
||||
CFIndex count = CFDictionaryGetCount(product_name_dict);
|
||||
if (count > 0) {
|
||||
std::vector<const void *> keys(count);
|
||||
std::vector<const void *> values(count);
|
||||
CFDictionaryGetKeysAndValues(product_name_dict, keys.data(), values.data());
|
||||
CFStringRef name_ref = (CFStringRef)values[0];
|
||||
if (name_ref) {
|
||||
CFIndex maxSize =
|
||||
CFStringGetMaximumSizeForEncoding(CFStringGetLength(name_ref), kCFStringEncodingUTF8) +
|
||||
1;
|
||||
std::vector<char> buffer(maxSize);
|
||||
if (CFStringGetCString(name_ref, buffer.data(), buffer.size(), kCFStringEncodingUTF8)) {
|
||||
result = buffer.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CFRelease(display_info);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
|
||||
helper_ = [[SckHelper alloc] initWithCapturer:this];
|
||||
}
|
||||
|
||||
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
||||
display_info_list_.clear();
|
||||
display_id_map_.clear();
|
||||
display_id_map_reverse_.clear();
|
||||
display_id_name_map_.clear();
|
||||
|
||||
if (nv12_frame_) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
}
|
||||
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
[helper_ releaseCapturer];
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||
_on_data = cb;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block SCShareableContent *content = nil;
|
||||
|
||||
[SCShareableContent
|
||||
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Failed to get shareable content: %@", error);
|
||||
} else {
|
||||
content = result;
|
||||
}
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (!content || content.displays.count == 0) {
|
||||
LOG_ERROR("Failed to get display info");
|
||||
return 0;
|
||||
}
|
||||
|
||||
CGDirectDisplayID displays[10];
|
||||
uint32_t count;
|
||||
CGGetActiveDisplayList(10, displays, &count);
|
||||
|
||||
int unnamed_count = 1;
|
||||
for (SCDisplay *display in content.displays) {
|
||||
CGDirectDisplayID display_id = display.displayID;
|
||||
CGRect bounds = CGDisplayBounds(display_id);
|
||||
bool is_primary = CGDisplayIsMain(display_id);
|
||||
|
||||
std::string name;
|
||||
name = GetDisplayName(display_id);
|
||||
|
||||
if (name.empty()) {
|
||||
name = "Display " + std::to_string(unnamed_count++);
|
||||
}
|
||||
|
||||
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 + bounds.size.width),
|
||||
static_cast<int>(bounds.origin.y + bounds.size.height));
|
||||
|
||||
display_info_list_.push_back(info);
|
||||
display_id_map_[display_info_list_.size() - 1] = display_id;
|
||||
display_id_map_reverse_[display_id] = display_info_list_.size() - 1;
|
||||
display_id_name_map_[display_id] = name;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Start() {
|
||||
StartOrReconfigureCapturer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
|
||||
if (stream_) {
|
||||
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) {
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
stream_ = nil;
|
||||
current_display_ = display_id_map_[monitor_index];
|
||||
StartOrReconfigureCapturer();
|
||||
}];
|
||||
} else {
|
||||
current_display_ = display_id_map_[monitor_index];
|
||||
StartOrReconfigureCapturer();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Destroy() {
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
if (stream_) {
|
||||
LOG_INFO("Destroying stream");
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
stream_ = nil;
|
||||
}
|
||||
current_display_ = 0;
|
||||
permanent_error_ = false;
|
||||
_on_data = nullptr;
|
||||
[helper_ releaseCapturer];
|
||||
helper_ = nil;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Stop() {
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
if (stream_) {
|
||||
LOG_INFO("Stopping stream");
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
stream_ = nil;
|
||||
}
|
||||
current_display_ = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *content) {
|
||||
if (!content) {
|
||||
LOG_ERROR("getShareableContent failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.displays.count) {
|
||||
LOG_ERROR("getShareableContent returned no displays");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SCDisplay *captured_display;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
for (SCDisplay *display in content.displays) {
|
||||
if (current_display_ == display.displayID) {
|
||||
LOG_WARN("current display: {}, name: {}", current_display_,
|
||||
display_id_name_map_[current_display_]);
|
||||
captured_display = display;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!captured_display) {
|
||||
captured_display = content.displays.firstObject;
|
||||
current_display_ = captured_display.displayID;
|
||||
}
|
||||
}
|
||||
|
||||
SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display
|
||||
excludingWindows:@[]];
|
||||
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
|
||||
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
config.showsCursor = false;
|
||||
config.width = filter.contentRect.size.width * filter.pointPixelScale;
|
||||
config.height = filter.contentRect.size.height * filter.pointPixelScale;
|
||||
config.captureResolution = SCCaptureResolutionAutomatic;
|
||||
config.minimumFrameInterval = CMTimeMake(1, fps_);
|
||||
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
|
||||
if (stream_) {
|
||||
LOG_INFO("Updating stream configuration");
|
||||
[stream_ updateContentFilter:filter completionHandler:nil];
|
||||
[stream_ updateConfiguration:config completionHandler:nil];
|
||||
} else {
|
||||
stream_ = [[SCStream alloc] initWithFilter:filter configuration:config delegate:helper_];
|
||||
|
||||
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
|
||||
// best performance.
|
||||
NSError *add_stream_output_error;
|
||||
dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL);
|
||||
bool add_stream_output_result = [stream_ addStreamOutput:helper_
|
||||
type:SCStreamOutputTypeScreen
|
||||
sampleHandlerQueue:queue
|
||||
error:&add_stream_output_error];
|
||||
|
||||
if (!add_stream_output_result) {
|
||||
stream_ = nil;
|
||||
LOG_ERROR("addStreamOutput failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = ^(NSError *error) {
|
||||
if (error) {
|
||||
// It should be safe to access `this` here, because the C++ destructor
|
||||
// calls stopCaptureWithCompletionHandler on the stream, which cancels
|
||||
// this handler.
|
||||
permanent_error_ = true;
|
||||
LOG_ERROR("startCaptureWithCompletionHandler failed");
|
||||
} else {
|
||||
LOG_INFO("Capture started");
|
||||
}
|
||||
};
|
||||
|
||||
[stream_ startCaptureWithCompletionHandler:handler];
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
||||
CFDictionaryRef attachment) {
|
||||
size_t width = CVPixelBufferGetWidth(pixelBuffer);
|
||||
size_t height = CVPixelBufferGetHeight(pixelBuffer);
|
||||
|
||||
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
if (status != kCVReturnSuccess) {
|
||||
LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t required_size = width * height * 3 / 2;
|
||||
if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = new unsigned char[required_size];
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
||||
|
||||
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
|
||||
|
||||
unsigned char *dst_y = nv12_frame_;
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width);
|
||||
}
|
||||
|
||||
unsigned char *dst_uv = nv12_frame_ + width * height;
|
||||
for (size_t row = 0; row < height / 2; ++row) {
|
||||
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
|
||||
}
|
||||
|
||||
_on_data(nv12_frame_, width * height * 3 / 2, width, height,
|
||||
display_id_name_map_[current_display_].c_str());
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
|
||||
// The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_`
|
||||
// inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is
|
||||
// deleted before the block is executed.
|
||||
SckHelper *local_helper = helper_;
|
||||
auto handler = ^(SCShareableContent *content, NSError *error) {
|
||||
[local_helper onShareableContentCreated:content];
|
||||
};
|
||||
[SCShareableContent getShareableContentWithCompletionHandler:handler];
|
||||
}
|
||||
|
||||
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
|
||||
return std::make_unique<ScreenCapturerSckImpl>();
|
||||
}
|
||||
|
||||
@implementation SckHelper {
|
||||
// This lock is to prevent the capturer being destroyed while an instance
|
||||
// method is still running on another thread.
|
||||
std::mutex _capturer_lock;
|
||||
ScreenCapturerSckImpl *_capturer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_capturer = capturer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content {
|
||||
std::lock_guard<std::mutex> lock(_capturer_lock);
|
||||
if (_capturer) {
|
||||
_capturer->OnShareableContentCreated(content);
|
||||
} else {
|
||||
LOG_ERROR("Invalid capturer");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stream:(SCStream *)stream
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
ofType:(SCStreamOutputType)type {
|
||||
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (!pixelBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFRetain(pixelBuffer);
|
||||
|
||||
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
|
||||
if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) {
|
||||
LOG_ERROR("Discarding frame with no attachments");
|
||||
CFRelease(pixelBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
CFDictionaryRef attachment =
|
||||
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0));
|
||||
|
||||
std::lock_guard<std::mutex> lock(_capturer_lock);
|
||||
if (_capturer) {
|
||||
_capturer->OnNewCVPixelBuffer(pixelBuffer, attachment);
|
||||
}
|
||||
|
||||
CFRelease(pixelBuffer);
|
||||
}
|
||||
|
||||
- (void)releaseCapturer {
|
||||
std::lock_guard<std::mutex> lock(_capturer_lock);
|
||||
_capturer = nullptr;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,9 +9,12 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "display_info.h"
|
||||
|
||||
class ScreenCapturer {
|
||||
public:
|
||||
typedef std::function<void(unsigned char *, int, int, int)> cb_desktop_data;
|
||||
typedef std::function<void(unsigned char*, int, int, int, const char*)>
|
||||
cb_desktop_data;
|
||||
|
||||
public:
|
||||
virtual ~ScreenCapturer() {}
|
||||
@@ -19,10 +22,13 @@ class ScreenCapturer {
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb) = 0;
|
||||
virtual int Destroy() = 0;
|
||||
|
||||
virtual int Start() = 0;
|
||||
|
||||
virtual int Stop() = 0;
|
||||
virtual int Pause(int monitor_index) = 0;
|
||||
virtual int Resume(int monitor_index) = 0;
|
||||
|
||||
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
|
||||
virtual int SwitchTo(int monitor_index) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||