mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-28 20:06:14 +08:00
Compare commits
349 Commits
linux_remo
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
418ab7a1d2 | ||
|
|
6a2c9af316 | ||
|
|
eaabf478cc | ||
|
|
ffe3ca76af | ||
|
|
c5a6302220 | ||
|
|
9b2f81690f | ||
|
|
9621e6b570 | ||
|
|
ce3ae03bef | ||
|
|
e0457213ea | ||
|
|
10cdc440a0 | ||
|
|
ddc62c90bb | ||
|
|
9e70d0e8fc | ||
|
|
4533d53ba8 | ||
|
|
0caa243006 | ||
|
|
1e58abdfdd | ||
|
|
370ac08d09 | ||
|
|
31b6b2736c | ||
|
|
abd22ab7f1 | ||
|
|
5ab68988aa | ||
|
|
ba3edcc02a | ||
|
|
8414a57a5b | ||
|
|
3f777e4662 | ||
|
|
52828183a1 | ||
|
|
df7489f8e2 | ||
|
|
4fea7d86e1 | ||
|
|
cb17b7c8db | ||
|
|
cf7ef89bf2 | ||
|
|
2d2a578800 | ||
|
|
0ba12f3ccf | ||
|
|
5ac603977d | ||
|
|
25d5a80bee | ||
|
|
c9d452a025 | ||
|
|
8132d62c02 | ||
|
|
ca32ebeefe | ||
|
|
5bf5e9ee25 | ||
|
|
bf097008e7 | ||
|
|
59b1208321 | ||
|
|
84194188f8 | ||
|
|
a067441fb9 | ||
|
|
6eac8380b6 | ||
|
|
5aed8317ca | ||
|
|
9aed7a19cf | ||
|
|
c0154be1aa | ||
|
|
f9d024e971 | ||
|
|
526eb4bb31 | ||
|
|
f9347cbd49 | ||
|
|
b6671bdbe7 | ||
|
|
edcf5d408c | ||
|
|
8c8731909e | ||
|
|
de721ac6e3 | ||
|
|
963f1da1d8 | ||
|
|
4c6159e4d4 | ||
|
|
e3c2e9ec6d | ||
|
|
02022bdcdf | ||
|
|
19ea426efc | ||
|
|
863070a8a7 | ||
|
|
44f9e6a8c9 | ||
|
|
087d5d7e52 | ||
|
|
26fa53f867 | ||
|
|
d18af6cbc6 | ||
|
|
b5bb62bd22 | ||
|
|
9ed3ab9929 | ||
|
|
dca18762e0 | ||
|
|
fed7c3b103 | ||
|
|
d246b7a04d | ||
|
|
a49ca813e0 | ||
|
|
0c688efaee | ||
|
|
be3561d46f | ||
|
|
c3af40a3f0 | ||
|
|
d493b9a131 | ||
|
|
4e4e84ae4d | ||
|
|
fea545e5e7 | ||
|
|
9096769a85 | ||
|
|
04ab157ecb | ||
|
|
2331f08283 | ||
|
|
9f8f99f21b | ||
|
|
56dadb6a49 | ||
|
|
59c9ca8d53 | ||
|
|
f16a4e8aa2 | ||
|
|
890615e13a | ||
|
|
2f72e3957e | ||
|
|
1292018f51 | ||
|
|
8ae9513104 | ||
|
|
c1efe2f4ac | ||
|
|
1210a0b631 | ||
|
|
39863c597e | ||
|
|
8a964f0030 | ||
|
|
74e29f25bf | ||
|
|
1e5bea2b1e | ||
|
|
d8297ebb74 | ||
|
|
93d7f71cf2 | ||
|
|
887a217828 | ||
|
|
89b12136e4 | ||
|
|
def7025abf | ||
|
|
35af5aab43 | ||
|
|
9ea67df0fd | ||
|
|
72fda8a728 | ||
|
|
070b48d7a7 | ||
|
|
6168009cef | ||
|
|
06a7243ac1 | ||
|
|
c8602b0d89 | ||
|
|
e3c730fd5f | ||
|
|
b252cb6ddd | ||
|
|
8ca1e8e5a1 | ||
|
|
a7d45b78c8 | ||
|
|
018231eee4 | ||
|
|
4704d494ec | ||
|
|
65927c2091 | ||
|
|
574b9d10ab | ||
|
|
ff510a3b44 | ||
|
|
4d3c864950 | ||
|
|
2cde54cf30 | ||
|
|
d1f3d11318 | ||
|
|
436228946b | ||
|
|
2b4083ee10 | ||
|
|
e3abb4e3de | ||
|
|
4b7cd1005b | ||
|
|
03ea96096d | ||
|
|
0ea8916426 | ||
|
|
43b36eb893 | ||
|
|
03b6a187b3 | ||
|
|
664412dd4e | ||
|
|
b37e08a202 | ||
|
|
a05d72ec67 | ||
|
|
f77e9fe6a8 | ||
|
|
1f9614e060 | ||
|
|
50d92a763a | ||
|
|
ec23656334 | ||
|
|
880c2949c3 | ||
|
|
07f5fe81c8 | ||
|
|
5a992b6589 | ||
|
|
8e03e8e79b | ||
|
|
ceb3d9fe20 | ||
|
|
0dc0b87bc4 | ||
|
|
3a4284fece | ||
|
|
502a90f121 | ||
|
|
88cd4aca4a | ||
|
|
3395004f93 | ||
|
|
e4c05e1f4d | ||
|
|
d17c70c2c8 | ||
|
|
7b42923418 | ||
|
|
5b6bdee25a | ||
|
|
05deb73c29 | ||
|
|
3685acc549 | ||
|
|
8f5a53937a | ||
|
|
b9c5db41ab | ||
|
|
a99a4230af | ||
|
|
f446154747 | ||
|
|
5a1e2c5ed9 | ||
|
|
ff6f295fac | ||
|
|
3111b3a641 | ||
|
|
20bb13ce85 | ||
|
|
5aa05f3a13 | ||
|
|
c911aa2eb1 | ||
|
|
d0cd2fe9ab | ||
|
|
9702805331 | ||
|
|
872152f1be | ||
|
|
b822221d7f | ||
|
|
95ad605b36 | ||
|
|
af32e25149 | ||
|
|
e63b384d1e | ||
|
|
7f25f7426c | ||
|
|
eed93ea953 | ||
|
|
b5f8e92526 | ||
|
|
af04b0571e | ||
|
|
75452a3e76 | ||
|
|
3f717f1df2 | ||
|
|
ad6f2c2c70 | ||
|
|
8076e7f662 | ||
|
|
be78496992 | ||
|
|
a3f745d441 | ||
|
|
e693d920d3 | ||
|
|
0f1b89eda9 | ||
|
|
172b8836fd | ||
|
|
71178ffa33 | ||
|
|
95a014a601 | ||
|
|
34d6bac345 | ||
|
|
399785409c | ||
|
|
5f1d9b6912 | ||
|
|
d963a0cf38 | ||
|
|
f9c1bc48b4 | ||
|
|
2906d05a4b | ||
|
|
053a0f86ad | ||
|
|
6c2363b239 | ||
|
|
167514fed8 | ||
|
|
342eb0c386 | ||
|
|
52c7099dbe | ||
|
|
12faf7cd2d | ||
|
|
6d921a3309 | ||
|
|
5a690ebbb6 | ||
|
|
4b3839aa34 | ||
|
|
efb165b56f | ||
|
|
0047b4ecc5 | ||
|
|
844710af7c | ||
|
|
562d54090a | ||
|
|
f7fd37651e | ||
|
|
280f59f97d | ||
|
|
0683ad9d27 | ||
|
|
e061e3b4d7 | ||
|
|
eaedcb8d06 | ||
|
|
e7e6380adc | ||
|
|
1f50483b50 | ||
|
|
6f703c8267 | ||
|
|
d150c374b5 | ||
|
|
f29b2ee09d | ||
|
|
0a934e8c01 | ||
|
|
2163aa87d4 | ||
|
|
5d8408d892 | ||
|
|
93d0e3a5d0 | ||
|
|
b4a5e91bc9 | ||
|
|
759078ef7f | ||
|
|
905539a6eb | ||
|
|
f1512812ad | ||
|
|
5f1cf89649 | ||
|
|
f291ad189a | ||
|
|
8807636372 | ||
|
|
70be1d8afc | ||
|
|
1f76aa427d | ||
|
|
134cbf8b75 | ||
|
|
669b944cfd | ||
|
|
9962829885 | ||
|
|
1393615f01 | ||
|
|
d58ae3a6b1 | ||
|
|
a188729af6 | ||
|
|
422478bd9a | ||
|
|
d8980f0082 | ||
|
|
e88bb017fa | ||
|
|
87466d6074 | ||
|
|
fbbbfc5e6a | ||
|
|
d2cefd1827 | ||
|
|
a350e06529 | ||
|
|
8c742ffa08 | ||
|
|
475005b8a4 | ||
|
|
4da5188759 | ||
|
|
d8df4df5ae |
186
.github/workflows/release.yaml
vendored
Normal file
186
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
name: Build and Release CrossDesk
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [release]
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
# Linux
|
||||
build-linux:
|
||||
name: Build on Ubuntu
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: crossdesk/ubuntu22.04:latest
|
||||
options: --user root
|
||||
steps:
|
||||
- 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 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_x86_64.sh
|
||||
./scripts/linux/pkg_x86_64.sh
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crossdesk-linux-x86_64
|
||||
path: ${{ github.workspace }}/CrossDesk-0.0.1.deb
|
||||
|
||||
# macOS
|
||||
build-macos:
|
||||
name: Build on macOS
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
runner: macos-13
|
||||
cache-key: intel
|
||||
out-dir: ./build/macosx/x86_64/release/crossdesk
|
||||
artifact-name: crossdesk-macos-x86_64
|
||||
package_script: ./scripts/macosx/pkg_x86_64.sh
|
||||
- arch: arm64
|
||||
runner: macos-14
|
||||
cache-key: arm
|
||||
out-dir: ./build/macosx/arm64/release/crossdesk
|
||||
artifact-name: crossdesk-macos-arm64
|
||||
package_script: ./scripts/macosx/pkg_arm64.sh
|
||||
steps:
|
||||
- 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 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 }}
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: crossdesk-macos-${{ matrix.arch }}-v0.0.1.pkg
|
||||
|
||||
- name: Move files to release dir
|
||||
run: |
|
||||
mkdir -p release
|
||||
cp crossdesk-macos-${{ matrix.arch }}-v0.0.1.pkg release/
|
||||
|
||||
# Windows
|
||||
build-windows:
|
||||
name: Build on Windows
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
XMAKE_GLOBALDIR: D:\xmake_global
|
||||
steps:
|
||||
- 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: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Build CrossDesk
|
||||
run: 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
|
||||
run: |
|
||||
cd ./scripts/windows
|
||||
makensis nsis_script.nsi
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crossdesk-win-x86_64
|
||||
path: ${{ github.workspace }}/scripts/windows/CrossDesk-0.0.1.exe
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "thirdparty/projectx"]
|
||||
path = thirdparty/projectx
|
||||
url = git@github.com:dijunkun/projectx.git
|
||||
[submodule "thirdparty/minirtc"]
|
||||
path = thirdparty/minirtc
|
||||
url = https://github.com/kunkundi/minirtc.git
|
||||
|
||||
39
Info.plist
Normal file
39
Info.plist
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
24
README.md
24
README.md
@@ -1,46 +1,46 @@
|
||||
# Continuous Desk
|
||||
# CrossDesk
|
||||
|
||||
#### More than remote desktop
|
||||
|
||||
----
|
||||
[Chinese](README_CN.md) / [English](README.md)
|
||||
[中文](README_CN.md) / [English](README.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 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.
|
||||
|
||||
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 softwar/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 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/)).
|
||||
|
||||
## Usage
|
||||
|
||||
Enter the remote desktop ID in the 'REMOTE ID' field on the menu bar, and click 'Connect' button to initiate the remote connection.
|
||||
|
||||

|
||||

|
||||
|
||||
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<EFBFBD><EFBFBD>
|
||||
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<EFBFBD><EFBFBD>
|
||||
Following packages need to be installed on 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
|
||||
```
|
||||
|
||||
Commands
|
||||
Commands:
|
||||
```
|
||||
git clone https://github.com/dijunkun/continuous-desk
|
||||
|
||||
@@ -52,7 +52,7 @@ git submodule update
|
||||
|
||||
xmake b remote_desk
|
||||
```
|
||||
Run
|
||||
Run:
|
||||
```
|
||||
# Windows/MacOS
|
||||
xmake r remote_desk
|
||||
|
||||
42
README_CN.md
42
README_CN.md
@@ -1,47 +1,47 @@
|
||||
# Continuous Desk
|
||||
|
||||
#### <EFBFBD><EFBFBD>ֹԶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
#### 不止远程桌面
|
||||
|
||||
----
|
||||
[English](README.md) / [<EFBFBD><EFBFBD><EFBFBD><EFBFBD>](README_CN.md)
|
||||
[English](README.md) / [中文](README_CN.md)
|
||||
|
||||

|
||||

|
||||
|
||||
## <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
## 简介
|
||||
|
||||
Continuous Desk <EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD>ƽ̨Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>ͬһʱ<EFBFBD><EFBFBD>Զ<EFBFBD>̲ٿ<EFBFBD>ͬһ̨<EFBFBD><EFBFBD><EFBFBD>ԡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>⣬<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֧<EFBFBD>ֶ˵<EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䣬<EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Э<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
Continuous Desk 是一个轻量级的跨平台远程桌面软件。它允许多个用户在同一时间远程操控同一台电脑。除桌面图像传输外,它还支持端到端的语音传输,在远程桌面基础上提供额外的协作能力。
|
||||
|
||||
Continuous Desk <EFBFBD><EFBFBD> [Projectx](https://github.com/dijunkun/projectx) ʵʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD>á<EFBFBD>Projectx <20><>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD>ƽ̨ʵʱ<CAB5><CAB1><EFBFBD><EFBFBD>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD><EFBFBD>⡣<EFBFBD><E2A1A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><CDB8><EFBFBD><EFBFBD>[RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD>Ӳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>루H264<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>루[Opus](https://github.com/xiph/opus)<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƣ<EFBFBD>[TCP over UDP](https://libnice.freedesktop.org/)<EFBFBD><EFBFBD><EFBFBD>Ȼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
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/))等基础能力。
|
||||
|
||||
|
||||
## ʹ<EFBFBD><EFBFBD>
|
||||
## 使用
|
||||
|
||||
<EFBFBD>ڲ˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>REMOTE ID<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ID<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Connect<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɷ<EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӡ<EFBFBD>
|
||||
在菜单栏“REMOTE ID”处输入远端桌面的ID,点击“Connect”即可发起远程连接。
|
||||
|
||||

|
||||

|
||||
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>룬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD>ȷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܳɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>״̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֡<EFBFBD>Incorrect password<EFBFBD><EFBFBD><EFBFBD>澯<EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>
|
||||
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。密码错误时,状态栏会出现“Incorrect password”告警提示。
|
||||
|
||||

|
||||

|
||||
|
||||
<EFBFBD><EFBFBD><EFBFBD>ӳɹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD>ClientConnected<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
连接成功建立后,状态栏会有“ClientConnected”相关字样。
|
||||
|
||||

|
||||

|
||||
|
||||
## <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
## 编译
|
||||
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
依赖:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
- [vcpkg](https://vcpkg.io/en/getting-started)
|
||||
|
||||
Linux<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>谲װ<EFBFBD><EFBFBD><EFBFBD>°<EFBFBD><EFBFBD><EFBFBD>
|
||||
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
|
||||
```
|
||||
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
编译命令
|
||||
```
|
||||
git clone https://github.com/dijunkun/continuous-desk
|
||||
|
||||
@@ -53,15 +53,15 @@ git submodule update
|
||||
|
||||
xmake b remote_desk
|
||||
```
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
运行
|
||||
```
|
||||
# Windows/MacOS
|
||||
xmake r remote_desk
|
||||
|
||||
# Linux<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD>rootȨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
# Linux下需使用root权限运行
|
||||
./remote_desk
|
||||
```
|
||||
|
||||
## <EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤
|
||||
## 许可证
|
||||
|
||||
Continuous Desk ʹ<EFBFBD><EFBFBD> MIT <EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD><EFBFBD>ĵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD>зַ<EFBFBD><EFBFBD><EFBFBD>
|
||||
Continuous Desk 使用 MIT 许可证,其中使用到的第三方库根据自身许可证进行分发。
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
[signal server]
|
||||
ip = 120.77.216.215
|
||||
port = 9099
|
||||
|
||||
[stun server]
|
||||
ip = 120.77.216.215
|
||||
port = 3478
|
||||
|
||||
[turn server]
|
||||
ip = 120.77.216.215
|
||||
port = 3478
|
||||
username = dijunkun
|
||||
password = dijunkunpw
|
||||
|
||||
[hardware acceleration]
|
||||
turn_on = true
|
||||
2348
fonts/OPPOSans_Regular.h
Normal file
2348
fonts/OPPOSans_Regular.h
Normal file
File diff suppressed because it is too large
Load Diff
5668
fonts/fa_regular_400.h
Normal file
5668
fonts/fa_regular_400.h
Normal file
File diff suppressed because it is too large
Load Diff
35041
fonts/fa_solid_900.h
Normal file
35041
fonts/fa_solid_900.h
Normal file
File diff suppressed because it is too large
Load Diff
BIN
icon/捕获.PNG
BIN
icon/捕获.PNG
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
BIN
icon/捕获.ico
BIN
icon/捕获.ico
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB |
1
icons/app.rc
Normal file
1
icons/app.rc
Normal file
@@ -0,0 +1 @@
|
||||
IDI_ICON1 ICON "app_icon.ico"
|
||||
BIN
icons/app_icon.ico
Normal file
BIN
icons/app_icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 687 B |
BIN
icons/crossdesk.icns
Normal file
BIN
icons/crossdesk.icns
Normal file
Binary file not shown.
BIN
icons/crossdesk.ico
Normal file
BIN
icons/crossdesk.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 687 B |
BIN
icons/crossdesk.png
Normal file
BIN
icons/crossdesk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 840 B |
126
scripts/linux/pkg_x86_64.sh
Normal file
126
scripts/linux/pkg_x86_64.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 配置变量
|
||||
APP_NAME="CrossDesk"
|
||||
APP_VERSION="0.0.1"
|
||||
ARCHITECTURE="amd64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
# 目录结构
|
||||
DEB_DIR="$APP_NAME-$APP_VERSION"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/local/bin"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$APP_NAME/certs" # 用于中转安装时分发
|
||||
ICON_DIR="$DEB_DIR/usr/share/icons/hicolor/256x256/apps"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
# 清理已有的打包文件夹
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
# 创建目录结构
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$ICON_DIR" "$DESKTOP_DIR"
|
||||
|
||||
# 复制二进制文件
|
||||
cp build/linux/x86_64/release/crossdesk "$BIN_DIR"
|
||||
|
||||
# 复制证书文件(将来通过 postinst 拷贝到每个用户 XDG_CONFIG_HOME)
|
||||
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
|
||||
|
||||
# 复制图标文件
|
||||
cp icons/crossdesk.png "$ICON_DIR/crossdesk.png"
|
||||
|
||||
# 设置可执行权限
|
||||
chmod +x "$BIN_DIR/crossdesk"
|
||||
|
||||
# 创建 control 文件
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $APP_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, nvidia-cuda-toolkit
|
||||
Priority: optional
|
||||
Section: utils
|
||||
EOF
|
||||
|
||||
# 创建 desktop 文件
|
||||
cat > "$DESKTOP_DIR/$APP_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/local/bin/crossdesk
|
||||
Icon=crossdesk
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
# 创建卸载脚本 postrm
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
# post-removal script for $APP_NAME
|
||||
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/local/bin/crossdesk
|
||||
rm -f /usr/share/icons/hicolor/256x256/apps/crossdesk.png
|
||||
rm -f /usr/share/applications/$APP_NAME.desktop
|
||||
rm -rf /opt/$APP_NAME
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
# 创建安装后脚本 postinst(拷贝证书到每个用户 XDG_CONFIG_HOME)
|
||||
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"
|
||||
cp "$CERT_SRC/$CERT_FILE" "$target"
|
||||
chown -R "$username:$username" "$user_home/.config/CrossDesk"
|
||||
echo "✔ Installed cert for $username at $target"
|
||||
fi
|
||||
done
|
||||
|
||||
# 处理 root 用户(可选)
|
||||
if [ -d "/root" ]; then
|
||||
config_dir="/root/.config/CrossDesk/certs"
|
||||
mkdir -p "$config_dir"
|
||||
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE"
|
||||
chown -R root:root /root/.config/CrossDesk
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
# 构建 .deb 包
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
# 清理构建目录
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package for $APP_NAME created successfully."
|
||||
152
scripts/macosx/pkg_arm64.sh
Normal file
152
scripts/macosx/pkg_arm64.sh
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/bin/bash
|
||||
set -e # 遇错退出
|
||||
|
||||
# === 配置变量 ===
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk" # 这个变量用来指定大写的应用名
|
||||
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk" # 可执行文件路径
|
||||
APP_VERSION="0.0.1"
|
||||
PLATFORM="macos"
|
||||
ARCH="arm64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="./icons/crossdesk.icns" # .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}-v${APP_VERSION}.pkg" # 保持安装包名称小写
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
# === 清理旧文件 ===
|
||||
echo "🧹 清理旧文件..."
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
|
||||
# === 创建 .app 结构 ===
|
||||
echo "📦 创建 ${APP_BUNDLE}..."
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
echo "🚚 拷贝可执行文件..."
|
||||
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>"
|
||||
echo "🎨 图标添加完成"
|
||||
else
|
||||
ICON_KEY=""
|
||||
echo "⚠️ 未找到图标文件,跳过图标设置"
|
||||
fi
|
||||
|
||||
# === 生成 Info.plist ===
|
||||
echo "📝 生成 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 创建完成"
|
||||
|
||||
# === 构建应用组件包 ===
|
||||
echo "📦 构建应用组件包..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
# === 构建 certs 组件包 ===
|
||||
# 先创建脚本目录和脚本文件
|
||||
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
|
||||
|
||||
# 构建 certs 组件包,增加 --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
|
||||
|
||||
# === 组合产品包 ===
|
||||
echo "🏗️ 组合最终安装包..."
|
||||
productbuild \
|
||||
--package build_pkg_temp/${APP_NAME}-component.pkg \
|
||||
--package build_pkg_temp/${APP_NAME}-certs.pkg \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "✅ 生成安装包完成:${PKG_NAME}"
|
||||
|
||||
# === 可选:打包成 DMG ===
|
||||
echo "📦 可选打包成 DMG..."
|
||||
mkdir -p CrossDesk_dmg_temp
|
||||
cp "${PKG_NAME}" CrossDesk_dmg_temp/
|
||||
ln -s /Applications CrossDesk_dmg_temp/Applications
|
||||
|
||||
hdiutil create -volname "${VOL_NAME}" \
|
||||
-srcfolder CrossDesk_dmg_temp \
|
||||
-ov -format UDZO "${DMG_NAME}"
|
||||
|
||||
rm -rf CrossDesk_dmg_temp build_pkg_temp scripts ${APP_BUNDLE} ${DMG_NAME}
|
||||
|
||||
echo "🎉 所有打包完成:"
|
||||
echo " ✔️ 应用:${APP_BUNDLE}"
|
||||
echo " ✔️ 安装包:${PKG_NAME}"
|
||||
echo " ✔️ 镜像包(可选):${DMG_NAME}"
|
||||
152
scripts/macosx/pkg_x86_64.sh
Normal file
152
scripts/macosx/pkg_x86_64.sh
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/bin/bash
|
||||
set -e # 遇错退出
|
||||
|
||||
# === 配置变量 ===
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk" # 这个变量用来指定大写的应用名
|
||||
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk" # 可执行文件路径
|
||||
APP_VERSION="0.0.1"
|
||||
PLATFORM="macos"
|
||||
ARCH="x86_64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/crossdesk.icns" # .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}-v${APP_VERSION}.pkg" # 保持安装包名称小写
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
# === 清理旧文件 ===
|
||||
echo "🧹 清理旧文件..."
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
|
||||
# === 创建 .app 结构 ===
|
||||
echo "📦 创建 ${APP_BUNDLE}..."
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
echo "🚚 拷贝可执行文件..."
|
||||
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>"
|
||||
echo "🎨 图标添加完成"
|
||||
else
|
||||
ICON_KEY=""
|
||||
echo "⚠️ 未找到图标文件,跳过图标设置"
|
||||
fi
|
||||
|
||||
# === 生成 Info.plist ===
|
||||
echo "📝 生成 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 创建完成"
|
||||
|
||||
# === 构建应用组件包 ===
|
||||
echo "📦 构建应用组件包..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
# === 构建 certs 组件包 ===
|
||||
# 先创建脚本目录和脚本文件
|
||||
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
|
||||
|
||||
# 构建 certs 组件包,增加 --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
|
||||
|
||||
# === 组合产品包 ===
|
||||
echo "🏗️ 组合最终安装包..."
|
||||
productbuild \
|
||||
--package build_pkg_temp/${APP_NAME}-component.pkg \
|
||||
--package build_pkg_temp/${APP_NAME}-certs.pkg \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "✅ 生成安装包完成:${PKG_NAME}"
|
||||
|
||||
# === 可选:打包成 DMG ===
|
||||
echo "📦 可选打包成 DMG..."
|
||||
mkdir -p CrossDesk_dmg_temp
|
||||
cp "${PKG_NAME}" CrossDesk_dmg_temp/
|
||||
ln -s /Applications CrossDesk_dmg_temp/Applications
|
||||
|
||||
hdiutil create -volname "${VOL_NAME}" \
|
||||
-srcfolder CrossDesk_dmg_temp \
|
||||
-ov -format UDZO "${DMG_NAME}"
|
||||
|
||||
rm -rf CrossDesk_dmg_temp build_pkg_temp scripts ${APP_BUNDLE} ${DMG_NAME}
|
||||
|
||||
echo "🎉 所有打包完成:"
|
||||
echo " ✔️ 应用:${APP_BUNDLE}"
|
||||
echo " ✔️ 安装包:${PKG_NAME}"
|
||||
echo " ✔️ 镜像包(可选):${DMG_NAME}"
|
||||
102
scripts/windows/nsis_script.nsi
Normal file
102
scripts/windows/nsis_script.nsi
Normal file
@@ -0,0 +1,102 @@
|
||||
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>
|
||||
!addincludedir "${__FILEDIR__}"
|
||||
|
||||
; <20><>װ<EFBFBD><D7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC><EFBFBD>峣<EFBFBD><E5B3A3>
|
||||
!define PRODUCT_NAME "CrossDesk"
|
||||
!define PRODUCT_VERSION "0.0.1"
|
||||
!define PRODUCT_PUBLISHER "CrossDesk"
|
||||
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
|
||||
!define APP_NAME "CrossDesk"
|
||||
!define UNINSTALL_REG_KEY "CrossDesk"
|
||||
|
||||
; <20><><EFBFBD>ð<EFBFBD>װ<EFBFBD><D7B0>ͼ<EFBFBD><CDBC>·<EFBFBD><C2B7>
|
||||
!define MUI_ICON "${__FILEDIR__}\..\..\icons\crossdesk.ico"
|
||||
|
||||
; <20><><EFBFBD><EFBFBD>֤<EFBFBD><D6A4>·<EFBFBD><C2B7>
|
||||
!define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt"
|
||||
|
||||
; ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
SetCompressor /FINAL lzma
|
||||
|
||||
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԱȨ<D4B1>ޣ<EFBFBD>д<EFBFBD><D0B4>HKLM<4C><4D>Ҫ<EFBFBD><D2AA>
|
||||
RequestExecutionLevel admin
|
||||
|
||||
; ------ MUI <20>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD>涨<EFBFBD><E6B6A8> ------
|
||||
!include "MUI.nsh"
|
||||
!define MUI_ABORTWARNING
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
|
||||
; ------ MUI <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ------
|
||||
|
||||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||
OutFile "CrossDesk-${PRODUCT_VERSION}.exe"
|
||||
InstallDir "$PROGRAMFILES\CrossDesk"
|
||||
ShowInstDetails show
|
||||
|
||||
Section "MainSection"
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite ifnewer
|
||||
|
||||
; <20><><EFBFBD>ó<EFBFBD><C3B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>·<EFBFBD><C2B7>
|
||||
File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
|
||||
|
||||
; ? <20><><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD>װĿ¼
|
||||
File "${MUI_ICON}"
|
||||
|
||||
; д<><D0B4>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>Ϣ
|
||||
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
|
||||
SectionEnd
|
||||
|
||||
Section "Cert"
|
||||
SetOutPath "$APPDATA\CrossDesk\certs"
|
||||
File /r "${CERT_FILE}"
|
||||
SectionEnd
|
||||
|
||||
Section -AdditionalIcons
|
||||
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
|
||||
|
||||
; <20><>ʼ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
|
||||
|
||||
; <20><>ҳ<EFBFBD><D2B3><EFBFBD>ݷ<EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD>棩
|
||||
WriteIniStr "$DESKTOP\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
; ɾ<><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD>س<EFBFBD><D8B3><EFBFBD>
|
||||
Delete "$INSTDIR\crossdesk.exe"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
; <20>ݹ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD><EFBFBD>װĿ¼
|
||||
RMDir /r "$INSTDIR"
|
||||
|
||||
; ɾ<><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϳ<EFBFBD>ʼ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.url"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
|
||||
|
||||
; ɾ<><C9BE>ע<EFBFBD><D7A2><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
|
||||
|
||||
; <20>ݹ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD>û<EFBFBD> AppData <20>е<EFBFBD> CrossDesk <20>ļ<EFBFBD><C4BC><EFBFBD>
|
||||
RMDir /r "$APPDATA\CrossDesk"
|
||||
RMDir /r "$LOCALAPPDATA\CrossDesk"
|
||||
SectionEnd
|
||||
|
||||
|
||||
Section -Post
|
||||
SectionEnd
|
||||
44
src/common/display_info.h
Normal file
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
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Winsock2.h>
|
||||
@@ -25,13 +25,13 @@ std::string GetMac() {
|
||||
#ifdef _WIN32
|
||||
IP_ADAPTER_INFO adapterInfo[16];
|
||||
DWORD bufferSize = sizeof(adapterInfo);
|
||||
|
||||
DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize);
|
||||
if (result == ERROR_SUCCESS) {
|
||||
PIP_ADAPTER_INFO adapter = adapterInfo;
|
||||
while (adapter) {
|
||||
for (UINT i = 0; i < adapter->AddressLength; i++) {
|
||||
len += sprintf(mac_addr + len, "%.2X", adapter->Address[i]);
|
||||
len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
|
||||
adapter->Address[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -55,7 +55,8 @@ std::string GetMac() {
|
||||
const unsigned char *base =
|
||||
(const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen];
|
||||
for (int i = 0; i < dlAddr->sdl_alen; i++) {
|
||||
len += sprintf(mac_addr + len, "%.2X", base[i]);
|
||||
len +=
|
||||
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
|
||||
}
|
||||
}
|
||||
cursor = cursor->ifa_next;
|
||||
@@ -98,4 +99,27 @@ std::string GetMac() {
|
||||
close(sock);
|
||||
#endif
|
||||
return mac_addr;
|
||||
}
|
||||
|
||||
std::string GetHostName() {
|
||||
char hostname[256];
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||
std::cerr << "WSAStartup failed." << std::endl;
|
||||
return "";
|
||||
}
|
||||
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
|
||||
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
|
||||
WSACleanup();
|
||||
return "";
|
||||
}
|
||||
WSACleanup();
|
||||
#else
|
||||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||||
LOG_ERROR("gethostname failed");
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
return hostname;
|
||||
}
|
||||
@@ -10,5 +10,6 @@
|
||||
#include <iostream>
|
||||
|
||||
std::string GetMac();
|
||||
std::string GetHostName();
|
||||
|
||||
#endif
|
||||
45
src/config_center/config_center.cpp
Normal file
45
src/config_center/config_center.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "config_center.h"
|
||||
|
||||
ConfigCenter::ConfigCenter() {}
|
||||
|
||||
ConfigCenter::~ConfigCenter() {}
|
||||
|
||||
int ConfigCenter::SetLanguage(LANGUAGE language) {
|
||||
language_ = language;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) {
|
||||
video_quality_ = video_quality;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoEncodeFormat(
|
||||
VIDEO_ENCODE_FORMAT video_encode_format) {
|
||||
video_encode_format_ = video_encode_format;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) {
|
||||
hardware_video_codec_ = hardware_video_codec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetTurn(bool enable_turn) {
|
||||
enable_turn_ = enable_turn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; }
|
||||
|
||||
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() {
|
||||
return video_quality_;
|
||||
}
|
||||
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() {
|
||||
return video_encode_format_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; }
|
||||
|
||||
bool ConfigCenter::IsEnableTurn() { return enable_turn_; }
|
||||
43
src/config_center/config_center.h
Normal file
43
src/config_center/config_center.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CONFIG_CENTER_H_
|
||||
#define _CONFIG_CENTER_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 };
|
||||
|
||||
public:
|
||||
ConfigCenter();
|
||||
~ConfigCenter();
|
||||
|
||||
public:
|
||||
int SetLanguage(LANGUAGE language);
|
||||
int SetVideoQuality(VIDEO_QUALITY video_quality);
|
||||
int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format);
|
||||
int SetHardwareVideoCodec(bool hardware_video_codec);
|
||||
int SetTurn(bool enable_turn);
|
||||
|
||||
public:
|
||||
LANGUAGE GetLanguage();
|
||||
VIDEO_QUALITY GetVideoQuality();
|
||||
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat();
|
||||
bool IsHardwareVideoCodec();
|
||||
bool IsEnableTurn();
|
||||
|
||||
private:
|
||||
// Default value should be same with parameters in localization.h
|
||||
LANGUAGE language_ = LANGUAGE::CHINESE;
|
||||
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
|
||||
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1;
|
||||
bool hardware_video_codec_ = false;
|
||||
bool enable_turn_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -9,12 +9,31 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef enum { mouse = 0, keyboard } ControlType;
|
||||
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
|
||||
#include "display_info.h"
|
||||
|
||||
typedef enum {
|
||||
mouse = 0,
|
||||
keyboard,
|
||||
audio_capture,
|
||||
host_infomation,
|
||||
display_id,
|
||||
} ControlType;
|
||||
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;
|
||||
|
||||
@@ -23,22 +42,42 @@ typedef struct {
|
||||
KeyFlag flag;
|
||||
} Key;
|
||||
|
||||
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 {
|
||||
ControlType type;
|
||||
union {
|
||||
Mouse m;
|
||||
Key k;
|
||||
HostInfo i;
|
||||
bool a;
|
||||
int d;
|
||||
};
|
||||
} RemoteAction;
|
||||
|
||||
// int key_code, bool is_down
|
||||
typedef void (*OnKeyAction)(int, bool, void*);
|
||||
|
||||
class DeviceController {
|
||||
public:
|
||||
virtual ~DeviceController() {}
|
||||
|
||||
public:
|
||||
virtual int Init(int screen_width, int screen_height) = 0;
|
||||
virtual int Destroy() = 0;
|
||||
virtual int SendCommand(RemoteAction remote_action) = 0;
|
||||
// virtual int Init(int screen_width, int screen_height);
|
||||
// virtual int Destroy();
|
||||
// virtual int SendMouseCommand(RemoteAction remote_action);
|
||||
|
||||
// virtual int Hook();
|
||||
// virtual int Unhook();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,7 @@
|
||||
#define _DEVICE_CONTROLLER_FACTORY_H_
|
||||
|
||||
#include "device_controller.h"
|
||||
#include "keyboard_capturer.h"
|
||||
#include "mouse_controller.h"
|
||||
|
||||
class DeviceControllerFactory {
|
||||
@@ -23,7 +24,7 @@ class DeviceControllerFactory {
|
||||
case Mouse:
|
||||
return new MouseController();
|
||||
case Keyboard:
|
||||
return nullptr;
|
||||
return new KeyboardCapturer();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
69
src/device_controller/keyboard/linux/keyboard_capturer.cpp
Normal file
69
src/device_controller/keyboard/linux/keyboard_capturer.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "keyboard_capturer.h"
|
||||
|
||||
#include "keyboard_converter.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
static OnKeyAction g_on_key_action = nullptr;
|
||||
static void* g_user_ptr = nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
32
src/device_controller/keyboard/linux/keyboard_capturer.h
Normal file
32
src/device_controller/keyboard/linux/keyboard_capturer.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-22
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#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 {
|
||||
public:
|
||||
KeyboardCapturer();
|
||||
virtual ~KeyboardCapturer();
|
||||
|
||||
public:
|
||||
virtual int Hook(OnKeyAction on_key_action, void *user_ptr);
|
||||
virtual int Unhook();
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down);
|
||||
|
||||
private:
|
||||
Display *display_;
|
||||
Window root_;
|
||||
bool running_;
|
||||
};
|
||||
|
||||
#endif
|
||||
135
src/device_controller/keyboard/mac/keyboard_capturer.cpp
Normal file
135
src/device_controller/keyboard/mac/keyboard_capturer.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "keyboard_capturer.h"
|
||||
|
||||
#include "keyboard_converter.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
static OnKeyAction g_on_key_action = nullptr;
|
||||
static void *g_user_ptr = nullptr;
|
||||
|
||||
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
|
||||
CGEventRef event, void *userInfo) {
|
||||
KeyboardCapturer *keyboard_capturer = (KeyboardCapturer *)userInfo;
|
||||
if (!keyboard_capturer) {
|
||||
LOG_ERROR("keyboard_capturer is nullptr");
|
||||
return event;
|
||||
}
|
||||
|
||||
int vk_code = 0;
|
||||
|
||||
if (type == kCGEventKeyDown || type == kCGEventKeyUp) {
|
||||
CGKeyCode key_code = static_cast<CGKeyCode>(
|
||||
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
|
||||
if (CGKeyCodeToVkCode.find(key_code) != CGKeyCodeToVkCode.end()) {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], type == kCGEventKeyDown,
|
||||
g_user_ptr);
|
||||
}
|
||||
} else if (type == kCGEventFlagsChanged) {
|
||||
CGEventFlags current_flags = CGEventGetFlags(event);
|
||||
CGKeyCode key_code = static_cast<CGKeyCode>(
|
||||
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
|
||||
|
||||
// caps lock
|
||||
bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0;
|
||||
if (caps_lock_state != keyboard_capturer->caps_lock_flag_) {
|
||||
keyboard_capturer->caps_lock_flag_ = caps_lock_state;
|
||||
if (keyboard_capturer->caps_lock_flag_) {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
|
||||
} else {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// shift
|
||||
bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0;
|
||||
if (shift_state != keyboard_capturer->shift_flag_) {
|
||||
keyboard_capturer->shift_flag_ = shift_state;
|
||||
if (keyboard_capturer->shift_flag_) {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
|
||||
} else {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// control
|
||||
bool control_state = (current_flags & kCGEventFlagMaskControl) != 0;
|
||||
if (control_state != keyboard_capturer->control_flag_) {
|
||||
keyboard_capturer->control_flag_ = control_state;
|
||||
if (keyboard_capturer->control_flag_) {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
|
||||
} else {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// option
|
||||
bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0;
|
||||
if (option_state != keyboard_capturer->option_flag_) {
|
||||
keyboard_capturer->option_flag_ = option_state;
|
||||
if (keyboard_capturer->option_flag_) {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
|
||||
} else {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// command
|
||||
bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0;
|
||||
if (command_state != keyboard_capturer->command_flag_) {
|
||||
keyboard_capturer->command_flag_ = command_state;
|
||||
if (keyboard_capturer->command_flag_) {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
|
||||
} else {
|
||||
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KeyboardCapturer::KeyboardCapturer() {}
|
||||
|
||||
KeyboardCapturer::~KeyboardCapturer() {}
|
||||
|
||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void *user_ptr) {
|
||||
g_on_key_action = on_key_action;
|
||||
g_user_ptr = user_ptr;
|
||||
|
||||
CGEventMask eventMask = (1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) |
|
||||
(1 << kCGEventFlagsChanged);
|
||||
|
||||
event_tap_ = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
|
||||
kCGEventTapOptionDefault, eventMask,
|
||||
eventCallback, this);
|
||||
|
||||
if (!event_tap_) {
|
||||
LOG_ERROR("CGEventTapCreate failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
run_loop_source_ =
|
||||
CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event_tap_, 0);
|
||||
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_,
|
||||
kCFRunLoopCommonModes);
|
||||
|
||||
CGEventTapEnable(event_tap_, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() {
|
||||
CFRelease(run_loop_source_);
|
||||
CFRelease(event_tap_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
CFRelease(event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
36
src/device_controller/keyboard/mac/keyboard_capturer.h
Normal file
36
src/device_controller/keyboard/mac/keyboard_capturer.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-22
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KEYBOARD_CAPTURER_H_
|
||||
#define _KEYBOARD_CAPTURER_H_
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class KeyboardCapturer : public DeviceController {
|
||||
public:
|
||||
KeyboardCapturer();
|
||||
virtual ~KeyboardCapturer();
|
||||
|
||||
public:
|
||||
virtual int Hook(OnKeyAction on_key_action, void *user_ptr);
|
||||
virtual int Unhook();
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down);
|
||||
|
||||
private:
|
||||
CFMachPortRef event_tap_;
|
||||
CFRunLoopSourceRef run_loop_source_;
|
||||
|
||||
public:
|
||||
bool caps_lock_flag_ = false;
|
||||
bool shift_flag_ = false;
|
||||
bool control_flag_ = false;
|
||||
bool option_flag_ = false;
|
||||
bool command_flag_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
56
src/device_controller/keyboard/windows/keyboard_capturer.cpp
Normal file
56
src/device_controller/keyboard/windows/keyboard_capturer.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "keyboard_capturer.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
static OnKeyAction g_on_key_action = nullptr;
|
||||
static void* g_user_ptr = nullptr;
|
||||
|
||||
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
|
||||
if (nCode == HC_ACTION && g_on_key_action) {
|
||||
KBDLLHOOKSTRUCT* kbData = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
|
||||
|
||||
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
|
||||
g_on_key_action(kbData->vkCode, true, g_user_ptr);
|
||||
} else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
|
||||
g_on_key_action(kbData->vkCode, false, g_user_ptr);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
KeyboardCapturer::KeyboardCapturer() {}
|
||||
|
||||
KeyboardCapturer::~KeyboardCapturer() {}
|
||||
|
||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
||||
g_on_key_action = on_key_action;
|
||||
g_user_ptr = user_ptr;
|
||||
|
||||
keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
|
||||
if (!keyboard_hook_) {
|
||||
LOG_ERROR("Failed to install keyboard hook");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() {
|
||||
UnhookWindowsHookEx(keyboard_hook_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// apply remote keyboard commands to the local machine
|
||||
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
|
||||
INPUT input = {0};
|
||||
input.type = INPUT_KEYBOARD;
|
||||
input.ki.wVk = (WORD)key_code;
|
||||
|
||||
if (!is_down) {
|
||||
input.ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
}
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
src/device_controller/keyboard/windows/keyboard_capturer.h
Normal file
28
src/device_controller/keyboard/windows/keyboard_capturer.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-22
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KEYBOARD_CAPTURER_H_
|
||||
#define _KEYBOARD_CAPTURER_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class KeyboardCapturer : public DeviceController {
|
||||
public:
|
||||
KeyboardCapturer();
|
||||
virtual ~KeyboardCapturer();
|
||||
|
||||
public:
|
||||
virtual int Hook(OnKeyAction on_key_action, void *user_ptr);
|
||||
virtual int Unhook();
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down);
|
||||
|
||||
private:
|
||||
HHOOK keyboard_hook_ = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
304
src/device_controller/keyboard_converter.h
Normal file
304
src/device_controller/keyboard_converter.h
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* @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 = {
|
||||
{0x41, 0x0041}, {0x42, 0x0042}, {0x43, 0x0043}, {0x44, 0x0044},
|
||||
{0x45, 0x0045}, {0x46, 0x0046}, {0x47, 0x0047}, {0x48, 0x0048},
|
||||
{0x49, 0x0049}, {0x4A, 0x004A}, {0x4B, 0x004B}, {0x4C, 0x004C},
|
||||
{0x4D, 0x004D}, {0x4E, 0x004E}, {0x4F, 0x004F}, {0x50, 0x0050},
|
||||
{0x51, 0x0051}, {0x52, 0x0052}, {0x53, 0x0053}, {0x54, 0x0054},
|
||||
{0x55, 0x0055}, {0x56, 0x0056}, {0x57, 0x0057}, {0x58, 0x0058},
|
||||
{0x59, 0x0059}, {0x5A, 0x005A}, {0x30, 0x0030}, {0x31, 0x0031},
|
||||
{0x32, 0x0032}, {0x33, 0x0033}, {0x34, 0x0034}, {0x35, 0x0035},
|
||||
{0x36, 0x0036}, {0x37, 0x0037}, {0x38, 0x0038}, {0x39, 0x0039},
|
||||
{0x1B, 0xFF1B}, {0x0D, 0xFF0D}, {0x20, 0x0020}, {0x08, 0xFF08},
|
||||
{0x09, 0xFF09}, {0x25, 0xFF51}, {0x27, 0xFF53}, {0x26, 0xFF52},
|
||||
{0x28, 0xFF54}, {0x70, 0xFFBE}, {0x71, 0xFFBF}, {0x72, 0xFFC0},
|
||||
{0x73, 0xFFC1}, {0x74, 0xFFC2}, {0x75, 0xFFC3}, {0x76, 0xFFC4},
|
||||
{0x77, 0xFFC5}, {0x78, 0xFFC6}, {0x79, 0xFFC7}, {0x7A, 0xFFC8},
|
||||
{0x7B, 0xFFC9},
|
||||
};
|
||||
|
||||
// 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;
|
||||
}();
|
||||
|
||||
// macOS CGKeyCode to X11 KeySym
|
||||
std::map<int, int> cgKeyCodeToX11KeySym = {
|
||||
{0x00, 0x0041}, {0x0B, 0x0042}, {0x08, 0x0043}, {0x02, 0x0044},
|
||||
{0x0E, 0x0045}, {0x03, 0x0046}, {0x05, 0x0047}, {0x04, 0x0048},
|
||||
{0x22, 0x0049}, {0x26, 0x004A}, {0x28, 0x004B}, {0x25, 0x004C},
|
||||
{0x2E, 0x004D}, {0x2D, 0x004E}, {0x1F, 0x004F}, {0x23, 0x0050},
|
||||
{0x0C, 0x0051}, {0x0F, 0x0052}, {0x01, 0x0053}, {0x11, 0x0054},
|
||||
{0x20, 0x0055}, {0x09, 0x0056}, {0x0D, 0x0057}, {0x07, 0x0058},
|
||||
{0x10, 0x0059}, {0x06, 0x005A}, {0x12, 0x0031}, {0x13, 0x0032},
|
||||
{0x14, 0x0033}, {0x15, 0x0034}, {0x17, 0x0035}, {0x16, 0x0036},
|
||||
{0x1A, 0x0037}, {0x1C, 0x0038}, {0x19, 0x0039}, {0x1D, 0x0030},
|
||||
{0x35, 0xFF1B}, {0x24, 0xFF0D}, {0x31, 0x0020}, {0x33, 0xFF08},
|
||||
{0x30, 0xFF09}, {0x7B, 0xFF51}, {0x7C, 0xFF53}, {0x7E, 0xFF52},
|
||||
{0x7D, 0xFF54}, {0x7A, 0xFFBE}, {0x78, 0xFFBF}, {0x63, 0xFFC0},
|
||||
{0x76, 0xFFC1}, {0x60, 0xFFC2}, {0x61, 0xFFC3}, {0x62, 0xFFC4},
|
||||
{0x64, 0xFFC5}, {0x65, 0xFFC6}, {0x6D, 0xFFC7}, {0x67, 0xFFC8},
|
||||
{0x6F, 0xFFC9},
|
||||
};
|
||||
|
||||
// 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;
|
||||
}();
|
||||
|
||||
#endif
|
||||
@@ -1,122 +1,124 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include "log.h"
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {}
|
||||
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 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;
|
||||
}
|
||||
|
||||
write(uinput_fd_, &uidev, sizeof(uidev));
|
||||
ioctl(uinput_fd_, UI_DEV_CREATE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() {
|
||||
ioctl(uinput_fd_, UI_DEV_DESTROY);
|
||||
close(uinput_fd_);
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
display_ = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::SendCommand(RemoteAction remote_action) {
|
||||
int mouse_pos_x = remote_action.m.x * screen_width_ / 1280;
|
||||
int mouse_pos_y = remote_action.m.y * screen_height_ / 720;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyDown(int fd, int kval) {
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, 0);
|
||||
|
||||
event.type = EV_KEY;
|
||||
event.value = 1;
|
||||
event.code = kval;
|
||||
write(fd, &event, sizeof(event));
|
||||
|
||||
event.type = EV_SYN;
|
||||
event.value = 0;
|
||||
event.code = SYN_REPORT;
|
||||
write(fd, &event, sizeof(event));
|
||||
void MouseController::SetMousePosition(int x, int y) {
|
||||
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyUp(int fd, int kval) {
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, 0);
|
||||
|
||||
event.type = EV_KEY;
|
||||
event.value = 0;
|
||||
event.code = kval;
|
||||
write(fd, &event, sizeof(event));
|
||||
|
||||
event.type = EV_SYN;
|
||||
event.value = 0;
|
||||
event.code = SYN_REPORT;
|
||||
write(fd, &event, sizeof(event));
|
||||
void MouseController::SimulateKeyDown(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
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));
|
||||
void MouseController::SimulateKeyUp(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
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::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;
|
||||
};
|
||||
|
||||
@@ -2,47 +2,99 @@
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
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 mouse_pos_x = remote_action.m.x * screen_width_ / 1280;
|
||||
int mouse_pos_y = remote_action.m.y * screen_height_ / 720;
|
||||
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) {
|
||||
mouse_type = kCGEventLeftMouseDown;
|
||||
} else if (remote_action.m.flag == MouseFlag::left_up) {
|
||||
mouse_type = kCGEventLeftMouseUp;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_down) {
|
||||
mouse_type = kCGEventRightMouseDown;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_up) {
|
||||
mouse_type = kCGEventRightMouseUp;
|
||||
} else {
|
||||
mouse_type = kCGEventMouseMoved;
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::left_down:
|
||||
mouse_type = kCGEventLeftMouseDown;
|
||||
left_dragging_ = true;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonLeft);
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
mouse_type = kCGEventLeftMouseUp;
|
||||
left_dragging_ = false;
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonLeft);
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
mouse_type = kCGEventRightMouseDown;
|
||||
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, mouse_point,
|
||||
mouse_button);
|
||||
break;
|
||||
}
|
||||
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type,
|
||||
CGPointMake(mouse_pos_x, mouse_pos_y),
|
||||
kCGMouseButtonLeft);
|
||||
|
||||
CGEventPost(kCGHIDEventTap, mouse_event);
|
||||
CFRelease(mouse_event);
|
||||
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,13 +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;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
bool left_dragging_ = false;
|
||||
bool right_dragging_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,39 +1,64 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
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 = remote_action.m.x * screen_width_ / 1280;
|
||||
ip.mi.dy = remote_action.m.y * screen_height_ / 720;
|
||||
if (remote_action.m.flag == MouseFlag::left_down) {
|
||||
ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
|
||||
} else if (remote_action.m.flag == MouseFlag::left_up) {
|
||||
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_down) {
|
||||
ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE;
|
||||
} else if (remote_action.m.flag == MouseFlag::right_up) {
|
||||
ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE;
|
||||
} else {
|
||||
ip.mi.dwFlags = MOUSEEVENTF_MOVE;
|
||||
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;
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE;
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE;
|
||||
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
|
||||
867
src/gui/main.cpp
867
src/gui/main.cpp
@@ -1,872 +1,17 @@
|
||||
#include <SDL.h>
|
||||
#include <stdio.h>
|
||||
#ifdef _WIN32
|
||||
#ifdef REMOTE_DESK_DEBUG
|
||||
#ifdef DESK_PORT_DEBUG
|
||||
#pragma comment(linker, "/subsystem:\"console\"")
|
||||
#else
|
||||
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../../thirdparty/projectx/src/interface/x.h"
|
||||
#include "device_controller_factory.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
#include "imgui_impl_sdlrenderer2.h"
|
||||
#include "log.h"
|
||||
#include "screen_capturer_factory.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
|
||||
#ifdef REMOTE_DESK_DEBUG
|
||||
#define MOUSE_CONTROL 0
|
||||
#else
|
||||
#define MOUSE_CONTROL 1
|
||||
#endif
|
||||
|
||||
int screen_w = 1280, screen_h = 720;
|
||||
int window_w = 1280, window_h = 720;
|
||||
const int pixel_w = 1280, pixel_h = 720;
|
||||
|
||||
unsigned char dst_buffer[pixel_w * pixel_h * 3 / 2];
|
||||
unsigned char audio_buffer[960];
|
||||
SDL_Texture *sdlTexture = nullptr;
|
||||
SDL_Renderer *sdlRenderer = nullptr;
|
||||
SDL_Rect sdlRect;
|
||||
SDL_Window *window;
|
||||
static SDL_AudioDeviceID input_dev;
|
||||
static SDL_AudioDeviceID output_dev;
|
||||
|
||||
uint32_t start_time, end_time, elapsed_time;
|
||||
uint32_t frame_count = 0;
|
||||
int fps = 0;
|
||||
|
||||
static std::atomic<bool> audio_buffer_fresh = false;
|
||||
static uint32_t last_ts = 0;
|
||||
|
||||
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
|
||||
int src_rate = 48000;
|
||||
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
int src_nb_channels = 0;
|
||||
uint8_t **src_data = NULL;
|
||||
int src_linesize;
|
||||
int src_nb_samples = 480;
|
||||
|
||||
int64_t dst_ch_layout = AV_CH_LAYOUT_MONO;
|
||||
int dst_rate = 48000;
|
||||
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
int dst_nb_channels = 0;
|
||||
uint8_t **dst_data = NULL;
|
||||
int dst_linesize;
|
||||
int dst_nb_samples;
|
||||
int max_dst_nb_samples;
|
||||
|
||||
int dst_bufsize;
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
int ret;
|
||||
|
||||
int audio_len = 0;
|
||||
|
||||
std::string window_title = "Remote Desk Client";
|
||||
std::string server_connection_status_str = "-";
|
||||
std::string client_connection_status_str = "-";
|
||||
std::string server_signal_status_str = "-";
|
||||
std::string client_signal_status_str = "-";
|
||||
|
||||
std::atomic<ConnectionStatus> server_connection_status =
|
||||
ConnectionStatus::Closed;
|
||||
std::atomic<ConnectionStatus> client_connection_status =
|
||||
ConnectionStatus::Closed;
|
||||
std::atomic<SignalStatus> server_signal_status = SignalStatus::SignalClosed;
|
||||
std::atomic<SignalStatus> client_signal_status = SignalStatus::SignalClosed;
|
||||
|
||||
// Refresh Event
|
||||
#define REFRESH_EVENT (SDL_USEREVENT + 1)
|
||||
#define QUIT_EVENT (SDL_USEREVENT + 2)
|
||||
|
||||
typedef struct {
|
||||
char password[16];
|
||||
} CDCache;
|
||||
|
||||
int thread_exit = 0;
|
||||
PeerPtr *peer_server = nullptr;
|
||||
PeerPtr *peer_client = nullptr;
|
||||
bool joined = false;
|
||||
bool received_frame = false;
|
||||
bool menu_hovered = false;
|
||||
|
||||
static bool connect_button_pressed = false;
|
||||
static const char *connect_label = "Connect";
|
||||
static char input_password[7] = "";
|
||||
static FILE *cd_cache_file = nullptr;
|
||||
static CDCache cd_cache;
|
||||
|
||||
static bool is_create_connection = false;
|
||||
static bool done = false;
|
||||
|
||||
ScreenCapturerFactory *screen_capturer_factory = nullptr;
|
||||
ScreenCapturer *screen_capturer = nullptr;
|
||||
|
||||
DeviceControllerFactory *device_controller_factory = nullptr;
|
||||
MouseController *mouse_controller = nullptr;
|
||||
|
||||
char *nv12_buffer = nullptr;
|
||||
|
||||
#ifdef __linux__
|
||||
std::chrono::_V2::system_clock::time_point last_frame_time_;
|
||||
#else
|
||||
std::chrono::steady_clock::time_point last_frame_time_;
|
||||
#endif
|
||||
|
||||
inline int ProcessMouseKeyEven(SDL_Event &ev) {
|
||||
float ratio = (float)(1280.0 / window_w);
|
||||
|
||||
RemoteAction remote_action;
|
||||
remote_action.m.x = (size_t)(ev.button.x * ratio);
|
||||
remote_action.m.y = (size_t)(ev.button.y * ratio);
|
||||
|
||||
if (SDL_KEYDOWN == ev.type) // SDL_KEYUP
|
||||
{
|
||||
// printf("SDLK_DOWN: %d\n", SDL_KeyCode(ev.key.keysym.sym));
|
||||
if (SDLK_DOWN == ev.key.keysym.sym) {
|
||||
// printf("SDLK_DOWN \n");
|
||||
|
||||
} else if (SDLK_UP == ev.key.keysym.sym) {
|
||||
// printf("SDLK_UP \n");
|
||||
|
||||
} else if (SDLK_LEFT == ev.key.keysym.sym) {
|
||||
// printf("SDLK_LEFT \n");
|
||||
|
||||
} else if (SDLK_RIGHT == ev.key.keysym.sym) {
|
||||
// printf("SDLK_RIGHT \n");
|
||||
}
|
||||
} else if (SDL_MOUSEBUTTONDOWN == ev.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (SDL_BUTTON_LEFT == ev.button.button) {
|
||||
remote_action.m.flag = MouseFlag::left_down;
|
||||
} else if (SDL_BUTTON_RIGHT == ev.button.button) {
|
||||
remote_action.m.flag = MouseFlag::right_down;
|
||||
}
|
||||
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
|
||||
sizeof(remote_action));
|
||||
} else if (SDL_MOUSEBUTTONUP == ev.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (SDL_BUTTON_LEFT == ev.button.button) {
|
||||
remote_action.m.flag = MouseFlag::left_up;
|
||||
} else if (SDL_BUTTON_RIGHT == ev.button.button) {
|
||||
remote_action.m.flag = MouseFlag::right_up;
|
||||
}
|
||||
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
|
||||
sizeof(remote_action));
|
||||
} else if (SDL_MOUSEMOTION == ev.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
remote_action.m.flag = MouseFlag::move;
|
||||
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
|
||||
sizeof(remote_action));
|
||||
} else if (SDL_QUIT == ev.type) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
printf("SDL_QUIT\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
|
||||
int64_t delay = swr_get_delay(swr_ctx, src_rate);
|
||||
dst_nb_samples = (int)av_rescale_rnd(delay + src_nb_samples, dst_rate,
|
||||
src_rate, AV_ROUND_UP);
|
||||
if (dst_nb_samples > max_dst_nb_samples) {
|
||||
av_freep(&dst_data[0]);
|
||||
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
|
||||
dst_nb_samples, dst_sample_fmt, 1);
|
||||
if (ret < 0) return;
|
||||
max_dst_nb_samples = dst_nb_samples;
|
||||
}
|
||||
|
||||
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
|
||||
(const uint8_t **)&stream, src_nb_samples);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error while converting\n");
|
||||
return;
|
||||
}
|
||||
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret,
|
||||
dst_sample_fmt, 1);
|
||||
if (dst_bufsize < 0) {
|
||||
fprintf(stderr, "Could not get sample buffer size\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (1) {
|
||||
if ("ClientConnected" == client_connection_status_str) {
|
||||
SendData(peer_client, DATA_TYPE::AUDIO, (const char *)dst_data[0],
|
||||
dst_bufsize);
|
||||
}
|
||||
|
||||
if ("ServerConnected" == server_connection_status_str) {
|
||||
SendData(peer_server, DATA_TYPE::AUDIO, (const char *)dst_data[0],
|
||||
dst_bufsize);
|
||||
}
|
||||
} else {
|
||||
memcpy(audio_buffer, dst_data[0], dst_bufsize);
|
||||
audio_len = dst_bufsize;
|
||||
SDL_Delay(10);
|
||||
audio_buffer_fresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) {
|
||||
// if ("ClientConnected" != client_connection_status_str) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (!audio_buffer_fresh) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_memset(stream, 0, len);
|
||||
|
||||
if (audio_len == 0) {
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
|
||||
len = (len > audio_len ? audio_len : len);
|
||||
SDL_MixAudioFormat(stream, audio_buffer, AUDIO_S16LSB, len,
|
||||
SDL_MIX_MAXVOLUME);
|
||||
audio_buffer_fresh = false;
|
||||
}
|
||||
|
||||
void ServerReceiveVideoBuffer(const char *data, size_t size,
|
||||
const char *user_id, size_t user_id_size) {}
|
||||
|
||||
void ClientReceiveVideoBuffer(const char *data, size_t size,
|
||||
const char *user_id, size_t user_id_size) {
|
||||
// std::cout << "Receive: [" << user_id << "] " << std::endl;
|
||||
if (joined) {
|
||||
memcpy(dst_buffer, data, size);
|
||||
|
||||
SDL_Event event;
|
||||
event.type = REFRESH_EVENT;
|
||||
SDL_PushEvent(&event);
|
||||
received_frame = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerReceiveAudioBuffer(const char *data, size_t size,
|
||||
const char *user_id, size_t user_id_size) {
|
||||
// memset(audio_buffer, 0, size);
|
||||
// memcpy(audio_buffer, data, size);
|
||||
// audio_len = size;
|
||||
audio_buffer_fresh = true;
|
||||
|
||||
SDL_QueueAudio(output_dev, data, (Uint32)size);
|
||||
// printf("queue audio\n");
|
||||
}
|
||||
|
||||
void ClientReceiveAudioBuffer(const char *data, size_t size,
|
||||
const char *user_id, size_t user_id_size) {
|
||||
// std::cout << "Client receive audio, size " << size << ", user [" << user_id
|
||||
// << "] " << std::endl;
|
||||
SDL_QueueAudio(output_dev, data, (Uint32)size);
|
||||
}
|
||||
|
||||
void ServerReceiveDataBuffer(const char *data, size_t size, const char *user_id,
|
||||
size_t user_id_size) {
|
||||
std::string user(user_id, user_id_size);
|
||||
|
||||
RemoteAction remote_action;
|
||||
memcpy(&remote_action, data, sizeof(remote_action));
|
||||
|
||||
// std::cout << "remote_action: " << remote_action.type << " "
|
||||
// << remote_action.m.flag << " " << remote_action.m.x << " "
|
||||
// << remote_action.m.y << std::endl;
|
||||
#if MOUSE_CONTROL
|
||||
mouse_controller->SendCommand(remote_action);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClientReceiveDataBuffer(const char *data, size_t size, const char *user_id,
|
||||
size_t user_id_size) {}
|
||||
|
||||
void ServerSignalStatus(SignalStatus status) {
|
||||
server_signal_status = status;
|
||||
if (SignalStatus::SignalConnecting == status) {
|
||||
server_signal_status_str = "ServerSignalConnecting";
|
||||
} else if (SignalStatus::SignalConnected == status) {
|
||||
server_signal_status_str = "ServerSignalConnected";
|
||||
} else if (SignalStatus::SignalFailed == status) {
|
||||
server_signal_status_str = "ServerSignalFailed";
|
||||
} else if (SignalStatus::SignalClosed == status) {
|
||||
server_signal_status_str = "ServerSignalClosed";
|
||||
} else if (SignalStatus::SignalReconnecting == status) {
|
||||
server_signal_status_str = "ServerSignalReconnecting";
|
||||
}
|
||||
}
|
||||
|
||||
void ClientSignalStatus(SignalStatus status) {
|
||||
client_signal_status = status;
|
||||
if (SignalStatus::SignalConnecting == status) {
|
||||
client_signal_status_str = "ClientSignalConnecting";
|
||||
} else if (SignalStatus::SignalConnected == status) {
|
||||
client_signal_status_str = "ClientSignalConnected";
|
||||
} else if (SignalStatus::SignalFailed == status) {
|
||||
client_signal_status_str = "ClientSignalFailed";
|
||||
} else if (SignalStatus::SignalClosed == status) {
|
||||
client_signal_status_str = "ClientSignalClosed";
|
||||
} else if (SignalStatus::SignalReconnecting == status) {
|
||||
client_signal_status_str = "ClientSignalReconnecting";
|
||||
}
|
||||
}
|
||||
|
||||
void ServerConnectionStatus(ConnectionStatus status) {
|
||||
server_connection_status = status;
|
||||
if (ConnectionStatus::Connecting == status) {
|
||||
server_connection_status_str = "ServerConnecting";
|
||||
} else if (ConnectionStatus::Connected == status) {
|
||||
server_connection_status_str = "ServerConnected";
|
||||
} else if (ConnectionStatus::Disconnected == status) {
|
||||
server_connection_status_str = "ServerDisconnected";
|
||||
} else if (ConnectionStatus::Failed == status) {
|
||||
server_connection_status_str = "ServerFailed";
|
||||
} else if (ConnectionStatus::Closed == status) {
|
||||
server_connection_status_str = "ServerClosed";
|
||||
} else if (ConnectionStatus::IncorrectPassword == status) {
|
||||
server_connection_status_str = "Incorrect password";
|
||||
if (connect_button_pressed) {
|
||||
connect_button_pressed = false;
|
||||
joined = false;
|
||||
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientConnectionStatus(ConnectionStatus status) {
|
||||
client_connection_status = status;
|
||||
if (ConnectionStatus::Connecting == status) {
|
||||
client_connection_status_str = "ClientConnecting";
|
||||
} else if (ConnectionStatus::Connected == status) {
|
||||
client_connection_status_str = "ClientConnected";
|
||||
joined = true;
|
||||
} else if (ConnectionStatus::Disconnected == status) {
|
||||
client_connection_status_str = "ClientDisconnected";
|
||||
} else if (ConnectionStatus::Failed == status) {
|
||||
client_connection_status_str = "ClientFailed";
|
||||
} else if (ConnectionStatus::Closed == status) {
|
||||
client_connection_status_str = "ClientClosed";
|
||||
} else if (ConnectionStatus::IncorrectPassword == status) {
|
||||
client_connection_status_str = "Incorrect password";
|
||||
if (connect_button_pressed) {
|
||||
connect_button_pressed = false;
|
||||
joined = false;
|
||||
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
|
||||
}
|
||||
} else if (ConnectionStatus::NoSuchTransmissionId == status) {
|
||||
client_connection_status_str = "No such transmission id";
|
||||
if (connect_button_pressed) {
|
||||
connect_button_pressed = false;
|
||||
joined = false;
|
||||
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int initResampler() {
|
||||
/* create resampler context */
|
||||
swr_ctx = swr_alloc();
|
||||
if (!swr_ctx) {
|
||||
fprintf(stderr, "Could not allocate resampler context\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* set options */
|
||||
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
|
||||
|
||||
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
|
||||
|
||||
/* initialize the resampling context */
|
||||
if ((ret = swr_init(swr_ctx)) < 0) {
|
||||
fprintf(stderr, "Failed to initialize the resampling context\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* allocate source and destination samples buffers */
|
||||
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
|
||||
|
||||
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
|
||||
src_nb_channels, src_nb_samples,
|
||||
src_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate source samples\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
max_dst_nb_samples = dst_nb_samples =
|
||||
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
|
||||
/* buffer is going to be directly written to a rawaudio file, no alignment */
|
||||
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
|
||||
|
||||
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
|
||||
dst_nb_channels, dst_nb_samples,
|
||||
dst_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate destination samples\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
LOG_INFO("Remote desk");
|
||||
|
||||
last_ts = static_cast<uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch())
|
||||
.count());
|
||||
|
||||
initResampler();
|
||||
|
||||
cd_cache_file = fopen("cache.cd", "r+");
|
||||
if (cd_cache_file) {
|
||||
fseek(cd_cache_file, 0, SEEK_SET);
|
||||
fread(&cd_cache.password, sizeof(cd_cache.password), 1, cd_cache_file);
|
||||
fclose(cd_cache_file);
|
||||
strncpy(input_password, cd_cache.password, sizeof(cd_cache.password));
|
||||
}
|
||||
|
||||
// Setup SDL
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER |
|
||||
SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
printf("Error: %s\n", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// From 2.0.18: Enable native IME.
|
||||
#ifdef SDL_HINT_IME_SHOW_UI
|
||||
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
||||
#endif
|
||||
|
||||
// Create window with SDL_Renderer graphics context
|
||||
SDL_WindowFlags window_flags =
|
||||
(SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
window = SDL_CreateWindow("Remote Desk", SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, window_w, window_h,
|
||||
window_flags);
|
||||
|
||||
SDL_DisplayMode DM;
|
||||
SDL_GetCurrentDisplayMode(0, &DM);
|
||||
screen_w = DM.w;
|
||||
screen_h = DM.h;
|
||||
|
||||
sdlRenderer = SDL_CreateRenderer(
|
||||
window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
||||
if (sdlRenderer == nullptr) {
|
||||
SDL_Log("Error creating SDL_Renderer!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Uint32 pixformat = 0;
|
||||
pixformat = SDL_PIXELFORMAT_NV12;
|
||||
|
||||
sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat,
|
||||
SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);
|
||||
|
||||
// Audio
|
||||
SDL_AudioSpec want_in, have_in, want_out, have_out;
|
||||
SDL_zero(want_in);
|
||||
want_in.freq = 48000;
|
||||
want_in.format = AUDIO_S16LSB;
|
||||
want_in.channels = 1;
|
||||
want_in.samples = 480;
|
||||
want_in.callback = SdlCaptureAudioIn;
|
||||
|
||||
input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in, 0);
|
||||
if (input_dev == 0) {
|
||||
SDL_Log("Failed to open input: %s", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_zero(want_out);
|
||||
want_out.freq = 48000;
|
||||
want_out.format = AUDIO_S16LSB;
|
||||
want_out.channels = 1;
|
||||
// want_out.silence = 0;
|
||||
want_out.samples = 480;
|
||||
want_out.callback = NULL;
|
||||
|
||||
output_dev = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out, 0);
|
||||
if (output_dev == 0) {
|
||||
SDL_Log("Failed to open input: %s", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(input_dev, 0);
|
||||
SDL_PauseAudioDevice(output_dev, 0);
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
|
||||
io.ConfigFlags |=
|
||||
ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
||||
io.ConfigFlags |=
|
||||
ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
||||
|
||||
// Setup Dear ImGui style
|
||||
ImGui::StyleColorsDark();
|
||||
// ImGui::StyleColorsLight();
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(window, sdlRenderer);
|
||||
ImGui_ImplSDLRenderer2_Init(sdlRenderer);
|
||||
|
||||
// Our state
|
||||
bool show_demo_window = true;
|
||||
bool show_another_window = false;
|
||||
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
|
||||
std::string mac_addr_str = GetMac();
|
||||
|
||||
std::thread rtc_thread(
|
||||
[](int screen_width, int screen_height) {
|
||||
std::string default_cfg_path = "../../../../config/config.ini";
|
||||
std::ifstream f(default_cfg_path.c_str());
|
||||
|
||||
std::string mac_addr_str = GetMac();
|
||||
|
||||
Params server_params;
|
||||
server_params.cfg_path =
|
||||
f.good() ? "../../../../config/config.ini" : "config.ini";
|
||||
server_params.on_receive_video_buffer = ServerReceiveVideoBuffer;
|
||||
server_params.on_receive_audio_buffer = ServerReceiveAudioBuffer;
|
||||
server_params.on_receive_data_buffer = ServerReceiveDataBuffer;
|
||||
server_params.on_signal_status = ServerSignalStatus;
|
||||
server_params.on_connection_status = ServerConnectionStatus;
|
||||
|
||||
Params client_params;
|
||||
client_params.cfg_path =
|
||||
f.good() ? "../../../../config/config.ini" : "config.ini";
|
||||
client_params.on_receive_video_buffer = ClientReceiveVideoBuffer;
|
||||
client_params.on_receive_audio_buffer = ClientReceiveAudioBuffer;
|
||||
client_params.on_receive_data_buffer = ClientReceiveDataBuffer;
|
||||
client_params.on_signal_status = ClientSignalStatus;
|
||||
client_params.on_connection_status = ClientConnectionStatus;
|
||||
|
||||
std::string transmission_id = "000001";
|
||||
|
||||
peer_server = CreatePeer(&server_params);
|
||||
LOG_INFO("Create peer_server");
|
||||
std::string server_user_id = "S-" + mac_addr_str;
|
||||
Init(peer_server, server_user_id.c_str());
|
||||
LOG_INFO("peer_server init finish");
|
||||
|
||||
peer_client = CreatePeer(&client_params);
|
||||
LOG_INFO("Create peer_client");
|
||||
std::string client_user_id = "C-" + mac_addr_str;
|
||||
Init(peer_client, client_user_id.c_str());
|
||||
LOG_INFO("peer_client init finish");
|
||||
|
||||
{
|
||||
while (SignalStatus::SignalConnected != server_signal_status &&
|
||||
!done) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string user_id = "S-" + mac_addr_str;
|
||||
is_create_connection =
|
||||
CreateConnection(peer_server, mac_addr_str.c_str(),
|
||||
input_password)
|
||||
? false
|
||||
: true;
|
||||
|
||||
nv12_buffer = new char[NV12_BUFFER_SIZE];
|
||||
|
||||
// Screen capture
|
||||
screen_capturer_factory = new ScreenCapturerFactory();
|
||||
screen_capturer = (ScreenCapturer *)screen_capturer_factory->Create();
|
||||
|
||||
last_frame_time_ = std::chrono::high_resolution_clock::now();
|
||||
ScreenCapturer::RECORD_DESKTOP_RECT rect;
|
||||
rect.left = 0;
|
||||
rect.top = 0;
|
||||
rect.right = screen_w;
|
||||
rect.bottom = screen_h;
|
||||
|
||||
screen_capturer->Init(
|
||||
rect, 60,
|
||||
[](unsigned char *data, int size, int width, int height) -> void {
|
||||
auto now_time = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> duration =
|
||||
now_time - last_frame_time_;
|
||||
auto tc = duration.count() * 1000;
|
||||
|
||||
if (tc >= 0) {
|
||||
SendData(peer_server, DATA_TYPE::VIDEO, (const char *)data,
|
||||
NV12_BUFFER_SIZE);
|
||||
last_frame_time_ = now_time;
|
||||
}
|
||||
});
|
||||
|
||||
screen_capturer->Start();
|
||||
|
||||
// Mouse control
|
||||
device_controller_factory = new DeviceControllerFactory();
|
||||
mouse_controller =
|
||||
(MouseController *)device_controller_factory->Create(
|
||||
DeviceControllerFactory::Device::Mouse);
|
||||
mouse_controller->Init(screen_w, screen_h);
|
||||
}
|
||||
},
|
||||
screen_w, screen_h);
|
||||
|
||||
// Main loop
|
||||
while (!done) {
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
if (joined && !menu_hovered) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_None);
|
||||
}
|
||||
|
||||
{
|
||||
static float f = 0.0f;
|
||||
static int counter = 0;
|
||||
|
||||
const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Once);
|
||||
ImGui::SetNextWindowSize(ImVec2(190, 200));
|
||||
|
||||
ImGui::Begin("Menu", nullptr, ImGuiWindowFlags_NoResize);
|
||||
|
||||
{
|
||||
menu_hovered = ImGui::IsWindowHovered();
|
||||
ImGui::Text(" LOCAL ID:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(95);
|
||||
ImGui::InputText(
|
||||
"##local_id", (char *)mac_addr_str.c_str(),
|
||||
mac_addr_str.length() + 1,
|
||||
ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::Text(" PASSWORD:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(95);
|
||||
|
||||
char input_password_tmp[7] = "";
|
||||
strncpy(input_password_tmp, input_password, sizeof(input_password));
|
||||
|
||||
ImGui::InputTextWithHint("##server_pwd", "max 6 chars", input_password,
|
||||
IM_ARRAYSIZE(input_password),
|
||||
ImGuiInputTextFlags_CharsNoBlank);
|
||||
if (strcmp(input_password_tmp, input_password)) {
|
||||
cd_cache_file = fopen("cache.cd", "w+");
|
||||
if (cd_cache_file) {
|
||||
fseek(cd_cache_file, 0, SEEK_SET);
|
||||
strncpy(cd_cache.password, input_password, sizeof(input_password));
|
||||
fwrite(&cd_cache.password, sizeof(cd_cache.password), 1,
|
||||
cd_cache_file);
|
||||
fclose(cd_cache_file);
|
||||
}
|
||||
LeaveConnection(peer_server);
|
||||
CreateConnection(peer_server, mac_addr_str.c_str(), input_password);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Spacing();
|
||||
{
|
||||
{
|
||||
static char remote_id[20] = "";
|
||||
ImGui::Text("REMOTE ID:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(95);
|
||||
ImGui::InputTextWithHint("##remote_id", mac_addr_str.c_str(),
|
||||
remote_id, IM_ARRAYSIZE(remote_id),
|
||||
ImGuiInputTextFlags_CharsUppercase |
|
||||
ImGuiInputTextFlags_CharsNoBlank);
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text(" PASSWORD:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(95);
|
||||
static char client_password[20] = "";
|
||||
ImGui::InputTextWithHint("##client_pwd", "max 6 chars",
|
||||
client_password,
|
||||
IM_ARRAYSIZE(client_password),
|
||||
ImGuiInputTextFlags_CharsNoBlank);
|
||||
|
||||
if (ImGui::Button(connect_label)) {
|
||||
int ret = -1;
|
||||
if ("ClientSignalConnected" == client_signal_status_str) {
|
||||
if (strcmp(connect_label, "Connect") == 0 && !joined) {
|
||||
std::string user_id = "C-" + mac_addr_str;
|
||||
ret = JoinConnection(peer_client, remote_id, client_password);
|
||||
if (0 == ret) {
|
||||
// joined = true;
|
||||
}
|
||||
} else if (strcmp(connect_label, "Disconnect") == 0 && joined) {
|
||||
ret = LeaveConnection(peer_client);
|
||||
memset(audio_buffer, 0, 960);
|
||||
if (0 == ret) {
|
||||
joined = false;
|
||||
received_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == ret) {
|
||||
connect_button_pressed = !connect_button_pressed;
|
||||
connect_label =
|
||||
connect_button_pressed ? "Disconnect" : "Connect";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
{
|
||||
if (ImGui::Button("Resize Window")) {
|
||||
SDL_GetWindowSize(window, &window_w, &window_h);
|
||||
|
||||
if (window_h != window_w * 9 / 16) {
|
||||
window_w = window_h * 16 / 9;
|
||||
}
|
||||
|
||||
SDL_SetWindowSize(window, window_w, window_h);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
SDL_RenderSetScale(sdlRenderer, io.DisplayFramebufferScale.x,
|
||||
io.DisplayFramebufferScale.y);
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
if (event.type == SDL_QUIT) {
|
||||
done = true;
|
||||
} else if (event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
SDL_GetWindowSize(window, &window_w, &window_h);
|
||||
} else if (event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_CLOSE &&
|
||||
event.window.windowID == SDL_GetWindowID(window)) {
|
||||
done = true;
|
||||
} else if (event.type == REFRESH_EVENT) {
|
||||
sdlRect.x = 0;
|
||||
sdlRect.y = 0;
|
||||
sdlRect.w = window_w;
|
||||
sdlRect.h = window_h;
|
||||
|
||||
SDL_UpdateTexture(sdlTexture, NULL, dst_buffer, pixel_w);
|
||||
} else {
|
||||
if (joined) {
|
||||
ProcessMouseKeyEven(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderClear(sdlRenderer);
|
||||
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
|
||||
|
||||
if (!joined || !received_frame) {
|
||||
SDL_RenderClear(sdlRenderer);
|
||||
SDL_SetRenderDrawColor(
|
||||
sdlRenderer, (Uint8)(clear_color.x * 0), (Uint8)(clear_color.y * 0),
|
||||
(Uint8)(clear_color.z * 0), (Uint8)(clear_color.w * 0));
|
||||
}
|
||||
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_RenderPresent(sdlRenderer);
|
||||
|
||||
frame_count++;
|
||||
end_time = SDL_GetTicks();
|
||||
elapsed_time = end_time - start_time;
|
||||
if (elapsed_time >= 1000) {
|
||||
fps = frame_count / (elapsed_time / 1000);
|
||||
frame_count = 0;
|
||||
window_title = "Remote Desk Client FPS [" + std::to_string(fps) +
|
||||
"] status [" + server_signal_status_str + "|" +
|
||||
client_signal_status_str + "|" +
|
||||
server_connection_status_str + "|" +
|
||||
client_connection_status_str + "]";
|
||||
// For MacOS, UI frameworks can only be called from the main thread
|
||||
SDL_SetWindowTitle(window, window_title.c_str());
|
||||
start_time = end_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
|
||||
if (is_create_connection) {
|
||||
LeaveConnection(peer_server);
|
||||
}
|
||||
|
||||
if (joined) {
|
||||
LeaveConnection(peer_client);
|
||||
}
|
||||
|
||||
rtc_thread.join();
|
||||
SDL_CloseAudioDevice(output_dev);
|
||||
SDL_CloseAudioDevice(input_dev);
|
||||
|
||||
mouse_controller->Destroy();
|
||||
|
||||
ImGui_ImplSDLRenderer2_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
SDL_DestroyRenderer(sdlRenderer);
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) {
|
||||
Render render;
|
||||
render.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
142
src/localization/localization.h
Normal file
142
src/localization/localization.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* @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 = {
|
||||
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_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> 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"};
|
||||
} // namespace localization
|
||||
|
||||
#endif
|
||||
127
src/log/log.h
127
src/log/log.h
@@ -1,127 +0,0 @@
|
||||
#ifndef _LOG_H_
|
||||
#define _LOG_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "spdlog/common.h"
|
||||
#include "spdlog/logger.h"
|
||||
#include "spdlog/sinks/base_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
|
||||
|
||||
// SPDLOG_TRACE(...)
|
||||
// SPDLOG_DEBUG(...)
|
||||
// SPDLOG_INFO(...)
|
||||
// SPDLOG_WARN(...)
|
||||
// SPDLOG_ERROR(...)
|
||||
// SPDLOG_CRITICAL(...)
|
||||
|
||||
#ifdef SIGNAL_LOGGER
|
||||
constexpr auto LOGGER_NAME = "siganl_server";
|
||||
#else
|
||||
constexpr auto LOGGER_NAME = "remote_desk";
|
||||
#endif
|
||||
|
||||
#define LOG_INFO(...) \
|
||||
if (nullptr == spdlog::get(LOGGER_NAME)) { \
|
||||
auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \
|
||||
auto timet = std::chrono::system_clock::to_time_t(now); \
|
||||
auto localTime = *std::gmtime(&timet); \
|
||||
std::stringstream ss; \
|
||||
std::string filename; \
|
||||
ss << LOGGER_NAME; \
|
||||
ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \
|
||||
ss >> filename; \
|
||||
std::string path = "logs/" + filename; \
|
||||
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)); \
|
||||
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); \
|
||||
SPDLOG_LOGGER_INFO(combined_logger, __VA_ARGS__); \
|
||||
} else { \
|
||||
SPDLOG_LOGGER_INFO(spdlog::get(LOGGER_NAME), __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_WARN(...) \
|
||||
if (nullptr == spdlog::get(LOGGER_NAME)) { \
|
||||
auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \
|
||||
auto timet = std::chrono::system_clock::to_time_t(now); \
|
||||
auto localTime = *std::gmtime(&timet); \
|
||||
std::stringstream ss; \
|
||||
std::string filename; \
|
||||
ss << LOGGER_NAME; \
|
||||
ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \
|
||||
ss >> filename; \
|
||||
std::string path = "logs/" + filename; \
|
||||
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)); \
|
||||
auto combined_logger = std::make_shared<spdlog::logger>( \
|
||||
LOGGER_NAME, begin(sinks), end(sinks)); \
|
||||
spdlog::register_logger(combined_logger); \
|
||||
SPDLOG_LOGGER_WARN(combined_logger, __VA_ARGS__); \
|
||||
} else { \
|
||||
SPDLOG_LOGGER_WARN(spdlog::get(LOGGER_NAME), __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_ERROR(...) \
|
||||
if (nullptr == spdlog::get(LOGGER_NAME)) { \
|
||||
auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \
|
||||
auto timet = std::chrono::system_clock::to_time_t(now); \
|
||||
auto localTime = *std::gmtime(&timet); \
|
||||
std::stringstream ss; \
|
||||
std::string filename; \
|
||||
ss << LOGGER_NAME; \
|
||||
ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \
|
||||
ss >> filename; \
|
||||
std::string path = "logs/" + filename; \
|
||||
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)); \
|
||||
auto combined_logger = std::make_shared<spdlog::logger>( \
|
||||
LOGGER_NAME, begin(sinks), end(sinks)); \
|
||||
spdlog::register_logger(combined_logger); \
|
||||
SPDLOG_LOGGER_ERROR(combined_logger, __VA_ARGS__); \
|
||||
} else { \
|
||||
SPDLOG_LOGGER_ERROR(spdlog::get(LOGGER_NAME), __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_FATAL(...) \
|
||||
if (nullptr == spdlog::get(LOGGER_NAME)) { \
|
||||
auto now = std::chrono::system_clock::now() + std::chrono::hours(8); \
|
||||
auto timet = std::chrono::system_clock::to_time_t(now); \
|
||||
auto localTime = *std::gmtime(&timet); \
|
||||
std::stringstream ss; \
|
||||
std::string filename; \
|
||||
ss << LOGGER_NAME; \
|
||||
ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log"); \
|
||||
ss >> filename; \
|
||||
std::string path = "logs/" + filename; \
|
||||
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)); \
|
||||
auto combined_logger = std::make_shared<spdlog::logger>( \
|
||||
LOGGER_NAME, begin(sinks), end(sinks)); \
|
||||
spdlog::register_logger(combined_logger); \
|
||||
SPDLOG_LOGGER_CRITICAL(combined_logger, __VA_ARGS__); \
|
||||
} else { \
|
||||
SPDLOG_LOGGER_CRITICAL(spdlog::get(LOGGER_NAME), __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#endif
|
||||
62
src/log/rd_log.cpp
Normal file
62
src/log/rd_log.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "rd_log.h"
|
||||
|
||||
#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
|
||||
gmtime_r(&now_time, &tm_info);
|
||||
#endif
|
||||
|
||||
std::stringstream ss;
|
||||
ss << LOGGER_NAME;
|
||||
ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log");
|
||||
|
||||
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>(
|
||||
filename, 5 * 1024 * 1024, 3));
|
||||
|
||||
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 g_logger;
|
||||
}
|
||||
39
src/log/rd_log.h
Normal file
39
src/log/rd_log.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-07-21
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _RD_LOG_H_
|
||||
#define _RD_LOG_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "spdlog/common.h"
|
||||
#include "spdlog/logger.h"
|
||||
#include "spdlog/sinks/base_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
|
||||
|
||||
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__)
|
||||
|
||||
#endif
|
||||
91
src/path_manager/path_manager.cpp
Normal file
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
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,143 +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() {}
|
||||
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);
|
||||
// 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);
|
||||
y_plane_.resize(width_ * height_);
|
||||
uv_plane_.resize((width_ / 2) * (height_ / 2) * 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Destroy() {
|
||||
if (capture_thread_->joinable()) {
|
||||
capture_thread_->join();
|
||||
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_.reset(new std::thread([this]() {
|
||||
while (1) {
|
||||
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 (running_) return 0;
|
||||
running_ = true;
|
||||
paused_ = false;
|
||||
thread_ = std::thread([this]() {
|
||||
while (running_) {
|
||||
if (!paused_) OnFrame();
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Pause() { return 0; }
|
||||
int ScreenCapturerX11::Stop() {
|
||||
if (!running_) return 0;
|
||||
running_ = false;
|
||||
if (thread_.joinable()) thread_.join();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Resume() { return 0; }
|
||||
int ScreenCapturerX11::Pause(int monitor_index) {
|
||||
paused_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Stop() { 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,56 +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;
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
int Stop();
|
||||
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_ = 30;
|
||||
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;
|
||||
// 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::unique_ptr<std::thread> capture_thread_ = nullptr;
|
||||
// 缓冲区
|
||||
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,144 +0,0 @@
|
||||
#include "screen_capturer_avf.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
|
||||
|
||||
ScreenCapturerAvf::ScreenCapturerAvf() {}
|
||||
|
||||
ScreenCapturerAvf::~ScreenCapturerAvf() {}
|
||||
|
||||
int ScreenCapturerAvf::Init(const RECORD_DESKTOP_RECT &rect, 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", "1", 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);
|
||||
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();
|
||||
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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Destroy() {
|
||||
if (capture_thread_->joinable()) {
|
||||
capture_thread_->join();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Start() {
|
||||
capture_thread_.reset(new std::thread([this]() {
|
||||
while (1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Pause() { return 0; }
|
||||
|
||||
int ScreenCapturerAvf::Resume() { return 0; }
|
||||
|
||||
int ScreenCapturerAvf::Stop() { return 0; }
|
||||
|
||||
void ScreenCapturerAvf::OnFrame() {}
|
||||
|
||||
void ScreenCapturerAvf::CleanUp() {}
|
||||
@@ -1,85 +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 RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb);
|
||||
virtual int Destroy();
|
||||
|
||||
virtual int Start();
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
int Stop();
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
std::atomic_bool _running;
|
||||
std::atomic_bool _paused;
|
||||
std::atomic_bool _inited;
|
||||
|
||||
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;
|
||||
// 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::unique_ptr<std::thread> capture_thread_ = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
73
src/screen_capturer/macosx/screen_capturer_sck.cpp
Normal file
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() {}
|
||||
59
src/screen_capturer/macosx/screen_capturer_sck.h
Normal file
59
src/screen_capturer/macosx/screen_capturer_sck.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-10-17
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_SCK_H_
|
||||
#define _SCREEN_CAPTURER_SCK_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
class ScreenCapturerSck : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerSck();
|
||||
~ScreenCapturerSck();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ScreenCapturer> CreateScreenCapturerSck();
|
||||
|
||||
private:
|
||||
int _fps;
|
||||
cb_desktop_data on_data_;
|
||||
unsigned char* nv12_frame_ = nullptr;
|
||||
bool inited_ = false;
|
||||
|
||||
// thread
|
||||
std::thread capture_thread_;
|
||||
std::atomic_bool running_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ScreenCapturer> screen_capturer_sck_impl_;
|
||||
};
|
||||
|
||||
#endif
|
||||
488
src/screen_capturer/macosx/screen_capturer_sck_impl.mm
Normal file
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_ = 30;
|
||||
|
||||
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,25 +9,26 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "display_info.h"
|
||||
|
||||
class ScreenCapturer {
|
||||
public:
|
||||
typedef struct {
|
||||
int left;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
} RECORD_DESKTOP_RECT;
|
||||
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() {}
|
||||
|
||||
public:
|
||||
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb) = 0;
|
||||
virtual int Init(const int fps, cb_desktop_data cb) = 0;
|
||||
virtual int Destroy() = 0;
|
||||
|
||||
virtual int 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
|
||||
@@ -8,12 +8,12 @@
|
||||
#define _SCREEN_CAPTURER_FACTORY_H_
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "screen_capturer_wgc.h"
|
||||
#elif __linux__
|
||||
#include "screen_capturer_x11.h"
|
||||
#elif __APPLE__
|
||||
#include "screen_capturer_avf.h"
|
||||
// #include "screen_capturer_avf.h"
|
||||
#include "screen_capturer_sck.h"
|
||||
#endif
|
||||
|
||||
class ScreenCapturerFactory {
|
||||
@@ -27,7 +27,8 @@ class ScreenCapturerFactory {
|
||||
#elif __linux__
|
||||
return new ScreenCapturerX11();
|
||||
#elif __APPLE__
|
||||
return new ScreenCapturerAvf();
|
||||
// return new ScreenCapturerAvf();
|
||||
return new ScreenCapturerSck();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
@@ -5,64 +5,76 @@
|
||||
#include <winrt/Windows.Foundation.Metadata.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int BGRAToNV12FFmpeg(unsigned char *src_buffer, int width, int height,
|
||||
unsigned char *dst_buffer) {
|
||||
AVFrame *Input_pFrame = av_frame_alloc();
|
||||
AVFrame *Output_pFrame = av_frame_alloc();
|
||||
struct SwsContext *img_convert_ctx =
|
||||
sws_getContext(width, height, AV_PIX_FMT_BGRA, 1280, 720, AV_PIX_FMT_NV12,
|
||||
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
av_image_fill_arrays(Input_pFrame->data, Input_pFrame->linesize, src_buffer,
|
||||
AV_PIX_FMT_BGRA, width, height, 1);
|
||||
av_image_fill_arrays(Output_pFrame->data, Output_pFrame->linesize, dst_buffer,
|
||||
AV_PIX_FMT_NV12, 1280, 720, 1);
|
||||
static std::vector<DisplayInfo> gs_display_list;
|
||||
|
||||
sws_scale(img_convert_ctx, (uint8_t const **)Input_pFrame->data,
|
||||
Input_pFrame->linesize, 0, height, Output_pFrame->data,
|
||||
Output_pFrame->linesize);
|
||||
|
||||
if (Input_pFrame) av_free(Input_pFrame);
|
||||
if (Output_pFrame) av_free(Output_pFrame);
|
||||
if (img_convert_ctx) sws_freeContext(img_convert_ctx);
|
||||
|
||||
return 0;
|
||||
std::string WideToUtf8(const wchar_t *wideStr) {
|
||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
std::string result(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr,
|
||||
nullptr);
|
||||
result.pop_back();
|
||||
return result;
|
||||
}
|
||||
|
||||
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc,
|
||||
LPARAM data) {
|
||||
MONITORINFOEX info_ex;
|
||||
info_ex.cbSize = sizeof(MONITORINFOEX);
|
||||
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
|
||||
[[maybe_unused]] LPRECT lprc, LPARAM data) {
|
||||
MONITORINFOEX monitor_info_;
|
||||
monitor_info_.cbSize = sizeof(MONITORINFOEX);
|
||||
|
||||
GetMonitorInfo(hmonitor, &info_ex);
|
||||
|
||||
if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
|
||||
|
||||
if (info_ex.dwFlags & MONITORINFOF_PRIMARY) {
|
||||
*(HMONITOR *)data = hmonitor;
|
||||
if (GetMonitorInfo(hmonitor, &monitor_info_)) {
|
||||
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
|
||||
gs_display_list.insert(
|
||||
gs_display_list.begin(),
|
||||
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
|
||||
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
||||
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
||||
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
|
||||
*(HMONITOR *)data = hmonitor;
|
||||
} else {
|
||||
gs_display_list.push_back(DisplayInfo(
|
||||
(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
|
||||
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
|
||||
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
|
||||
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HMONITOR GetPrimaryMonitor() {
|
||||
HMONITOR hmonitor = nullptr;
|
||||
|
||||
gs_display_list.clear();
|
||||
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor);
|
||||
|
||||
return hmonitor;
|
||||
}
|
||||
|
||||
ScreenCapturerWgc::ScreenCapturerWgc() {}
|
||||
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
|
||||
|
||||
ScreenCapturerWgc::~ScreenCapturerWgc() {}
|
||||
ScreenCapturerWgc::~ScreenCapturerWgc() {
|
||||
Stop();
|
||||
CleanUp();
|
||||
|
||||
if (nv12_frame_) {
|
||||
delete nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
}
|
||||
|
||||
if (nv12_frame_scaled_) {
|
||||
delete nv12_frame_scaled_;
|
||||
nv12_frame_scaled_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenCapturerWgc::IsWgcSupported() {
|
||||
try {
|
||||
@@ -76,105 +88,185 @@ bool ScreenCapturerWgc::IsWgcSupported() {
|
||||
}
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb) {
|
||||
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
|
||||
int error = 0;
|
||||
if (_inited == true) return error;
|
||||
if (inited_ == true) return error;
|
||||
|
||||
nv12_frame_ = new unsigned char[rect.right * rect.bottom * 4];
|
||||
// nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2];
|
||||
// nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2];
|
||||
|
||||
_fps = fps;
|
||||
fps_ = fps;
|
||||
|
||||
_on_data = cb;
|
||||
on_data_ = cb;
|
||||
|
||||
do {
|
||||
if (!IsWgcSupported()) {
|
||||
std::cout << "AE_UNSUPPORT" << std::endl;
|
||||
error = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
session_ = new WgcSessionImpl();
|
||||
if (!session_) {
|
||||
error = -1;
|
||||
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
session_->RegisterObserver(this);
|
||||
|
||||
error = session_->Initialize(GetPrimaryMonitor());
|
||||
|
||||
_inited = true;
|
||||
} while (0);
|
||||
|
||||
if (error != 0) {
|
||||
if (!IsWgcSupported()) {
|
||||
LOG_ERROR("WGC not supported");
|
||||
error = 2;
|
||||
return error;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
monitor_ = GetPrimaryMonitor();
|
||||
|
||||
int ScreenCapturerWgc::Destroy() {
|
||||
if (nv12_frame_) {
|
||||
delete nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
display_info_list_ = gs_display_list;
|
||||
|
||||
if (display_info_list_.empty()) {
|
||||
LOG_ERROR("No display found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Stop();
|
||||
CleanUp();
|
||||
for (int i = 0; i < display_info_list_.size(); i++) {
|
||||
const auto &display = display_info_list_[i];
|
||||
LOG_INFO(
|
||||
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
|
||||
"({}, {})",
|
||||
i, display.name, (display.is_primary ? "yes" : "no"), display.left,
|
||||
display.top, display.right, display.bottom);
|
||||
|
||||
sessions_.push_back(
|
||||
{std::make_unique<WgcSessionImpl>(i), false, false, false});
|
||||
sessions_.back().session_->RegisterObserver(this);
|
||||
error = sessions_.back().session_->Initialize((HMONITOR)display.handle);
|
||||
if (error != 0) {
|
||||
return error;
|
||||
}
|
||||
sessions_[i].inited_ = true;
|
||||
inited_ = true;
|
||||
}
|
||||
|
||||
LOG_INFO("Default on monitor {}:{}", monitor_index_,
|
||||
display_info_list_[monitor_index_].name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Destroy() { return 0; }
|
||||
|
||||
int ScreenCapturerWgc::Start() {
|
||||
if (_running == true) {
|
||||
std::cout << "record desktop duplication is already running" << std::endl;
|
||||
if (running_ == true) {
|
||||
LOG_ERROR("Screen capturer already running");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_inited == false) {
|
||||
std::cout << "AE_NEED_INIT" << std::endl;
|
||||
if (inited_ == false) {
|
||||
LOG_ERROR("Screen capturer not inited");
|
||||
return 4;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
session_->Start();
|
||||
for (int i = 0; i < sessions_.size(); i++) {
|
||||
if (sessions_[i].inited_ == false) {
|
||||
LOG_ERROR("Session {} not inited", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sessions_[i].running_) {
|
||||
LOG_ERROR("Session {} is already running", i);
|
||||
} else {
|
||||
sessions_[i].session_->Start();
|
||||
|
||||
if (i != 0) {
|
||||
sessions_[i].session_->Pause();
|
||||
sessions_[i].paused_ = true;
|
||||
}
|
||||
sessions_[i].running_ = true;
|
||||
}
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Pause() {
|
||||
_paused = true;
|
||||
if (session_) session_->Pause();
|
||||
int ScreenCapturerWgc::Pause(int monitor_index) {
|
||||
if (monitor_index >= sessions_.size() || monitor_index < 0) {
|
||||
LOG_ERROR("Invalid session index: {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!sessions_[monitor_index].paused_) {
|
||||
sessions_[monitor_index].session_->Pause();
|
||||
sessions_[monitor_index].paused_ = true;
|
||||
LOG_INFO("Pausing session {}", monitor_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Resume() {
|
||||
_paused = false;
|
||||
if (session_) session_->Resume();
|
||||
int ScreenCapturerWgc::Resume(int monitor_index) {
|
||||
if (monitor_index >= sessions_.size() || monitor_index < 0) {
|
||||
LOG_ERROR("Invalid session index: {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sessions_[monitor_index].paused_) {
|
||||
sessions_[monitor_index].session_->Resume();
|
||||
sessions_[monitor_index].paused_ = false;
|
||||
LOG_INFO("Resuming session {}", monitor_index);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerWgc::Stop() {
|
||||
_running = false;
|
||||
|
||||
if (session_) session_->Stop();
|
||||
for (int i = 0; i < sessions_.size(); i++) {
|
||||
if (sessions_[i].running_) {
|
||||
sessions_[i].session_->Stop();
|
||||
sessions_[i].running_ = false;
|
||||
}
|
||||
}
|
||||
running_ = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
|
||||
if (_on_data)
|
||||
BGRAToNV12FFmpeg((unsigned char *)frame.data, frame.width, frame.height,
|
||||
nv12_frame_);
|
||||
_on_data(nv12_frame_, frame.width * frame.height * 4, frame.width,
|
||||
frame.height);
|
||||
int ScreenCapturerWgc::SwitchTo(int monitor_index) {
|
||||
if (monitor_index_ == monitor_index) {
|
||||
LOG_INFO("Already on monitor {}:{}", monitor_index_ + 1,
|
||||
display_info_list_[monitor_index_].name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (monitor_index >= display_info_list_.size()) {
|
||||
LOG_ERROR("Invalid monitor index: {}", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!sessions_[monitor_index].inited_) {
|
||||
LOG_ERROR("Monitor {} not inited", monitor_index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Pause(monitor_index_);
|
||||
|
||||
monitor_index_ = monitor_index;
|
||||
LOG_INFO("Switching to monitor {}:{}", monitor_index_,
|
||||
display_info_list_[monitor_index_].name);
|
||||
|
||||
Resume(monitor_index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame,
|
||||
int id) {
|
||||
if (on_data_) {
|
||||
if (!nv12_frame_) {
|
||||
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
|
||||
}
|
||||
|
||||
libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4,
|
||||
(uint8_t *)nv12_frame_, frame.width,
|
||||
(uint8_t *)(nv12_frame_ + frame.width * frame.height),
|
||||
frame.width, frame.width, frame.height);
|
||||
|
||||
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
|
||||
frame.height, display_info_list_[id].name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerWgc::CleanUp() {
|
||||
_inited = false;
|
||||
|
||||
if (session_) session_->Release();
|
||||
|
||||
session_ = nullptr;
|
||||
if (inited_) {
|
||||
for (auto &session : sessions_) {
|
||||
if (session.session_) {
|
||||
session.session_->Stop();
|
||||
}
|
||||
}
|
||||
sessions_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
#include "wgc_session.h"
|
||||
@@ -19,39 +20,49 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
||||
public:
|
||||
bool IsWgcSupported();
|
||||
|
||||
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb);
|
||||
virtual int Destroy();
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Stop() override;
|
||||
|
||||
virtual int Start();
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
int Stop();
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
|
||||
|
||||
void OnFrame(const WgcSession::wgc_session_frame &frame);
|
||||
int SwitchTo(int monitor_index);
|
||||
|
||||
void OnFrame(const WgcSession::wgc_session_frame &frame, int id);
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
WgcSession *session_ = nullptr;
|
||||
HMONITOR monitor_;
|
||||
MONITORINFOEX monitor_info_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
int monitor_index_ = 0;
|
||||
|
||||
std::atomic_bool _running;
|
||||
std::atomic_bool _paused;
|
||||
std::atomic_bool _inited;
|
||||
private:
|
||||
class WgcSessionInfo {
|
||||
public:
|
||||
std::unique_ptr<WgcSession> session_;
|
||||
bool inited_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
};
|
||||
|
||||
std::thread _thread;
|
||||
std::vector<WgcSessionInfo> sessions_;
|
||||
|
||||
std::string _device_name;
|
||||
std::atomic_bool running_;
|
||||
std::atomic_bool inited_;
|
||||
|
||||
RECORD_DESKTOP_RECT _rect;
|
||||
int fps_;
|
||||
|
||||
int _fps;
|
||||
|
||||
cb_desktop_data _on_data;
|
||||
cb_desktop_data on_data_ = nullptr;
|
||||
|
||||
unsigned char *nv12_frame_ = nullptr;
|
||||
unsigned char *nv12_frame_scaled_ = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -16,7 +16,7 @@ class WgcSession {
|
||||
class wgc_session_observer {
|
||||
public:
|
||||
virtual ~wgc_session_observer() {}
|
||||
virtual void OnFrame(const wgc_session_frame &frame) = 0;
|
||||
virtual void OnFrame(const wgc_session_frame &frame, int id) = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -33,7 +33,6 @@ class WgcSession {
|
||||
virtual int Pause() = 0;
|
||||
virtual int Resume() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~WgcSession(){};
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
|
||||
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
|
||||
}
|
||||
|
||||
WgcSessionImpl::WgcSessionImpl() {}
|
||||
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
|
||||
|
||||
WgcSessionImpl::~WgcSessionImpl() {
|
||||
Stop();
|
||||
@@ -89,6 +89,8 @@ int WgcSessionImpl::Start() {
|
||||
|
||||
capture_session_.StartCapture();
|
||||
|
||||
capture_session_.IsCursorCaptureEnabled(false);
|
||||
|
||||
error = 0;
|
||||
} catch (winrt::hresult_error) {
|
||||
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
|
||||
@@ -122,6 +124,8 @@ int WgcSessionImpl::Stop() {
|
||||
int WgcSessionImpl::Pause() {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
is_paused_ = true;
|
||||
|
||||
CHECK_INIT;
|
||||
return 0;
|
||||
}
|
||||
@@ -129,6 +133,8 @@ int WgcSessionImpl::Pause() {
|
||||
int WgcSessionImpl::Resume() {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
is_paused_ = false;
|
||||
|
||||
CHECK_INIT;
|
||||
return 0;
|
||||
}
|
||||
@@ -146,7 +152,7 @@ auto WgcSessionImpl::CreateD3D11Device() {
|
||||
|
||||
if (DXGI_ERROR_UNSUPPORTED == hr) {
|
||||
// change D3D_DRIVER_TYPE
|
||||
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_WARP;
|
||||
type = D3D_DRIVER_TYPE_WARP;
|
||||
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
|
||||
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
|
||||
nullptr);
|
||||
@@ -211,7 +217,7 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
|
||||
|
||||
void WgcSessionImpl::OnFrame(
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender,
|
||||
winrt::Windows::Foundation::IInspectable const &args) {
|
||||
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const &args) {
|
||||
std::lock_guard locker(lock_);
|
||||
|
||||
auto is_new_size = false;
|
||||
@@ -231,6 +237,10 @@ void WgcSessionImpl::OnFrame(
|
||||
|
||||
// copy to mapped texture
|
||||
{
|
||||
if (is_paused_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto frame_captured =
|
||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||
|
||||
@@ -254,11 +264,13 @@ void WgcSessionImpl::OnFrame(
|
||||
|
||||
// copy data from map_result.pData
|
||||
if (map_result.pData && observer_) {
|
||||
observer_->OnFrame(wgc_session_frame{
|
||||
static_cast<unsigned int>(frame_size.Width),
|
||||
static_cast<unsigned int>(frame_size.Height), map_result.RowPitch,
|
||||
const_cast<const unsigned char *>(
|
||||
(unsigned char *)map_result.pData)});
|
||||
observer_->OnFrame(
|
||||
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
|
||||
static_cast<unsigned int>(frame_size.Height),
|
||||
map_result.RowPitch,
|
||||
const_cast<const unsigned char *>(
|
||||
(unsigned char *)map_result.pData)},
|
||||
id_);
|
||||
}
|
||||
|
||||
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
|
||||
|
||||
@@ -35,7 +35,7 @@ class WgcSessionImpl : public WgcSession {
|
||||
} target_{0};
|
||||
|
||||
public:
|
||||
WgcSessionImpl();
|
||||
WgcSessionImpl(int id);
|
||||
~WgcSessionImpl() override;
|
||||
|
||||
public:
|
||||
@@ -72,6 +72,7 @@ class WgcSessionImpl : public WgcSession {
|
||||
// void message_func();
|
||||
|
||||
private:
|
||||
int id_ = -1;
|
||||
std::mutex lock_;
|
||||
bool is_initialized_ = false;
|
||||
bool is_running_ = false;
|
||||
|
||||
1408
src/single_window/IconsFontAwesome6.h
Normal file
1408
src/single_window/IconsFontAwesome6.h
Normal file
File diff suppressed because it is too large
Load Diff
55
src/single_window/about_window.cpp
Normal file
55
src/single_window/about_window.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "layout_style.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::AboutWindow() {
|
||||
if (show_about_window_) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(
|
||||
(viewport->WorkSize.x - viewport->WorkPos.x - about_window_width_) / 2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y - about_window_height_) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height_));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::Begin(
|
||||
localization::about[localization_language_index_].c_str(), nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
|
||||
std::string version;
|
||||
#ifdef RD_VERSION
|
||||
version = RD_VERSION;
|
||||
#else
|
||||
version = "Unknown";
|
||||
#endif
|
||||
|
||||
std::string text =
|
||||
localization::version[localization_language_index_] + ": " + version;
|
||||
ImGui::Text("%s", text.c_str());
|
||||
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
|
||||
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
|
||||
// OK
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str())) {
|
||||
show_about_window_ = false;
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::End();
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
179
src/single_window/connection_status_window.cpp
Normal file
179
src/single_window/connection_status_window.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "layout_style.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::ConnectionStatusWindow(
|
||||
std::shared_ptr<SubStreamWindowProperties> &props) {
|
||||
if (show_connection_status_window_) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
|
||||
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_);
|
||||
client_properties_.erase(props->remote_id_);
|
||||
}
|
||||
}
|
||||
|
||||
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 0;
|
||||
}
|
||||
325
src/single_window/control_bar.cpp
Normal file
325
src/single_window/control_bar.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
#include "layout_style.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_, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
} else {
|
||||
SDL_SetWindowFullscreen(stream_window_, SDL_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_ - 40.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::TableNextRow();
|
||||
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::TableNextRow();
|
||||
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::TableNextRow();
|
||||
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::TableNextRow();
|
||||
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::EndTable();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
223
src/single_window/control_window.cpp
Normal file
223
src/single_window/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;
|
||||
}
|
||||
39
src/single_window/layout_style.h
Normal file
39
src/single_window/layout_style.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* @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 181
|
||||
#define SETTINGS_WINDOW_WIDTH_EN 228
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 220
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 220
|
||||
#define LANGUAGE_SELECT_WINDOW_PADDING_CN 100
|
||||
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 147
|
||||
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 100
|
||||
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 147
|
||||
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 100
|
||||
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 147
|
||||
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 151
|
||||
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 198
|
||||
#define ENABLE_TURN_CHECKBOX_PADDING_CN 151
|
||||
#define ENABLE_TURN_CHECKBOX_PADDING_EN 198
|
||||
#define SETTINGS_SELECT_WINDOW_WIDTH 73
|
||||
#define SETTINGS_OK_BUTTON_PADDING_CN 55
|
||||
#define SETTINGS_OK_BUTTON_PADDING_EN 78
|
||||
|
||||
#endif
|
||||
303
src/single_window/local_peer_window.cpp
Normal file
303
src/single_window/local_peer_window.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#include <random>
|
||||
|
||||
#include "layout_style.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::LocalWindow() {
|
||||
ImGui::SetNextWindowPos(ImVec2(-1.0f, title_bar_height_), ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::BeginChild("LocalDesktopWindow",
|
||||
ImVec2(local_window_width_, local_window_height_),
|
||||
ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_);
|
||||
ImGui::Indent(main_child_window_x_padding_);
|
||||
|
||||
ImGui::TextColored(
|
||||
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
||||
localization::local_desktop[localization_language_index_].c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
{
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(main_child_window_x_padding_,
|
||||
title_bar_height_ + main_child_window_y_padding_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255,
|
||||
242.0f / 255, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
||||
ImGui::BeginChild(
|
||||
"LocalDesktopWindow_1",
|
||||
ImVec2(local_child_window_width_, local_child_window_height_),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
{
|
||||
ImGui::SetWindowFontScale(0.8f);
|
||||
ImGui::Text("%s",
|
||||
localization::local_id[localization_language_index_].c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
|
||||
if (strcmp(client_id_display_, client_id_)) {
|
||||
for (int i = 0, j = 0; i < sizeof(client_id_); i++, j++) {
|
||||
client_id_display_[j] = client_id_[i];
|
||||
if (i == 2 || i == 5) {
|
||||
client_id_display_[++j] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::InputText(
|
||||
"##local_id", client_id_display_, IM_ARRAYSIZE(client_id_display_),
|
||||
ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
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))) {
|
||||
local_id_copied_ = true;
|
||||
ImGui::SetClipboardText(client_id_);
|
||||
copy_start_time_ = ImGui::GetTime();
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
double time_duration = ImGui::GetTime() - copy_start_time_;
|
||||
if (local_id_copied_ && time_duration < 1.0f) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
notification_window_width_) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
notification_window_height_) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(notification_window_width_, notification_window_height_));
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_WindowBg,
|
||||
ImVec4(1.0f, 1.0f, 1.0f, 1.0f - (float)time_duration));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
auto window_width = ImGui::GetWindowSize().x;
|
||||
auto window_height = ImGui::GetWindowSize().y;
|
||||
ImGui::SetWindowFontScale(0.8f);
|
||||
std::string text = localization::local_id_copied_to_clipboard
|
||||
[localization_language_index_];
|
||||
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
|
||||
ImGui::SetCursorPosY(window_height * 0.5f);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,
|
||||
ImVec4(0, 0, 0, 1.0f - (float)time_duration));
|
||||
ImGui::Text("%s", text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::SetWindowFontScale(0.8f);
|
||||
ImGui::Text("%s",
|
||||
localization::password[localization_language_index_].c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH);
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
ImGui::InputTextWithHint(
|
||||
"##server_pwd",
|
||||
localization::max_password_len[localization_language_index_].c_str(),
|
||||
password_saved_, IM_ARRAYSIZE(password_saved_),
|
||||
show_password_
|
||||
? ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_ReadOnly
|
||||
: ImGuiInputTextFlags_CharsNoBlank |
|
||||
ImGuiInputTextFlags_Password |
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0));
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
auto l_x = ImGui::GetCursorScreenPos().x;
|
||||
auto l_y = ImGui::GetCursorScreenPos().y;
|
||||
if (ImGui::Button(ICON_FA_EYE, ImVec2(22, 38))) {
|
||||
show_password_ = !show_password_;
|
||||
}
|
||||
|
||||
if (!show_password_) {
|
||||
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);
|
||||
draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 11.0f),
|
||||
ImVec2(l_x + 20.3f, l_y + 25.0f),
|
||||
IM_COL32(0, 0, 0, 255), 1.5f);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(
|
||||
regenerate_password_ ? ICON_FA_SPINNER : ICON_FA_ARROWS_ROTATE,
|
||||
ImVec2(22, 38))) {
|
||||
regenerate_password_ = true;
|
||||
regenerate_password_start_time_ = ImGui::GetTime();
|
||||
LeaveConnection(peer_, client_id_);
|
||||
}
|
||||
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;
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
if (show_reset_password_window_) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
|
||||
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("ResetPasswordWindow", nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
auto window_width = ImGui::GetWindowSize().x;
|
||||
auto window_height = ImGui::GetWindowSize().y;
|
||||
std::string text =
|
||||
localization::new_password[localization_language_index_];
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
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::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;
|
||||
}
|
||||
|
||||
bool enter_pressed = ImGui::InputText(
|
||||
"##new_password", new_password_, IM_ARRAYSIZE(new_password_),
|
||||
ImGuiInputTextFlags_CharsNoBlank |
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
|
||||
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()) ||
|
||||
enter_pressed) {
|
||||
if (6 != strlen(new_password_)) {
|
||||
LOG_ERROR("Invalid password length");
|
||||
show_reset_password_window_ = true;
|
||||
focus_on_input_widget_ = true;
|
||||
} else {
|
||||
show_reset_password_window_ = false;
|
||||
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_);
|
||||
DestroyPeer(&peer_);
|
||||
focus_on_input_widget_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str())) {
|
||||
show_reset_password_window_ = false;
|
||||
focus_on_input_widget_ = true;
|
||||
memset(new_password_, 0, sizeof(new_password_));
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return 0;
|
||||
}
|
||||
37
src/single_window/main_window.cpp
Normal file
37
src/single_window/main_window.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::MainWindow() {
|
||||
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::BeginChild("DeskWindow",
|
||||
ImVec2(main_window_width_default_, local_window_height_),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
LocalWindow();
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddLine(
|
||||
ImVec2(main_window_width_default_ / 2, title_bar_height_ + 15.0f),
|
||||
ImVec2(main_window_width_default_ / 2, title_bar_height_ + 225.0f),
|
||||
IM_COL32(0, 0, 0, 122), 1.0f);
|
||||
|
||||
RemoteWindow();
|
||||
ImGui::EndChild();
|
||||
|
||||
RecentConnectionsWindow();
|
||||
StatusBar();
|
||||
|
||||
for (auto& it : client_properties_) {
|
||||
ConnectionStatusWindow(it.second);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
286
src/single_window/recent_connections.cpp
Normal file
286
src/single_window/recent_connections.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::RecentConnectionsWindow() {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(0, title_bar_height_ + local_window_height_ - 1.0f),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::BeginChild(
|
||||
"RecentConnectionsWindow",
|
||||
ImVec2(main_window_width_default_,
|
||||
main_window_height_default_ - title_bar_height_ -
|
||||
local_window_height_ - status_bar_height_ + 1.0f),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_);
|
||||
ImGui::Indent(main_child_window_x_padding_);
|
||||
|
||||
ImGui::TextColored(
|
||||
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
||||
localization::recent_connections[localization_language_index_].c_str());
|
||||
|
||||
ShowRecentConnections();
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ShowRecentConnections() {
|
||||
ImGui::SetCursorPosX(25.0f);
|
||||
ImVec2 sub_window_pos = ImGui::GetCursorPos();
|
||||
std::map<std::string, ImVec2> sub_containers_pos;
|
||||
float recent_connection_sub_container_width =
|
||||
recent_connection_image_width_ + 16.0f;
|
||||
float recent_connection_sub_container_height =
|
||||
recent_connection_image_height_ + 36.0f;
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg,
|
||||
ImVec4(239.0f / 255, 240.0f / 255, 242.0f / 255, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
||||
ImGui::BeginChild("RecentConnectionsContainer",
|
||||
ImVec2(main_window_width_default_ - 50.0f, 145.0f),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_AlwaysHorizontalScrollbar |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
size_t recent_connections_count = recent_connections_.size();
|
||||
int count = 0;
|
||||
float button_width = 22;
|
||||
float button_height = 22;
|
||||
for (auto& it : recent_connections_) {
|
||||
sub_containers_pos[it.first] = ImGui::GetCursorPos();
|
||||
std::string recent_connection_sub_window_name =
|
||||
"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),
|
||||
ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
std::string connection_info = it.first;
|
||||
|
||||
// remote id length is 9
|
||||
// password length is 6
|
||||
// connection_info -> remote_id + 'Y' + host_name + '@' + password
|
||||
// -> remote_id + 'N' + host_name
|
||||
if ('Y' == connection_info[9] && connection_info.size() >= 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) {
|
||||
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 {
|
||||
it.second.remote_host_name = "unknown";
|
||||
}
|
||||
|
||||
ImVec2 image_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x + 5.0f,
|
||||
ImGui::GetCursorScreenPos().y + 5.0f);
|
||||
ImVec2 image_pos =
|
||||
ImVec2(ImGui::GetCursorPosX() + 5.0f, ImGui::GetCursorPosY() + 5.0f);
|
||||
ImGui::SetCursorPos(image_pos);
|
||||
ImGui::Image((ImTextureID)(intptr_t)it.second.texture,
|
||||
ImVec2((float)recent_connection_image_width_,
|
||||
(float)recent_connection_image_height_));
|
||||
|
||||
// remote id display button
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.2f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0.2f));
|
||||
|
||||
ImVec2 dummy_button_pos =
|
||||
ImVec2(image_pos.x, image_pos.y + recent_connection_image_height_);
|
||||
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(),
|
||||
ImVec2(recent_connection_image_width_ - 2 * button_width,
|
||||
button_height));
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(dummy_button_pos.x + 2.0f, dummy_button_pos.y + 1.0f));
|
||||
ImGui::SetWindowFontScale(0.65f);
|
||||
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", it.second.remote_host_name.c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
ImVec4(1.0f, 1.0f, 1.0f, 0.7f));
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
// trash button
|
||||
{
|
||||
ImVec2 trash_can_button_pos = ImVec2(
|
||||
image_pos.x + recent_connection_image_width_ - 2 * button_width,
|
||||
image_pos.y + recent_connection_image_height_);
|
||||
ImGui::SetCursorPos(trash_can_button_pos);
|
||||
std::string trash_can = ICON_FA_TRASH_CAN;
|
||||
std::string recent_connection_delete_button_name =
|
||||
trash_can + "##RecentConnectionDelete" +
|
||||
std::to_string(trash_can_button_pos.x);
|
||||
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_ && delete_connection_name_ == it.first) {
|
||||
if (!thumbnail_->DeleteThumbnail(it.first)) {
|
||||
reload_recent_connections_ = true;
|
||||
delete_connection_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// connect button
|
||||
{
|
||||
ImVec2 connect_button_pos =
|
||||
ImVec2(image_pos.x + recent_connection_image_width_ - button_width,
|
||||
image_pos.y + recent_connection_image_height_);
|
||||
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;
|
||||
if (ImGui::Button(connect_to_this_connection_button_name.c_str(),
|
||||
ImVec2(button_width, button_height))) {
|
||||
ConnectTo(it.second.remote_id, it.second.password.c_str(),
|
||||
it.second.remember_password);
|
||||
}
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
if (count != recent_connections_count - 1) {
|
||||
ImVec2 line_start =
|
||||
ImVec2(image_screen_pos.x + recent_connection_image_width_ + 20.0f,
|
||||
image_screen_pos.y);
|
||||
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,
|
||||
IM_COL32(0, 0, 0, 122), 1.0f);
|
||||
}
|
||||
|
||||
count++;
|
||||
ImGui::SameLine(0, count != recent_connections_count ? 26.0f : 0.0f);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
if (show_confirm_delete_connection_) {
|
||||
ConfirmDeleteConnection();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ConfirmDeleteConnection() {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
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("ConfirmDeleteConnectionWindow", nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
std::string text =
|
||||
localization::confirm_delete_connection[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 6 / 19);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
|
||||
// ok
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
|
||||
delete_connection_ = true;
|
||||
show_confirm_delete_connection_ = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
delete_connection_ = false;
|
||||
show_confirm_delete_connection_ = false;
|
||||
}
|
||||
|
||||
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 0;
|
||||
}
|
||||
180
src/single_window/remote_peer_window.cpp
Normal file
180
src/single_window/remote_peer_window.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "layout_style.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData *data);
|
||||
|
||||
int Render::RemoteWindow() {
|
||||
ImGui::SetNextWindowPos(ImVec2(local_window_width_ + 1.0f, title_bar_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::BeginChild("RemoteDesktopWindow",
|
||||
ImVec2(remote_window_width_, remote_window_height_),
|
||||
ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_);
|
||||
ImGui::Indent(main_child_window_x_padding_ - 1.0f);
|
||||
|
||||
ImGui::TextColored(
|
||||
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
||||
localization::remote_desktop[localization_language_index_].c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
{
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(local_window_width_ + main_child_window_x_padding_ - 1.0f,
|
||||
title_bar_height_ + main_child_window_y_padding_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255,
|
||||
242.0f / 255, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
||||
|
||||
ImGui::BeginChild(
|
||||
"RemoteDesktopWindow_1",
|
||||
ImVec2(remote_child_window_width_, remote_child_window_height_),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
{
|
||||
ImGui::SetWindowFontScale(0.8f);
|
||||
ImGui::Text(
|
||||
"%s", localization::remote_id[localization_language_index_].c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
if (re_enter_remote_id_) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
re_enter_remote_id_ = false;
|
||||
memset(remote_id_display_, 0, sizeof(remote_id_display_));
|
||||
}
|
||||
bool enter_pressed = ImGui::InputText(
|
||||
"##remote_id_", remote_id_display_, IM_ARRAYSIZE(remote_id_display_),
|
||||
ImGuiInputTextFlags_CharsDecimal |
|
||||
ImGuiInputTextFlags_EnterReturnsTrue |
|
||||
ImGuiInputTextFlags_CallbackEdit,
|
||||
InputTextCallback);
|
||||
|
||||
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;
|
||||
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 (!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();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData *data) {
|
||||
if (data->BufTextLen > 3 && data->Buf[3] != ' ') {
|
||||
data->InsertChars(3, " ");
|
||||
}
|
||||
|
||||
if (data->BufTextLen > 7 && data->Buf[7] != ' ') {
|
||||
data->InsertChars(7, " ");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ConnectTo(const std::string &remote_id, const char *password,
|
||||
bool remember_password) {
|
||||
LOG_INFO("Connect to [{}]", 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 [{}] instance failed", props->local_id_);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
1379
src/single_window/render.cpp
Normal file
1379
src/single_window/render.cpp
Normal file
File diff suppressed because it is too large
Load Diff
438
src/single_window/render.h
Normal file
438
src/single_window/render.h
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* @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 <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_sdl2.h"
|
||||
#include "imgui_impl_sdlrenderer2.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"
|
||||
|
||||
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_ = 150;
|
||||
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;
|
||||
int mouse_pos_x_ = 0;
|
||||
int mouse_pos_y_ = 0;
|
||||
int mouse_pos_x_last_ = 0;
|
||||
int 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;
|
||||
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;
|
||||
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();
|
||||
|
||||
private:
|
||||
int CreateStreamRenderWindow();
|
||||
int TitleBar(bool main_window);
|
||||
int MainWindow();
|
||||
int StreamWindow();
|
||||
int LocalWindow();
|
||||
int RemoteWindow();
|
||||
int RecentConnectionsWindow();
|
||||
int SettingWindow();
|
||||
int ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props);
|
||||
int ControlBar(std::shared_ptr<SubStreamWindowProperties> &props);
|
||||
int AboutWindow();
|
||||
int StatusBar();
|
||||
int 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(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_encode_format;
|
||||
bool enable_hardware_video_codec;
|
||||
bool enable_turn;
|
||||
|
||||
unsigned char key[16];
|
||||
unsigned char iv[16];
|
||||
};
|
||||
|
||||
private:
|
||||
CDCache cd_cache_;
|
||||
std::mutex cd_cache_mutex_;
|
||||
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_;
|
||||
std::string imgui_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;
|
||||
|
||||
// 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_ = 200;
|
||||
float about_window_height_ = 150;
|
||||
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_ = "";
|
||||
bool need_to_send_host_info_ = false;
|
||||
SDL_Event last_mouse_event;
|
||||
|
||||
// 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;
|
||||
uint32_t stream_pixformat_ = 0;
|
||||
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 rejoin_ = false;
|
||||
bool local_id_copied_ = false;
|
||||
bool show_password_ = true;
|
||||
bool regenerate_password_ = false;
|
||||
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;
|
||||
double regenerate_password_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_encode_format_button_value_ = 0;
|
||||
bool enable_hardware_video_codec_ = false;
|
||||
bool enable_turn_ = 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 settings_window_pos_reset_ = true;
|
||||
/* ------ 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
|
||||
527
src/single_window/render_callback_func.cpp
Normal file
527
src/single_window/render_callback_func.cpp
Normal file
@@ -0,0 +1,527 @@
|
||||
#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
|
||||
|
||||
#define STREAM_FRASH (SDL_USEREVENT + 1)
|
||||
|
||||
#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(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_MOUSEBUTTONDOWN == 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_MOUSEBUTTONUP == 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_MOUSEMOTION == 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_MOUSEWHEEL == 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 = STREAM_FRASH;
|
||||
event.user.type = STREAM_FRASH;
|
||||
event.user.data1 = props;
|
||||
SDL_PushEvent(&event);
|
||||
props->streaming_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
SDL_QueueAudio(render->output_dev_, data, (uint32_t)size);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
283
src/single_window/setting_window.cpp
Normal file
283
src/single_window/setting_window.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "layout_style.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::SettingWindow() {
|
||||
if (show_settings_window_) {
|
||||
if (settings_window_pos_reset_) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
SETTINGS_WINDOW_WIDTH_CN) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
SETTINGS_WINDOW_HEIGHT_CN) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(SETTINGS_WINDOW_WIDTH_CN, SETTINGS_WINDOW_HEIGHT_CN));
|
||||
} else {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
SETTINGS_WINDOW_WIDTH_EN) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
SETTINGS_WINDOW_HEIGHT_EN) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(SETTINGS_WINDOW_WIDTH_EN, SETTINGS_WINDOW_HEIGHT_EN));
|
||||
}
|
||||
|
||||
settings_window_pos_reset_ = false;
|
||||
}
|
||||
|
||||
// Settings
|
||||
{
|
||||
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::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);
|
||||
{
|
||||
const char *language_items[] = {
|
||||
localization::language_zh[localization_language_index_].c_str(),
|
||||
localization::language_en[localization_language_index_].c_str()};
|
||||
|
||||
ImGui::SetCursorPosY(32);
|
||||
ImGui::Text(
|
||||
"%s", localization::language[localization_language_index_].c_str());
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(30);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo("##language", &language_button_value_, language_items,
|
||||
IM_ARRAYSIZE(language_items));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (stream_window_inited_) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
{
|
||||
const char *video_quality_items[] = {
|
||||
localization::video_quality_high[localization_language_index_]
|
||||
.c_str(),
|
||||
localization::video_quality_medium[localization_language_index_]
|
||||
.c_str(),
|
||||
localization::video_quality_low[localization_language_index_]
|
||||
.c_str()};
|
||||
|
||||
ImGui::SetCursorPosY(62);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::video_quality[localization_language_index_].c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(60);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo("##video_quality", &video_quality_button_value_,
|
||||
video_quality_items, IM_ARRAYSIZE(video_quality_items));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
const char *video_encode_format_items[] = {
|
||||
localization::av1[localization_language_index_].c_str(),
|
||||
localization::h264[localization_language_index_].c_str()};
|
||||
|
||||
ImGui::SetCursorPosY(92);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::video_encode_format[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(90);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo(
|
||||
"##video_encode_format", &video_encode_format_button_value_,
|
||||
video_encode_format_items, IM_ARRAYSIZE(video_encode_format_items));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
ImGui::SetCursorPosY(122);
|
||||
ImGui::Text("%s", localization::enable_hardware_video_codec
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(120);
|
||||
ImGui::Checkbox("##enable_hardware_video_codec",
|
||||
&enable_hardware_video_codec_);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
ImGui::SetCursorPosY(152);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::enable_turn[localization_language_index_].c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(150);
|
||||
ImGui::Checkbox("##enable_turn", &enable_turn_);
|
||||
}
|
||||
|
||||
if (stream_window_inited_) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(190.0f);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// OK
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = false;
|
||||
|
||||
// Language
|
||||
if (language_button_value_ == 0) {
|
||||
config_center_.SetLanguage(ConfigCenter::LANGUAGE::CHINESE);
|
||||
} else {
|
||||
config_center_.SetLanguage(ConfigCenter::LANGUAGE::ENGLISH);
|
||||
}
|
||||
language_button_value_last_ = language_button_value_;
|
||||
localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_;
|
||||
localization_language_index_ = language_button_value_;
|
||||
LOG_INFO("Set localization language: {}",
|
||||
localization_language_index_ == 0 ? "zh" : "en");
|
||||
|
||||
// Video quality
|
||||
if (video_quality_button_value_ == 0) {
|
||||
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
|
||||
} else if (video_quality_button_value_ == 1) {
|
||||
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
|
||||
} else {
|
||||
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(
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT::H264);
|
||||
}
|
||||
video_encode_format_button_value_last_ =
|
||||
video_encode_format_button_value_;
|
||||
|
||||
// Hardware video codec
|
||||
if (enable_hardware_video_codec_) {
|
||||
config_center_.SetHardwareVideoCodec(true);
|
||||
} else {
|
||||
config_center_.SetHardwareVideoCodec(false);
|
||||
}
|
||||
enable_hardware_video_codec_last_ = enable_hardware_video_codec_;
|
||||
|
||||
// TURN mode
|
||||
if (enable_turn_) {
|
||||
config_center_.SetTurn(true);
|
||||
} else {
|
||||
config_center_.SetTurn(false);
|
||||
}
|
||||
enable_turn_last_ = enable_turn_;
|
||||
|
||||
SaveSettingsIntoCacheFile();
|
||||
settings_window_pos_reset_ = true;
|
||||
|
||||
// Recreate peer instance
|
||||
LoadSettingsFromCacheFile();
|
||||
|
||||
// Recreate peer instance
|
||||
if (!stream_window_inited_) {
|
||||
LOG_INFO("Recreate peer instance");
|
||||
DestroyPeer(&peer_);
|
||||
CreateConnectionPeer();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// Cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = false;
|
||||
if (language_button_value_ != language_button_value_last_) {
|
||||
language_button_value_ = language_button_value_last_;
|
||||
}
|
||||
|
||||
if (video_quality_button_value_ != video_quality_button_value_last_) {
|
||||
video_quality_button_value_ = video_quality_button_value_last_;
|
||||
}
|
||||
|
||||
if (video_encode_format_button_value_ !=
|
||||
video_encode_format_button_value_last_) {
|
||||
video_encode_format_button_value_ =
|
||||
video_encode_format_button_value_last_;
|
||||
}
|
||||
|
||||
if (enable_hardware_video_codec_ != enable_hardware_video_codec_last_) {
|
||||
enable_hardware_video_codec_ = enable_hardware_video_codec_last_;
|
||||
}
|
||||
|
||||
if (enable_turn_ != enable_turn_last_) {
|
||||
enable_turn_ = enable_turn_last_;
|
||||
}
|
||||
|
||||
settings_window_pos_reset_ = true;
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
38
src/single_window/status_bar.cpp
Normal file
38
src/single_window/status_bar.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "localization.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::StatusBar() {
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
static bool a, b, c, d, e;
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(0, main_window_height_default_ - status_bar_height_ - 1),
|
||||
ImGuiCond_Always);
|
||||
|
||||
ImGui::BeginChild(
|
||||
"StatusBar", ImVec2(main_window_width_, status_bar_height_ + 1),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
|
||||
ImVec2 dot_pos =
|
||||
ImVec2(13, main_window_height_default_ - status_bar_height_ + 11.0f);
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddCircleFilled(dot_pos, 5.0f,
|
||||
ImColor(signal_connected_ ? 0.0f : 1.0f,
|
||||
signal_connected_ ? 1.0f : 0.0f, 0.0f),
|
||||
100);
|
||||
draw_list->AddCircle(dot_pos, 6.0f, ImColor(1.0f, 1.0f, 1.0f), 100);
|
||||
|
||||
ImGui::SetWindowFontScale(0.6f);
|
||||
draw_list->AddText(
|
||||
ImVec2(25, main_window_height_default_ - status_bar_height_ + 3.0f),
|
||||
ImColor(0.0f, 0.0f, 0.0f),
|
||||
signal_connected_
|
||||
? localization::signal_connected[localization_language_index_].c_str()
|
||||
: localization::signal_disconnected[localization_language_index_]
|
||||
.c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::EndChild();
|
||||
return 0;
|
||||
}
|
||||
8422
src/single_window/stb_image.h
Normal file
8422
src/single_window/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
2020
src/single_window/stb_image_write.h
Normal file
2020
src/single_window/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load Diff
197
src/single_window/stream_window.cpp
Normal file
197
src/single_window/stream_window.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
#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_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);
|
||||
if (ImGui::BeginTabItem(props->remote_id_.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);
|
||||
|
||||
if (!props->peer_) {
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_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_, SDL_FALSE);
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
} else {
|
||||
DrawConnectionStatusText(props);
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRenderRect();
|
||||
ImGui::End(); // End VideoBg
|
||||
|
||||
return 0;
|
||||
}
|
||||
431
src/single_window/thumbnail.cpp
Normal file
431
src/single_window/thumbnail.cpp
Normal file
@@ -0,0 +1,431 @@
|
||||
#include "thumbnail.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
|
||||
static std::string test;
|
||||
|
||||
bool LoadTextureFromMemory(const void* data, size_t data_size,
|
||||
SDL_Renderer* renderer, SDL_Texture** out_texture,
|
||||
int* out_width, int* out_height) {
|
||||
int image_width = 0;
|
||||
int image_height = 0;
|
||||
int channels = 4;
|
||||
unsigned char* image_data =
|
||||
stbi_load_from_memory((const unsigned char*)data, (int)data_size,
|
||||
&image_width, &image_height, NULL, 4);
|
||||
if (image_data == nullptr) {
|
||||
LOG_ERROR("Failed to load image: [{}]", stbi_failure_reason());
|
||||
return false;
|
||||
}
|
||||
|
||||
// ABGR
|
||||
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
|
||||
(void*)image_data, image_width, image_height, channels * 8,
|
||||
channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
|
||||
if (surface == nullptr) {
|
||||
LOG_ERROR("Failed to create SDL surface: [{}]", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
if (texture == nullptr) {
|
||||
LOG_ERROR("Failed to create SDL texture: [{}]", SDL_GetError());
|
||||
}
|
||||
|
||||
*out_texture = texture;
|
||||
*out_width = image_width;
|
||||
*out_height = image_height;
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
stbi_image_free(image_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer,
|
||||
SDL_Texture** out_texture, int* out_width,
|
||||
int* out_height) {
|
||||
std::filesystem::path file_path(file_name);
|
||||
if (!std::filesystem::exists(file_path)) return false;
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
if (!file) return false;
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (file_size == -1) return false;
|
||||
char* file_data = new char[file_size];
|
||||
if (!file_data) return false;
|
||||
file.read(file_data, file_size);
|
||||
bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture,
|
||||
out_width, out_height);
|
||||
delete[] file_data;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
|
||||
char* dst_rgba) {
|
||||
uint8_t* y = reinterpret_cast<uint8_t*>(src);
|
||||
uint8_t* uv = y + src_w * src_h;
|
||||
|
||||
float src_aspect = float(src_w) / src_h;
|
||||
float dst_aspect = float(dst_w) / dst_h;
|
||||
int fit_w = dst_w, fit_h = dst_h;
|
||||
if (src_aspect > dst_aspect) {
|
||||
fit_h = int(dst_w / src_aspect);
|
||||
} else {
|
||||
fit_w = int(dst_h * src_aspect);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> y_i420(src_w * src_h);
|
||||
std::vector<uint8_t> u_i420((src_w / 2) * (src_h / 2));
|
||||
std::vector<uint8_t> v_i420((src_w / 2) * (src_h / 2));
|
||||
libyuv::NV12ToI420(y, src_w, uv, src_w, y_i420.data(), src_w, u_i420.data(),
|
||||
src_w / 2, v_i420.data(), src_w / 2, src_w, src_h);
|
||||
|
||||
std::vector<uint8_t> y_fit(fit_w * fit_h);
|
||||
std::vector<uint8_t> u_fit((fit_w + 1) / 2 * (fit_h + 1) / 2);
|
||||
std::vector<uint8_t> v_fit((fit_w + 1) / 2 * (fit_h + 1) / 2);
|
||||
libyuv::I420Scale(y_i420.data(), src_w, u_i420.data(), src_w / 2,
|
||||
v_i420.data(), src_w / 2, src_w, src_h, y_fit.data(), fit_w,
|
||||
u_fit.data(), (fit_w + 1) / 2, v_fit.data(),
|
||||
(fit_w + 1) / 2, fit_w, fit_h, libyuv::kFilterBilinear);
|
||||
|
||||
std::vector<uint8_t> abgr(fit_w * fit_h * 4);
|
||||
libyuv::I420ToABGR(y_fit.data(), fit_w, u_fit.data(), (fit_w + 1) / 2,
|
||||
v_fit.data(), (fit_w + 1) / 2, abgr.data(), fit_w * 4,
|
||||
fit_w, fit_h);
|
||||
|
||||
memset(dst_rgba, 0, dst_w * dst_h * 4);
|
||||
for (int i = 0; i < dst_w * dst_h; ++i) {
|
||||
dst_rgba[i * 4 + 3] = 0xFF;
|
||||
}
|
||||
|
||||
for (int y = 0; y < fit_h; ++y) {
|
||||
int dst_offset =
|
||||
((y + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
|
||||
memcpy(dst_rgba + dst_offset, abgr.data() + y * fit_w * 4, fit_w * 4);
|
||||
}
|
||||
}
|
||||
|
||||
Thumbnail::Thumbnail(std::string save_path) {
|
||||
if (!save_path.empty()) {
|
||||
save_path_ = save_path;
|
||||
}
|
||||
|
||||
RAND_bytes(aes128_key_, sizeof(aes128_key_));
|
||||
RAND_bytes(aes128_iv_, sizeof(aes128_iv_));
|
||||
std::filesystem::create_directories(save_path_);
|
||||
}
|
||||
|
||||
Thumbnail::Thumbnail(std::string save_path, unsigned char* aes128_key,
|
||||
unsigned char* aes128_iv) {
|
||||
if (!save_path.empty()) {
|
||||
save_path_ = save_path;
|
||||
}
|
||||
|
||||
memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
|
||||
memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
|
||||
std::filesystem::create_directories(save_path_);
|
||||
}
|
||||
|
||||
Thumbnail::~Thumbnail() {
|
||||
if (rgba_buffer_) {
|
||||
delete[] rgba_buffer_;
|
||||
rgba_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
|
||||
const std::string& remote_id,
|
||||
const std::string& host_name,
|
||||
const std::string& password) {
|
||||
if (!rgba_buffer_) {
|
||||
rgba_buffer_ = new char[thumbnail_width_ * thumbnail_height_ * 4];
|
||||
}
|
||||
|
||||
if (yuv420p) {
|
||||
ScaleNv12ToABGR((char*)yuv420p, width, height, thumbnail_width_,
|
||||
thumbnail_height_, rgba_buffer_);
|
||||
} else {
|
||||
// If yuv420p is null, fill the buffer with black pixels
|
||||
memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
|
||||
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
|
||||
// Set alpha channel to opaque
|
||||
rgba_buffer_[i * 4 + 3] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
std::string image_file_name;
|
||||
if (password.empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
// delete the old thumbnail
|
||||
std::string filename_with_remote_id = remote_id;
|
||||
DeleteThumbnail(filename_with_remote_id);
|
||||
}
|
||||
|
||||
std::string cipher_password = AES_encrypt(password, aes128_key_, aes128_iv_);
|
||||
image_file_name = remote_id + 'Y' + host_name + '@' + cipher_password;
|
||||
std::string file_path = save_path_ + image_file_name;
|
||||
stbi_write_png(file_path.data(), thumbnail_width_, thumbnail_height_, 4,
|
||||
rgba_buffer_, thumbnail_width_ * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Thumbnail::LoadThumbnail(
|
||||
SDL_Renderer* renderer,
|
||||
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>&
|
||||
recent_connections,
|
||||
int* width, int* height) {
|
||||
for (auto& it : recent_connections) {
|
||||
if (it.second.texture != nullptr) {
|
||||
SDL_DestroyTexture(it.second.texture);
|
||||
it.second.texture = nullptr;
|
||||
}
|
||||
}
|
||||
recent_connections.clear();
|
||||
|
||||
std::vector<std::filesystem::path> image_paths =
|
||||
FindThumbnailPath(save_path_);
|
||||
|
||||
if (image_paths.size() == 0) {
|
||||
return -1;
|
||||
} else {
|
||||
for (int i = 0; i < image_paths.size(); i++) {
|
||||
size_t pos1 = image_paths[i].string().find('/') + 1;
|
||||
std::string cipher_image_name = image_paths[i].filename().string();
|
||||
std::string remote_id;
|
||||
std::string cipher_password;
|
||||
std::string remote_host_name;
|
||||
std::string original_image_name;
|
||||
|
||||
if ('Y' == cipher_image_name[9] && cipher_image_name.size() >= 16) {
|
||||
size_t pos_y = cipher_image_name.find('Y');
|
||||
size_t pos_at = cipher_image_name.find('@');
|
||||
|
||||
if (pos_y == std::string::npos || pos_at == std::string::npos ||
|
||||
pos_y >= pos_at) {
|
||||
LOG_ERROR("Invalid filename");
|
||||
continue;
|
||||
}
|
||||
|
||||
remote_id = cipher_image_name.substr(0, pos_y);
|
||||
remote_host_name =
|
||||
cipher_image_name.substr(pos_y + 1, pos_at - pos_y - 1);
|
||||
cipher_password = cipher_image_name.substr(pos_at + 1);
|
||||
|
||||
original_image_name =
|
||||
remote_id + 'Y' + remote_host_name + "@" +
|
||||
AES_decrypt(cipher_password, aes128_key_, aes128_iv_);
|
||||
} else {
|
||||
size_t pos_n = cipher_image_name.find('N');
|
||||
size_t pos_at = cipher_image_name.find('@');
|
||||
|
||||
if (pos_n == std::string::npos) {
|
||||
LOG_ERROR("Invalid filename");
|
||||
continue;
|
||||
}
|
||||
|
||||
remote_id = cipher_image_name.substr(0, pos_n);
|
||||
remote_host_name = cipher_image_name.substr(pos_n + 1);
|
||||
|
||||
original_image_name =
|
||||
remote_id + 'N' + remote_host_name + "@" +
|
||||
AES_decrypt(cipher_password, aes128_key_, aes128_iv_);
|
||||
}
|
||||
|
||||
std::string image_path = save_path_ + cipher_image_name;
|
||||
recent_connections.emplace_back(
|
||||
std::make_pair(original_image_name, Thumbnail::RecentConnection()));
|
||||
LoadTextureFromFile(image_path.c_str(), renderer,
|
||||
&(recent_connections[i].second.texture), width,
|
||||
height);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Thumbnail::DeleteThumbnail(const std::string& filename_keyword) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(save_path_)) {
|
||||
if (entry.is_regular_file()) {
|
||||
const std::string filename = entry.path().filename().string();
|
||||
std::string id_hostname = filename_keyword.substr(0, filename.find('@'));
|
||||
if (filename.find(id_hostname) != std::string::npos) {
|
||||
std::filesystem::remove(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> Thumbnail::FindThumbnailPath(
|
||||
const std::filesystem::path& directory) {
|
||||
std::vector<std::filesystem::path> thumbnails_path;
|
||||
|
||||
if (!std::filesystem::is_directory(directory)) {
|
||||
LOG_ERROR("No such directory [{}]", directory.string());
|
||||
return thumbnails_path;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
thumbnails_path.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(thumbnails_path.begin(), thumbnails_path.end(),
|
||||
[](const std::filesystem::path& a, const std::filesystem::path& b) {
|
||||
return std::filesystem::last_write_time(a) >
|
||||
std::filesystem::last_write_time(b);
|
||||
});
|
||||
|
||||
return thumbnails_path;
|
||||
}
|
||||
|
||||
int Thumbnail::DeleteAllFilesInDirectory() {
|
||||
if (std::filesystem::exists(save_path_) &&
|
||||
std::filesystem::is_directory(save_path_)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(save_path_)) {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
std::filesystem::remove(entry.path());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string Thumbnail::AES_encrypt(const std::string& plaintext,
|
||||
unsigned char* key, unsigned char* iv) {
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
int len;
|
||||
int ciphertext_len;
|
||||
int ret = 0;
|
||||
std::vector<unsigned char> ciphertext(plaintext.size() + AES_BLOCK_SIZE);
|
||||
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) {
|
||||
LOG_ERROR("Error in EVP_CIPHER_CTX_new");
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in EVP_EncryptInit_ex");
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
ret = EVP_EncryptUpdate(
|
||||
ctx, ciphertext.data(), &len,
|
||||
reinterpret_cast<const unsigned char*>(plaintext.data()),
|
||||
(int)plaintext.size());
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in EVP_EncryptUpdate");
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
ciphertext_len = len;
|
||||
ret = EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len);
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in EVP_EncryptFinal_ex");
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
ciphertext_len += len;
|
||||
|
||||
unsigned char hex_str[256];
|
||||
size_t hex_str_len = 0;
|
||||
ret = OPENSSL_buf2hexstr_ex((char*)hex_str, sizeof(hex_str), &hex_str_len,
|
||||
ciphertext.data(), ciphertext_len, '\0');
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in OPENSSL_buf2hexstr_ex");
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
std::string str(reinterpret_cast<char*>(hex_str), hex_str_len);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string Thumbnail::AES_decrypt(const std::string& ciphertext,
|
||||
unsigned char* key, unsigned char* iv) {
|
||||
unsigned char ciphertext_buf[256];
|
||||
size_t ciphertext_buf_len = 0;
|
||||
unsigned char plaintext[256];
|
||||
int plaintext_len = 0;
|
||||
int plaintext_final_len = 0;
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
int ret = 0;
|
||||
|
||||
ret = OPENSSL_hexstr2buf_ex(ciphertext_buf, sizeof(ciphertext_buf),
|
||||
&ciphertext_buf_len, ciphertext.c_str(), '\0');
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in OPENSSL_hexstr2buf_ex");
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) {
|
||||
LOG_ERROR("Error in EVP_CIPHER_CTX_new");
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ret = EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in EVP_DecryptInit_ex");
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ret = EVP_DecryptUpdate(ctx, plaintext, &plaintext_len, ciphertext_buf,
|
||||
(int)ciphertext_buf_len);
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in EVP_DecryptUpdate");
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ret =
|
||||
EVP_DecryptFinal_ex(ctx, plaintext + plaintext_len, &plaintext_final_len);
|
||||
if (1 != ret) {
|
||||
LOG_ERROR("Error in EVP_DecryptFinal_ex");
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return ciphertext;
|
||||
}
|
||||
plaintext_len += plaintext_final_len;
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
return std::string(reinterpret_cast<char*>(plaintext), plaintext_len);
|
||||
}
|
||||
87
src/single_window/thumbnail.h
Normal file
87
src/single_window/thumbnail.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-07
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _THUMBNAIL_H_
|
||||
#define _THUMBNAIL_H_
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Thumbnail {
|
||||
public:
|
||||
struct RecentConnection {
|
||||
SDL_Texture* texture = nullptr;
|
||||
std::string remote_id;
|
||||
std::string remote_host_name;
|
||||
std::string password;
|
||||
bool remember_password = false;
|
||||
};
|
||||
|
||||
public:
|
||||
Thumbnail(std::string save_path);
|
||||
explicit Thumbnail(std::string save_path, unsigned char* aes128_key,
|
||||
unsigned char* aes128_iv);
|
||||
~Thumbnail();
|
||||
|
||||
public:
|
||||
int SaveToThumbnail(const char* yuv420p, int width, int height,
|
||||
const std::string& remote_id,
|
||||
const std::string& host_name,
|
||||
const std::string& password);
|
||||
|
||||
int LoadThumbnail(
|
||||
SDL_Renderer* renderer,
|
||||
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>&
|
||||
recent_connections,
|
||||
int* width, int* height);
|
||||
|
||||
int DeleteThumbnail(const std::string& filename_keyword);
|
||||
|
||||
int DeleteAllFilesInDirectory();
|
||||
|
||||
int GetKey(unsigned char* aes128_key) {
|
||||
memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
|
||||
return sizeof(aes128_key_);
|
||||
}
|
||||
|
||||
int GetIv(unsigned char* aes128_iv) {
|
||||
memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
|
||||
return sizeof(aes128_iv_);
|
||||
}
|
||||
|
||||
int GetKeyAndIv(unsigned char* aes128_key, unsigned char* aes128_iv) {
|
||||
memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
|
||||
memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::filesystem::path> FindThumbnailPath(
|
||||
const std::filesystem::path& directory);
|
||||
|
||||
std::string AES_encrypt(const std::string& plaintext, unsigned char* key,
|
||||
unsigned char* iv);
|
||||
|
||||
std::string AES_decrypt(const std::string& ciphertext, unsigned char* key,
|
||||
unsigned char* iv);
|
||||
|
||||
private:
|
||||
int thumbnail_width_ = 160;
|
||||
int thumbnail_height_ = 90;
|
||||
char* rgba_buffer_ = nullptr;
|
||||
std::string save_path_ = "thumbnails/";
|
||||
|
||||
unsigned char aes128_key_[16];
|
||||
unsigned char aes128_iv_[16];
|
||||
unsigned char ciphertext_[64];
|
||||
unsigned char decryptedtext_[64];
|
||||
};
|
||||
|
||||
#endif
|
||||
165
src/single_window/title_bar.cpp
Normal file
165
src/single_window/title_bar.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
#define BUTTON_PADDING 36.0f
|
||||
|
||||
int Render::TitleBar(bool main_window) {
|
||||
ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::SetWindowFontScale(0.8f);
|
||||
ImGui::BeginChild(
|
||||
main_window ? "MainTitleBar" : "StreamTitleBar",
|
||||
ImVec2(main_window ? main_window_width_ : stream_window_width_,
|
||||
title_bar_height_),
|
||||
ImGuiChildFlags_Border,
|
||||
ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
ImGui::SetCursorPosX(
|
||||
(main_window ? main_window_width_ : stream_window_width_) -
|
||||
(BUTTON_PADDING * 3 - 3));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0.1f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive,
|
||||
ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
if (main_window) {
|
||||
float bar_pos_x = ImGui::GetCursorPosX() + 6;
|
||||
float bar_pos_y = ImGui::GetCursorPosY() + 15;
|
||||
std::string menu_button = " "; // ICON_FA_BARS;
|
||||
if (ImGui::BeginMenu(menu_button.c_str())) {
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
if (ImGui::MenuItem(
|
||||
localization::settings[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = true;
|
||||
}
|
||||
if (ImGui::MenuItem(
|
||||
localization::about[localization_language_index_].c_str())) {
|
||||
show_about_window_ = true;
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
float menu_bar_line_size = 15.0f;
|
||||
draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y - 6),
|
||||
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y - 6),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y),
|
||||
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y + 6),
|
||||
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y + 6),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
|
||||
{
|
||||
SettingWindow();
|
||||
AboutWindow();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
ImGui::SetCursorPosX(main_window
|
||||
? (main_window_width_ - BUTTON_PADDING * 2)
|
||||
: (stream_window_width_ - BUTTON_PADDING * 3));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
float minimize_pos_x = ImGui::GetCursorPosX() + 12;
|
||||
float minimize_pos_y = ImGui::GetCursorPosY() + 15;
|
||||
std::string window_minimize_button = "##minimize"; // ICON_FA_MINUS;
|
||||
if (ImGui::Button(window_minimize_button.c_str(),
|
||||
ImVec2(BUTTON_PADDING, 30))) {
|
||||
SDL_MinimizeWindow(main_window ? main_window_ : stream_window_);
|
||||
}
|
||||
draw_list->AddLine(ImVec2(minimize_pos_x, minimize_pos_y),
|
||||
ImVec2(minimize_pos_x + 12, minimize_pos_y),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
if (!main_window) {
|
||||
ImGui::SetCursorPosX(stream_window_width_ - BUTTON_PADDING * 2);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
if (window_maximized_) {
|
||||
float pos_x_top = ImGui::GetCursorPosX() + 11;
|
||||
float pos_y_top = ImGui::GetCursorPosY() + 11;
|
||||
float pos_x_bottom = ImGui::GetCursorPosX() + 13;
|
||||
float pos_y_bottom = ImGui::GetCursorPosY() + 9;
|
||||
std::string window_restore_button =
|
||||
"##restore"; // ICON_FA_WINDOW_RESTORE;
|
||||
if (ImGui::Button(window_restore_button.c_str(),
|
||||
ImVec2(BUTTON_PADDING, 30))) {
|
||||
SDL_RestoreWindow(stream_window_);
|
||||
window_maximized_ = false;
|
||||
}
|
||||
draw_list->AddRect(ImVec2(pos_x_top, pos_y_top),
|
||||
ImVec2(pos_x_top + 12, pos_y_top + 12),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
draw_list->AddRect(ImVec2(pos_x_bottom, pos_y_bottom),
|
||||
ImVec2(pos_x_bottom + 12, pos_y_bottom + 12),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
draw_list->AddRectFilled(ImVec2(pos_x_top + 1, pos_y_top + 1),
|
||||
ImVec2(pos_x_top + 11, pos_y_top + 11),
|
||||
IM_COL32(255, 255, 255, 255));
|
||||
} else {
|
||||
float maximize_pos_x = ImGui::GetCursorPosX() + 12;
|
||||
float maximize_pos_y = ImGui::GetCursorPosY() + 10;
|
||||
std::string window_maximize_button =
|
||||
"##maximize"; // ICON_FA_SQUARE_FULL;
|
||||
if (ImGui::Button(window_maximize_button.c_str(),
|
||||
ImVec2(BUTTON_PADDING, 30))) {
|
||||
SDL_MaximizeWindow(stream_window_);
|
||||
window_maximized_ = !window_maximized_;
|
||||
}
|
||||
draw_list->AddRect(ImVec2(maximize_pos_x, maximize_pos_y),
|
||||
ImVec2(maximize_pos_x + 12, maximize_pos_y + 12),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(
|
||||
(main_window ? main_window_width_ : stream_window_width_) -
|
||||
BUTTON_PADDING);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0, 0, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0, 0, 0.5f));
|
||||
|
||||
float xmark_pos_x = ImGui::GetCursorPosX() + 18;
|
||||
float xmark_pos_y = ImGui::GetCursorPosY() + 16;
|
||||
float xmark_size = 12.0f;
|
||||
std::string close_button = "##xmark"; // ICON_FA_XMARK;
|
||||
if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
|
||||
xmark_pos_y - xmark_size / 2 + 0.75f),
|
||||
ImVec2(xmark_pos_x + xmark_size / 2 - 1.5f,
|
||||
xmark_pos_y + xmark_size / 2 - 0.5f),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
draw_list->AddLine(ImVec2(xmark_pos_x + xmark_size / 2 - 1.75f,
|
||||
xmark_pos_y - xmark_size / 2 + 0.75f),
|
||||
ImVec2(xmark_pos_x - xmark_size / 2,
|
||||
xmark_pos_y + xmark_size / 2 - 1.0f),
|
||||
IM_COL32(0, 0, 0, 255));
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::EndMenuBar();
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
return 0;
|
||||
}
|
||||
268
src/speaker_capturer/linux/speaker_capturer_linux.cpp
Normal file
268
src/speaker_capturer/linux/speaker_capturer_linux.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
#include "speaker_capturer_linux.h"
|
||||
|
||||
#include <pulse/error.h>
|
||||
#include <pulse/introspect.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
constexpr int kSampleRate = 48000;
|
||||
constexpr pa_sample_format_t kFormat = PA_SAMPLE_S16LE;
|
||||
constexpr int kChannels = 1;
|
||||
constexpr size_t kFrameSizeBytes = 480 * sizeof(int16_t);
|
||||
|
||||
SpeakerCapturerLinux::SpeakerCapturerLinux()
|
||||
: inited_(false), paused_(false), stop_flag_(false) {}
|
||||
SpeakerCapturerLinux::~SpeakerCapturerLinux() {
|
||||
Stop();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
int SpeakerCapturerLinux::Init(speaker_data_cb cb) {
|
||||
if (inited_) return 0;
|
||||
cb_ = cb;
|
||||
inited_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerLinux::Destroy() {
|
||||
inited_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string SpeakerCapturerLinux::GetDefaultMonitorSourceName() {
|
||||
std::string monitor_name;
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
|
||||
pa_mainloop* mainloop = pa_mainloop_new();
|
||||
pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
|
||||
pa_context* context = pa_context_new(api, "GetMonitor");
|
||||
|
||||
struct CallbackState {
|
||||
std::string* name;
|
||||
std::mutex* mtx;
|
||||
std::condition_variable* cv;
|
||||
bool* ready;
|
||||
} state{&monitor_name, &mtx, &cv, &ready};
|
||||
|
||||
pa_context_set_state_callback(
|
||||
context,
|
||||
[](pa_context* c, void* userdata) {
|
||||
auto* state = static_cast<CallbackState*>(userdata);
|
||||
if (pa_context_get_state(c) == PA_CONTEXT_READY) {
|
||||
pa_operation* operation = pa_context_get_server_info(
|
||||
c,
|
||||
[](pa_context*, const pa_server_info* info, void* userdata) {
|
||||
auto* state = static_cast<CallbackState*>(userdata);
|
||||
if (info && info->default_sink_name) {
|
||||
*(state->name) =
|
||||
std::string(info->default_sink_name) + ".monitor";
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*(state->mtx));
|
||||
*(state->ready) = true;
|
||||
}
|
||||
state->cv->notify_one();
|
||||
},
|
||||
userdata);
|
||||
if (operation) {
|
||||
pa_operation_unref(operation);
|
||||
}
|
||||
}
|
||||
},
|
||||
&state);
|
||||
|
||||
pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
|
||||
|
||||
std::thread loop_thread([&]() {
|
||||
int ret = 0;
|
||||
pa_mainloop_run(mainloop, &ret);
|
||||
});
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv.wait_for(lock, std::chrono::seconds(2), [&] { return ready; });
|
||||
}
|
||||
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
pa_mainloop_quit(mainloop, 0);
|
||||
loop_thread.join();
|
||||
pa_mainloop_free(mainloop);
|
||||
|
||||
return monitor_name;
|
||||
}
|
||||
|
||||
int SpeakerCapturerLinux::Start() {
|
||||
if (!inited_ || mainloop_thread_.joinable()) return -1;
|
||||
stop_flag_ = false;
|
||||
|
||||
mainloop_thread_ = std::thread([this]() {
|
||||
std::string monitor_name = GetDefaultMonitorSourceName();
|
||||
if (monitor_name.empty()) {
|
||||
LOG_ERROR("Failed to get monitor source");
|
||||
return;
|
||||
}
|
||||
|
||||
mainloop_ = pa_threaded_mainloop_new();
|
||||
pa_mainloop_api* api = pa_threaded_mainloop_get_api(mainloop_);
|
||||
context_ = pa_context_new(api, "SpeakerCapturer");
|
||||
|
||||
pa_context_set_state_callback(
|
||||
context_,
|
||||
[](pa_context* c, void* userdata) {
|
||||
auto self = static_cast<SpeakerCapturerLinux*>(userdata);
|
||||
pa_context_state_t state = pa_context_get_state(c);
|
||||
if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED ||
|
||||
state == PA_CONTEXT_TERMINATED) {
|
||||
pa_threaded_mainloop_signal(self->mainloop_, 0);
|
||||
}
|
||||
},
|
||||
this);
|
||||
|
||||
if (pa_threaded_mainloop_start(mainloop_) < 0) {
|
||||
LOG_ERROR("Failed to start mainloop");
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
|
||||
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) <
|
||||
0) {
|
||||
LOG_ERROR("Failed to connect context");
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
pa_context_state_t state = pa_context_get_state(context_);
|
||||
if (state == PA_CONTEXT_READY) break;
|
||||
if (!PA_CONTEXT_IS_GOOD(state) || stop_flag_) {
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
pa_threaded_mainloop_wait(mainloop_);
|
||||
}
|
||||
|
||||
pa_sample_spec ss = {kFormat, kSampleRate, kChannels};
|
||||
stream_ = pa_stream_new(context_, "Capture", &ss, nullptr);
|
||||
|
||||
pa_stream_set_read_callback(
|
||||
stream_,
|
||||
[](pa_stream* s, size_t len, void* u) {
|
||||
auto self = static_cast<SpeakerCapturerLinux*>(u);
|
||||
if (self->paused_ || self->stop_flag_) return;
|
||||
|
||||
const void* data = nullptr;
|
||||
if (pa_stream_peek(s, &data, &len) < 0 || !data) return;
|
||||
|
||||
const uint8_t* p = static_cast<const uint8_t*>(data);
|
||||
self->frame_cache_.insert(self->frame_cache_.end(), p, p + len);
|
||||
|
||||
while (self->frame_cache_.size() >= kFrameSizeBytes) {
|
||||
std::vector<uint8_t> temp_frame(
|
||||
self->frame_cache_.begin(),
|
||||
self->frame_cache_.begin() + kFrameSizeBytes);
|
||||
self->cb_(temp_frame.data(), kFrameSizeBytes, "audio");
|
||||
self->frame_cache_.erase(
|
||||
self->frame_cache_.begin(),
|
||||
self->frame_cache_.begin() + kFrameSizeBytes);
|
||||
}
|
||||
|
||||
pa_stream_drop(s);
|
||||
},
|
||||
this);
|
||||
|
||||
pa_buffer_attr attr = {.maxlength = (uint32_t)-1,
|
||||
.tlength = 0,
|
||||
.prebuf = 0,
|
||||
.minreq = 0,
|
||||
.fragsize = (uint32_t)kFrameSizeBytes};
|
||||
|
||||
if (pa_stream_connect_record(stream_, monitor_name.c_str(), &attr,
|
||||
PA_STREAM_ADJUST_LATENCY) < 0) {
|
||||
LOG_ERROR("Failed to connect stream");
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
pa_stream_state_t s = pa_stream_get_state(stream_);
|
||||
if (s == PA_STREAM_READY) break;
|
||||
if (!PA_STREAM_IS_GOOD(s) || stop_flag_) {
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
pa_threaded_mainloop_wait(mainloop_);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
|
||||
while (!stop_flag_)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerLinux::Stop() {
|
||||
stop_flag_ = true;
|
||||
|
||||
if (mainloop_) {
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
pa_threaded_mainloop_signal(mainloop_, 0);
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
}
|
||||
|
||||
if (mainloop_thread_.joinable()) {
|
||||
mainloop_thread_.join();
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SpeakerCapturerLinux::Cleanup() {
|
||||
if (mainloop_) {
|
||||
pa_threaded_mainloop_stop(mainloop_);
|
||||
pa_threaded_mainloop_lock(mainloop_);
|
||||
|
||||
if (stream_) {
|
||||
pa_stream_disconnect(stream_);
|
||||
pa_stream_unref(stream_);
|
||||
stream_ = nullptr;
|
||||
}
|
||||
|
||||
if (context_) {
|
||||
pa_context_disconnect(context_);
|
||||
pa_context_unref(context_);
|
||||
context_ = nullptr;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop_);
|
||||
pa_threaded_mainloop_free(mainloop_);
|
||||
mainloop_ = nullptr;
|
||||
}
|
||||
|
||||
frame_cache_.clear();
|
||||
}
|
||||
|
||||
int SpeakerCapturerLinux::Pause() {
|
||||
paused_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerLinux::Resume() {
|
||||
paused_ = false;
|
||||
return 0;
|
||||
}
|
||||
54
src/speaker_capturer/linux/speaker_capturer_linux.h
Normal file
54
src/speaker_capturer/linux/speaker_capturer_linux.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-07-15
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SPEAKER_CAPTURER_LINUX_H_
|
||||
#define _SPEAKER_CAPTURER_LINUX_H_
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "speaker_capturer.h"
|
||||
|
||||
class SpeakerCapturerLinux : public SpeakerCapturer {
|
||||
public:
|
||||
SpeakerCapturerLinux();
|
||||
~SpeakerCapturerLinux();
|
||||
|
||||
int Init(speaker_data_cb cb) override;
|
||||
int Destroy() override;
|
||||
int Start() override;
|
||||
int Stop() override;
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
|
||||
private:
|
||||
std::string GetDefaultMonitorSourceName();
|
||||
void Cleanup();
|
||||
|
||||
private:
|
||||
speaker_data_cb cb_ = nullptr;
|
||||
|
||||
std::atomic<bool> inited_;
|
||||
std::atomic<bool> paused_;
|
||||
std::atomic<bool> stop_flag_;
|
||||
|
||||
std::thread mainloop_thread_;
|
||||
pa_threaded_mainloop* mainloop_ = nullptr;
|
||||
pa_context* context_ = nullptr;
|
||||
pa_stream* stream_ = nullptr;
|
||||
|
||||
std::mutex state_mtx_;
|
||||
std::vector<uint8_t> frame_cache_;
|
||||
};
|
||||
|
||||
#endif
|
||||
37
src/speaker_capturer/macosx/speaker_capturer_macosx.h
Normal file
37
src/speaker_capturer/macosx/speaker_capturer_macosx.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-08-02
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SPEAKER_CAPTURER_MACOSX_H_
|
||||
#define _SPEAKER_CAPTURER_MACOSX_H_
|
||||
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "speaker_capturer.h"
|
||||
|
||||
class SpeakerCapturerMacosx : public SpeakerCapturer {
|
||||
public:
|
||||
SpeakerCapturerMacosx();
|
||||
~SpeakerCapturerMacosx();
|
||||
|
||||
public:
|
||||
virtual int Init(speaker_data_cb cb);
|
||||
virtual int Destroy();
|
||||
virtual int Start();
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
|
||||
public:
|
||||
speaker_data_cb cb_ = nullptr;
|
||||
bool inited_ = false;
|
||||
|
||||
class Impl;
|
||||
Impl* impl_ = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
264
src/speaker_capturer/macosx/speaker_capturer_macosx.mm
Normal file
264
src/speaker_capturer/macosx/speaker_capturer_macosx.mm
Normal file
@@ -0,0 +1,264 @@
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "speaker_capturer_macosx.h"
|
||||
|
||||
@interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput>
|
||||
@property(nonatomic, assign) SpeakerCapturerMacosx* owner;
|
||||
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner;
|
||||
@end
|
||||
|
||||
@implementation SpeakerCaptureDelegate
|
||||
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_owner = owner;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)stream:(SCStream*)stream
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
ofType:(SCStreamOutputType)type {
|
||||
if (type == SCStreamOutputTypeAudio) {
|
||||
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
|
||||
size_t length = CMBlockBufferGetDataLength(blockBuffer);
|
||||
char* dataPtr = NULL;
|
||||
CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr);
|
||||
CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
|
||||
const AudioStreamBasicDescription* asbd =
|
||||
CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
|
||||
|
||||
if (_owner->cb_ && dataPtr && length > 0 && asbd) {
|
||||
std::vector<short> out_pcm16;
|
||||
if (asbd->mFormatFlags & kAudioFormatFlagIsFloat) {
|
||||
int channels = asbd->mChannelsPerFrame;
|
||||
int samples = (int)(length / sizeof(float));
|
||||
float* floatData = (float*)dataPtr;
|
||||
std::vector<short> pcm16(samples);
|
||||
for (int i = 0; i < samples; ++i) {
|
||||
float v = floatData[i];
|
||||
if (v > 1.0f) v = 1.0f;
|
||||
if (v < -1.0f) v = -1.0f;
|
||||
pcm16[i] = (short)(v * 32767.0f);
|
||||
}
|
||||
|
||||
if (channels > 1) {
|
||||
int mono_samples = samples / channels;
|
||||
out_pcm16.resize(mono_samples);
|
||||
for (int i = 0; i < mono_samples; ++i) {
|
||||
int sum = 0;
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
sum += pcm16[i * channels + c];
|
||||
}
|
||||
out_pcm16[i] = sum / channels;
|
||||
}
|
||||
} else {
|
||||
out_pcm16 = std::move(pcm16);
|
||||
}
|
||||
} else if (asbd->mBitsPerChannel == 16) {
|
||||
int channels = asbd->mChannelsPerFrame;
|
||||
int samples = (int)(length / 2);
|
||||
short* src = (short*)dataPtr;
|
||||
if (channels > 1) {
|
||||
int mono_samples = samples / channels;
|
||||
out_pcm16.resize(mono_samples);
|
||||
for (int i = 0; i < mono_samples; ++i) {
|
||||
int sum = 0;
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
sum += src[i * channels + c];
|
||||
}
|
||||
out_pcm16[i] = sum / channels;
|
||||
}
|
||||
} else {
|
||||
out_pcm16.assign(src, src + samples);
|
||||
}
|
||||
}
|
||||
|
||||
size_t frame_bytes = 960; // 480 * 2
|
||||
size_t total_bytes = out_pcm16.size() * sizeof(short);
|
||||
unsigned char* p = (unsigned char*)out_pcm16.data();
|
||||
for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) {
|
||||
_owner->cb_(p + offset, frame_bytes, "audio");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
class SpeakerCapturerMacosx::Impl {
|
||||
public:
|
||||
SCStreamConfiguration* config = nil;
|
||||
SCStream* stream = nil;
|
||||
SpeakerCaptureDelegate* delegate = nil;
|
||||
dispatch_queue_t queue = nil;
|
||||
SCShareableContent* content = nil;
|
||||
SCDisplay* mainDisplay = nil;
|
||||
|
||||
~Impl() {
|
||||
if (stream) {
|
||||
[stream stopCaptureWithCompletionHandler:^(NSError* _Nullable error){
|
||||
}];
|
||||
stream = nil;
|
||||
}
|
||||
delegate = nil;
|
||||
if (queue) {
|
||||
queue = nil;
|
||||
}
|
||||
content = nil;
|
||||
mainDisplay = nil;
|
||||
config = nil;
|
||||
}
|
||||
};
|
||||
|
||||
SpeakerCapturerMacosx::SpeakerCapturerMacosx() {
|
||||
impl_ = new Impl();
|
||||
inited_ = false;
|
||||
cb_ = nullptr;
|
||||
}
|
||||
|
||||
SpeakerCapturerMacosx::~SpeakerCapturerMacosx() {
|
||||
Destroy();
|
||||
delete impl_;
|
||||
impl_ = nullptr;
|
||||
}
|
||||
|
||||
int SpeakerCapturerMacosx::Init(speaker_data_cb cb) {
|
||||
if (inited_) {
|
||||
return 0;
|
||||
}
|
||||
cb_ = cb;
|
||||
|
||||
impl_->config = [[SCStreamConfiguration alloc] init];
|
||||
impl_->config.capturesAudio = YES;
|
||||
impl_->config.sampleRate = 48000;
|
||||
impl_->config.channelCount = 1;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block NSError* error = nil;
|
||||
[SCShareableContent
|
||||
getShareableContentWithCompletionHandler:^(SCShareableContent* c, NSError* e) {
|
||||
impl_->content = c;
|
||||
error = e;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (error || !impl_->content) {
|
||||
LOG_ERROR("Failed to get shareable content: {}",
|
||||
std::string([error.localizedDescription UTF8String]));
|
||||
return -1;
|
||||
}
|
||||
|
||||
CGDirectDisplayID mainDisplayId = CGMainDisplayID();
|
||||
impl_->mainDisplay = nil;
|
||||
for (SCDisplay* d in impl_->content.displays) {
|
||||
if (d.displayID == mainDisplayId) {
|
||||
impl_->mainDisplay = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!impl_->mainDisplay) {
|
||||
LOG_ERROR("Main display not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!impl_->queue) {
|
||||
impl_->queue = dispatch_queue_create("SpeakerAudio.Queue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
|
||||
inited_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerMacosx::Start() {
|
||||
if (!inited_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (impl_->stream) {
|
||||
dispatch_semaphore_t semaStop = dispatch_semaphore_create(0);
|
||||
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
|
||||
dispatch_semaphore_signal(semaStop);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaStop, DISPATCH_TIME_FOREVER);
|
||||
impl_->stream = nil;
|
||||
impl_->delegate = nil;
|
||||
}
|
||||
|
||||
impl_->delegate = [[SpeakerCaptureDelegate alloc] initWithOwner:this];
|
||||
SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:impl_->mainDisplay
|
||||
excludingWindows:@[]];
|
||||
impl_->stream = [[SCStream alloc] initWithFilter:filter
|
||||
configuration:impl_->config
|
||||
delegate:impl_->delegate];
|
||||
|
||||
NSError* addOutputError = nil;
|
||||
BOOL ok = [impl_->stream addStreamOutput:impl_->delegate
|
||||
type:SCStreamOutputTypeAudio
|
||||
sampleHandlerQueue:impl_->queue
|
||||
error:&addOutputError];
|
||||
if (!ok || addOutputError) {
|
||||
LOG_ERROR("addStreamOutput error: {}",
|
||||
std::string([addOutputError.localizedDescription UTF8String]));
|
||||
impl_->stream = nil;
|
||||
impl_->delegate = nil;
|
||||
return -1;
|
||||
}
|
||||
|
||||
dispatch_semaphore_t semaStart = dispatch_semaphore_create(0);
|
||||
__block int ret = 0;
|
||||
[impl_->stream startCaptureWithCompletionHandler:^(NSError* _Nullable error) {
|
||||
if (error) {
|
||||
LOG_ERROR("startCaptureWithCompletionHandler error: {}",
|
||||
std::string([error.localizedDescription UTF8String]));
|
||||
ret = -1;
|
||||
}
|
||||
dispatch_semaphore_signal(semaStart);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaStart, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SpeakerCapturerMacosx::Stop() {
|
||||
if (!inited_) return -1;
|
||||
if (!impl_->stream) return -1;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
|
||||
if (error) {
|
||||
LOG_ERROR("stopCaptureWithCompletionHandler error: {}",
|
||||
std::string([error.localizedDescription UTF8String]));
|
||||
}
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
impl_->stream = nil;
|
||||
impl_->delegate = nil;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerMacosx::Destroy() {
|
||||
Stop();
|
||||
cb_ = nullptr;
|
||||
|
||||
if (impl_) {
|
||||
impl_->config = nil;
|
||||
impl_->content = nil;
|
||||
impl_->mainDisplay = nil;
|
||||
if (impl_->queue) {
|
||||
impl_->queue = nil;
|
||||
}
|
||||
}
|
||||
inited_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerMacosx::Pause() { return 0; }
|
||||
|
||||
int SpeakerCapturerMacosx::Resume() { return Start(); }
|
||||
27
src/speaker_capturer/speaker_capturer.h
Normal file
27
src/speaker_capturer/speaker_capturer.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-07-22
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SPEAKER_CAPTURER_H_
|
||||
#define _SPEAKER_CAPTURER_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
class SpeakerCapturer {
|
||||
public:
|
||||
typedef std::function<void(unsigned char *, size_t, const char *)>
|
||||
speaker_data_cb;
|
||||
|
||||
public:
|
||||
virtual ~SpeakerCapturer() {}
|
||||
|
||||
public:
|
||||
virtual int Init(speaker_data_cb cb) = 0;
|
||||
virtual int Destroy() = 0;
|
||||
virtual int Start() = 0;
|
||||
virtual int Stop() = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
36
src/speaker_capturer/speaker_capturer_factory.h
Normal file
36
src/speaker_capturer/speaker_capturer_factory.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-07-22
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SPEAKER_CAPTURER_FACTORY_H_
|
||||
#define _SPEAKER_CAPTURER_FACTORY_H_
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "speaker_capturer_wasapi.h"
|
||||
#elif __linux__
|
||||
#include "speaker_capturer_linux.h"
|
||||
#elif __APPLE__
|
||||
#include "speaker_capturer_macosx.h"
|
||||
#endif
|
||||
|
||||
class SpeakerCapturerFactory {
|
||||
public:
|
||||
virtual ~SpeakerCapturerFactory() {}
|
||||
|
||||
public:
|
||||
SpeakerCapturer* Create() {
|
||||
#ifdef _WIN32
|
||||
return new SpeakerCapturerWasapi();
|
||||
#elif __linux__
|
||||
return new SpeakerCapturerLinux();
|
||||
#elif __APPLE__
|
||||
return new SpeakerCapturerMacosx();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
101
src/speaker_capturer/windows/speaker_capturer_wasapi.cpp
Normal file
101
src/speaker_capturer/windows/speaker_capturer_wasapi.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "speaker_capturer_wasapi.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
|
||||
#define SAVE_AUDIO_FILE 0
|
||||
|
||||
static ma_device_config device_config_;
|
||||
static ma_device device_;
|
||||
static ma_format format_ = ma_format_s16;
|
||||
static ma_uint32 sample_rate_ = ma_standard_sample_rate_48000;
|
||||
static ma_uint32 channels_ = 1;
|
||||
static FILE* fp_ = nullptr;
|
||||
|
||||
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
|
||||
ma_uint32 frameCount) {
|
||||
SpeakerCapturerWasapi* ptr = (SpeakerCapturerWasapi*)pDevice->pUserData;
|
||||
if (ptr) {
|
||||
if (SAVE_AUDIO_FILE) {
|
||||
fwrite(pInput, frameCount * ma_get_bytes_per_frame(format_, channels_), 1,
|
||||
fp_);
|
||||
}
|
||||
|
||||
ptr->GetCallback()((unsigned char*)pInput,
|
||||
frameCount * ma_get_bytes_per_frame(format_, channels_),
|
||||
"audio");
|
||||
}
|
||||
|
||||
(void)pOutput;
|
||||
}
|
||||
|
||||
SpeakerCapturerWasapi::speaker_data_cb SpeakerCapturerWasapi::GetCallback() {
|
||||
return cb_;
|
||||
}
|
||||
|
||||
SpeakerCapturerWasapi::SpeakerCapturerWasapi() {}
|
||||
|
||||
SpeakerCapturerWasapi::~SpeakerCapturerWasapi() {
|
||||
if (SAVE_AUDIO_FILE) {
|
||||
fclose(fp_);
|
||||
}
|
||||
}
|
||||
|
||||
int SpeakerCapturerWasapi::Init(speaker_data_cb cb) {
|
||||
if (inited_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cb_ = cb;
|
||||
|
||||
if (SAVE_AUDIO_FILE) {
|
||||
fopen_s(&fp_, "system_audio.pcm", "wb");
|
||||
}
|
||||
|
||||
ma_result result;
|
||||
ma_backend backends[] = {ma_backend_wasapi};
|
||||
|
||||
device_config_ = ma_device_config_init(ma_device_type_loopback);
|
||||
device_config_.capture.pDeviceID = NULL;
|
||||
device_config_.capture.format = format_;
|
||||
device_config_.capture.channels = channels_;
|
||||
device_config_.sampleRate = sample_rate_;
|
||||
device_config_.dataCallback = data_callback;
|
||||
device_config_.pUserData = this;
|
||||
|
||||
result = ma_device_init_ex(backends, sizeof(backends) / sizeof(backends[0]),
|
||||
NULL, &device_config_, &device_);
|
||||
if (result != MA_SUCCESS) {
|
||||
LOG_ERROR("Failed to initialize loopback device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
inited_ = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerWasapi::Start() {
|
||||
ma_result result = ma_device_start(&device_);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_device_uninit(&device_);
|
||||
LOG_ERROR("Failed to start device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerWasapi::Stop() {
|
||||
ma_device_stop(&device_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerWasapi::Destroy() {
|
||||
ma_device_uninit(&device_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpeakerCapturerWasapi::Pause() { return 0; }
|
||||
35
src/speaker_capturer/windows/speaker_capturer_wasapi.h
Normal file
35
src/speaker_capturer/windows/speaker_capturer_wasapi.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-08-15
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SPEAKER_CAPTURER_WASAPI_H_
|
||||
#define _SPEAKER_CAPTURER_WASAPI_H_
|
||||
|
||||
#include "speaker_capturer.h"
|
||||
|
||||
class SpeakerCapturerWasapi : public SpeakerCapturer {
|
||||
public:
|
||||
SpeakerCapturerWasapi();
|
||||
~SpeakerCapturerWasapi();
|
||||
|
||||
public:
|
||||
virtual int Init(speaker_data_cb cb);
|
||||
virtual int Destroy();
|
||||
virtual int Start();
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
int Resume();
|
||||
|
||||
speaker_data_cb GetCallback();
|
||||
|
||||
private:
|
||||
speaker_data_cb cb_ = nullptr;
|
||||
|
||||
private:
|
||||
bool inited_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,232 +0,0 @@
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
static int get_format_from_sample_fmt(const char **fmt,
|
||||
enum AVSampleFormat sample_fmt) {
|
||||
int i;
|
||||
struct sample_fmt_entry {
|
||||
enum AVSampleFormat sample_fmt;
|
||||
const char *fmt_be, *fmt_le;
|
||||
} sample_fmt_entries[] = {
|
||||
{AV_SAMPLE_FMT_U8, "u8", "u8"},
|
||||
{AV_SAMPLE_FMT_S16, "s16be", "s16le"},
|
||||
{AV_SAMPLE_FMT_S32, "s32be", "s32le"},
|
||||
{AV_SAMPLE_FMT_FLT, "f32be", "f32le"},
|
||||
{AV_SAMPLE_FMT_DBL, "f64be", "f64le"},
|
||||
};
|
||||
*fmt = NULL;
|
||||
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
|
||||
struct sample_fmt_entry *entry = &sample_fmt_entries[i];
|
||||
if (sample_fmt == entry->sample_fmt) {
|
||||
*fmt = AV_NE(entry->fmt_be, entry->fmt_le);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Sample format %s not supported as output format\n",
|
||||
av_get_sample_fmt_name(sample_fmt));
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill dst buffer with nb_samples, generated starting from t. <20><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>
|
||||
*/
|
||||
static void fill_samples(double *dst, int nb_samples, int nb_channels,
|
||||
int sample_rate, double *t) {
|
||||
int i, j;
|
||||
double tincr = 1.0 / sample_rate, *dstp = dst;
|
||||
const double c = 2 * M_PI * 440.0;
|
||||
|
||||
/* generate sin tone with 440Hz frequency and duplicated channels */
|
||||
for (i = 0; i < nb_samples; i++) {
|
||||
*dstp = sin(c * *t);
|
||||
for (j = 1; j < nb_channels; j++) dstp[j] = dstp[0];
|
||||
dstp += nb_channels;
|
||||
*t += tincr;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
|
||||
int src_rate = 44100;
|
||||
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_DBL;
|
||||
int src_nb_channels = 0;
|
||||
uint8_t **src_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
|
||||
int src_linesize;
|
||||
int src_nb_samples = 1024;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO;
|
||||
int dst_rate = 48000;
|
||||
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
int dst_nb_channels = 0;
|
||||
uint8_t **dst_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
|
||||
int dst_linesize;
|
||||
int dst_nb_samples;
|
||||
int max_dst_nb_samples;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||
const char *dst_filename = NULL; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD><EFBFBD><F3B2A5B7><EFBFBD>֤
|
||||
FILE *dst_file;
|
||||
|
||||
int dst_bufsize;
|
||||
const char *fmt;
|
||||
|
||||
// <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5>
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
double t;
|
||||
int ret;
|
||||
|
||||
dst_filename = "res.pcm";
|
||||
|
||||
dst_file = fopen(dst_filename, "wb");
|
||||
if (!dst_file) {
|
||||
fprintf(stderr, "Could not open destination file %s\n", dst_filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD>
|
||||
/* create resampler context */
|
||||
swr_ctx = swr_alloc();
|
||||
if (!swr_ctx) {
|
||||
fprintf(stderr, "Could not allocate resampler context\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto end;
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/* set options */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
|
||||
|
||||
// <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
|
||||
/* initialize the resampling context */
|
||||
if ((ret = swr_init(swr_ctx)) < 0) {
|
||||
fprintf(stderr, "Failed to initialize the resampling context\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* allocate source and destination samples buffers */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD>
|
||||
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
|
||||
src_nb_channels, src_nb_samples,
|
||||
src_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate source samples\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* compute the number of converted samples: buffering is avoided
|
||||
* ensuring that the output buffer will contain at least all the
|
||||
* converted input samples */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
max_dst_nb_samples = dst_nb_samples =
|
||||
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
|
||||
/* buffer is going to be directly written to a rawaudio file, no alignment
|
||||
*/
|
||||
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
|
||||
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
|
||||
dst_nb_channels, dst_nb_samples,
|
||||
dst_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate destination samples\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
t = 0;
|
||||
do {
|
||||
/* generate synthetic audio */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
|
||||
fill_samples((double *)src_data[0], src_nb_samples, src_nb_channels,
|
||||
src_rate, &t);
|
||||
|
||||
/* compute destination number of samples */
|
||||
int64_t delay = swr_get_delay(swr_ctx, src_rate);
|
||||
dst_nb_samples =
|
||||
av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
if (dst_nb_samples > max_dst_nb_samples) {
|
||||
av_freep(&dst_data[0]);
|
||||
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
|
||||
dst_nb_samples, dst_sample_fmt, 1);
|
||||
if (ret < 0) break;
|
||||
max_dst_nb_samples = dst_nb_samples;
|
||||
}
|
||||
// int fifo_size = swr_get_out_samples(swr_ctx,src_nb_samples);
|
||||
// printf("fifo_size:%d\n", fifo_size);
|
||||
// if(fifo_size < 1024)
|
||||
// continue;
|
||||
|
||||
/* convert to destination format */
|
||||
// ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const
|
||||
// uint8_t **)src_data, src_nb_samples);
|
||||
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
|
||||
(const uint8_t **)src_data, src_nb_samples);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error while converting\n");
|
||||
goto end;
|
||||
}
|
||||
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
|
||||
ret, dst_sample_fmt, 1);
|
||||
if (dst_bufsize < 0) {
|
||||
fprintf(stderr, "Could not get sample buffer size\n");
|
||||
goto end;
|
||||
}
|
||||
printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret);
|
||||
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
|
||||
} while (t < 10);
|
||||
|
||||
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, NULL, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error while converting\n");
|
||||
goto end;
|
||||
}
|
||||
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret,
|
||||
dst_sample_fmt, 1);
|
||||
if (dst_bufsize < 0) {
|
||||
fprintf(stderr, "Could not get sample buffer size\n");
|
||||
goto end;
|
||||
}
|
||||
printf("flush in:%d out:%d\n", 0, ret);
|
||||
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
|
||||
|
||||
if ((ret = get_format_from_sample_fmt(&fmt, dst_sample_fmt)) < 0) goto end;
|
||||
fprintf(stderr,
|
||||
"Resampling succeeded. Play the output file with the command:\n"
|
||||
"ffplay -f %s -channel_layout %" PRId64 " -channels %d -ar %d %s\n",
|
||||
fmt, dst_ch_layout, dst_nb_channels, dst_rate, dst_filename);
|
||||
|
||||
end:
|
||||
fclose(dst_file);
|
||||
|
||||
if (src_data) av_freep(&src_data[0]);
|
||||
av_freep(&src_data);
|
||||
|
||||
if (dst_data) av_freep(&dst_data[0]);
|
||||
av_freep(&dst_data);
|
||||
|
||||
swr_free(&swr_ctx);
|
||||
return ret < 0;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int ret;
|
||||
SDL_AudioSpec wanted_spec, obtained_spec;
|
||||
|
||||
// Initialize SDL
|
||||
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL: %s",
|
||||
SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set audio format
|
||||
wanted_spec.freq = 44100; // Sample rate
|
||||
wanted_spec.format =
|
||||
AUDIO_F32SYS; // Sample format (32-bit float, system byte order)
|
||||
wanted_spec.channels = 2; // Number of channels (stereo)
|
||||
wanted_spec.samples = 1024; // Buffer size (in samples)
|
||||
wanted_spec.callback = NULL; // Audio callback function (not used here)
|
||||
|
||||
// Open audio device
|
||||
ret = SDL_OpenAudio(&wanted_spec, &obtained_spec);
|
||||
if (ret < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to open audio device: %s", SDL_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start playing audio
|
||||
SDL_PauseAudio(0);
|
||||
|
||||
// Write PCM data to audio buffer
|
||||
float *pcm_data = ...; // PCM data buffer (float, interleaved)
|
||||
int pcm_data_size = ...; // Size of PCM data buffer (in bytes)
|
||||
int bytes_written = SDL_QueueAudio(0, pcm_data, pcm_data_size);
|
||||
|
||||
// Wait until audio buffer is empty
|
||||
while (SDL_GetQueuedAudioSize(0) > 0) {
|
||||
SDL_Delay(100);
|
||||
}
|
||||
|
||||
// Stop playing audio
|
||||
SDL_PauseAudio(1);
|
||||
|
||||
// Close audio device
|
||||
SDL_CloseAudio();
|
||||
|
||||
// Quit SDL
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (SDL_Init(SDL_INIT_AUDIO)) {
|
||||
printf("SDL init error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// SDL_AudioSpec
|
||||
SDL_AudioSpec wanted_spec;
|
||||
SDL_zero(wanted_spec);
|
||||
wanted_spec.freq = 48000;
|
||||
wanted_spec.format = AUDIO_S16LSB;
|
||||
wanted_spec.channels = 2;
|
||||
wanted_spec.silence = 0;
|
||||
wanted_spec.samples = 960;
|
||||
wanted_spec.callback = NULL;
|
||||
|
||||
SDL_AudioDeviceID deviceID = 0;
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>豸
|
||||
if ((deviceID = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, NULL,
|
||||
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE)) < 2) {
|
||||
printf("could not open audio device: %s\n", SDL_GetError());
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD>ϵͳ
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(deviceID, 0);
|
||||
|
||||
FILE* fp = nullptr;
|
||||
|
||||
fopen_s(&fp, "ls.pcm", "rb+");
|
||||
if (fp == NULL) {
|
||||
printf("cannot open this file\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fp == NULL) {
|
||||
printf("error \n");
|
||||
}
|
||||
Uint32 buffer_size = 4096;
|
||||
char* buffer = (char*)malloc(buffer_size);
|
||||
|
||||
while (true) {
|
||||
if (fread(buffer, 1, buffer_size, fp) != buffer_size) {
|
||||
printf("end of file\n");
|
||||
break;
|
||||
}
|
||||
SDL_QueueAudio(deviceID, buffer, buffer_size);
|
||||
}
|
||||
|
||||
printf("Play...\n");
|
||||
|
||||
SDL_Delay(10000);
|
||||
|
||||
// Uint32 residueAudioLen = 0;
|
||||
|
||||
// while (true) {
|
||||
// residueAudioLen = SDL_GetQueuedAudioSize(deviceID);
|
||||
// printf("%10d\n", residueAudioLen);
|
||||
// if (residueAudioLen <= 0) break;
|
||||
// SDL_Delay(1);
|
||||
// }
|
||||
|
||||
// while (true) {
|
||||
// printf("1 <20><>ͣ 2 <20><><EFBFBD><EFBFBD> 3 <20>˳<EFBFBD> \n");
|
||||
// int flag = 0;
|
||||
|
||||
// scanf_s("%d", &flag);
|
||||
|
||||
// if (flag == 1)
|
||||
// SDL_PauseAudioDevice(deviceID, 1);
|
||||
// else if (flag == 2)
|
||||
// SDL_PauseAudioDevice(deviceID, 0);
|
||||
// else if (flag == 3)
|
||||
// break;
|
||||
// }
|
||||
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
static SDL_AudioDeviceID input_dev;
|
||||
static SDL_AudioDeviceID output_dev;
|
||||
|
||||
static Uint8 *buffer = 0;
|
||||
static int in_pos = 0;
|
||||
static int out_pos = 0;
|
||||
|
||||
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
|
||||
int src_rate = 48000;
|
||||
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
int src_nb_channels = 0;
|
||||
uint8_t **src_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
|
||||
int src_linesize;
|
||||
int src_nb_samples = 480;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
int64_t dst_ch_layout = AV_CH_LAYOUT_MONO;
|
||||
int dst_rate = 48000;
|
||||
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
int dst_nb_channels = 0;
|
||||
uint8_t **dst_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
|
||||
int dst_linesize;
|
||||
int dst_nb_samples;
|
||||
int max_dst_nb_samples;
|
||||
static unsigned char audio_buffer[960 * 3];
|
||||
static int audio_len = 0;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||
const char *dst_filename = NULL; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD><EFBFBD><F3B2A5B7><EFBFBD>֤
|
||||
FILE *dst_file;
|
||||
|
||||
int dst_bufsize;
|
||||
const char *fmt;
|
||||
|
||||
// <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5>
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
double t;
|
||||
int ret;
|
||||
|
||||
char *out = "audio_old.pcm";
|
||||
FILE *outfile = fopen(out, "wb+");
|
||||
|
||||
void cb_in(void *userdata, Uint8 *stream, int len) {
|
||||
// If len < 4, the printf below will probably segfault
|
||||
// SDL_QueueAudio(output_dev, stream, len);
|
||||
|
||||
int64_t delay = swr_get_delay(swr_ctx, src_rate);
|
||||
dst_nb_samples =
|
||||
av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
if (dst_nb_samples > max_dst_nb_samples) {
|
||||
av_freep(&dst_data[0]);
|
||||
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
|
||||
dst_nb_samples, dst_sample_fmt, 1);
|
||||
if (ret < 0) return;
|
||||
max_dst_nb_samples = dst_nb_samples;
|
||||
}
|
||||
|
||||
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
|
||||
(const uint8_t **)&stream, src_nb_samples);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error while converting\n");
|
||||
return;
|
||||
}
|
||||
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret,
|
||||
dst_sample_fmt, 1);
|
||||
if (dst_bufsize < 0) {
|
||||
fprintf(stderr, "Could not get sample buffer size\n");
|
||||
return;
|
||||
}
|
||||
printf("t:%f in:%d out:%d %d\n", t, src_nb_samples, ret, len);
|
||||
|
||||
memcpy(audio_buffer, dst_data[0], len);
|
||||
// SDL_QueueAudio(output_dev, dst_data[0], len);
|
||||
audio_len = len;
|
||||
}
|
||||
|
||||
void cb_out(void *userdata, Uint8 *stream, int len) {
|
||||
// If len < 4, the printf below will probably segfault
|
||||
printf("cb_out len = %d\n", len);
|
||||
SDL_memset(stream, 0, len);
|
||||
if (audio_len == 0) return;
|
||||
len = (len > audio_len ? audio_len : len);
|
||||
SDL_MixAudioFormat(stream, audio_buffer, AUDIO_S16LSB, len,
|
||||
SDL_MIX_MAXVOLUME);
|
||||
}
|
||||
|
||||
int init() {
|
||||
dst_filename = "res.pcm";
|
||||
|
||||
dst_file = fopen(dst_filename, "wb");
|
||||
if (!dst_file) {
|
||||
fprintf(stderr, "Could not open destination file %s\n", dst_filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD>
|
||||
/* create resampler context */
|
||||
swr_ctx = swr_alloc();
|
||||
if (!swr_ctx) {
|
||||
fprintf(stderr, "Could not allocate resampler context\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/* set options */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
|
||||
|
||||
// <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
|
||||
/* initialize the resampling context */
|
||||
if ((ret = swr_init(swr_ctx)) < 0) {
|
||||
fprintf(stderr, "Failed to initialize the resampling context\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* allocate source and destination samples buffers */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD>
|
||||
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
|
||||
src_nb_channels, src_nb_samples,
|
||||
src_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate source samples\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* compute the number of converted samples: buffering is avoided
|
||||
* ensuring that the output buffer will contain at least all the
|
||||
* converted input samples */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
max_dst_nb_samples = dst_nb_samples =
|
||||
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
|
||||
/* buffer is going to be directly written to a rawaudio file, no alignment */
|
||||
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
|
||||
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
|
||||
dst_nb_channels, dst_nb_samples,
|
||||
dst_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate destination samples\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
SDL_Init(SDL_INIT_AUDIO);
|
||||
|
||||
// 16Mb should be enough; the test lasts 5 seconds
|
||||
buffer = (Uint8 *)malloc(16777215);
|
||||
|
||||
SDL_AudioSpec want_in, want_out, have_in, have_out;
|
||||
|
||||
SDL_zero(want_in);
|
||||
want_in.freq = 48000;
|
||||
want_in.format = AUDIO_S16LSB;
|
||||
want_in.channels = 1;
|
||||
want_in.samples = 480;
|
||||
want_in.callback = cb_in;
|
||||
|
||||
input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in, 0);
|
||||
|
||||
printf("%d %d %d %d\n", have_in.freq, have_in.format, have_in.channels,
|
||||
have_in.samples);
|
||||
if (input_dev == 0) {
|
||||
SDL_Log("Failed to open input: %s", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_zero(want_out);
|
||||
want_out.freq = 48000;
|
||||
want_out.format = AUDIO_S16LSB;
|
||||
want_out.channels = 1;
|
||||
want_out.samples = 480;
|
||||
want_out.callback = cb_out;
|
||||
|
||||
output_dev = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out, 0);
|
||||
|
||||
printf("%d %d %d %d\n", have_out.freq, have_out.format, have_out.channels,
|
||||
have_out.samples);
|
||||
if (output_dev == 0) {
|
||||
SDL_Log("Failed to open input: %s", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(input_dev, 0);
|
||||
SDL_PauseAudioDevice(output_dev, 0);
|
||||
|
||||
while (1) {
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(output_dev);
|
||||
SDL_CloseAudioDevice(input_dev);
|
||||
free(buffer);
|
||||
|
||||
fclose(outfile);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int ret;
|
||||
AVFrame *frame = NULL;
|
||||
AVFrame *resampled_frame = NULL;
|
||||
AVCodecContext *codec_ctx = NULL;
|
||||
SwrContext *swr_ctx = NULL;
|
||||
|
||||
// Initialize FFmpeg
|
||||
av_log_set_level(AV_LOG_INFO);
|
||||
av_register_all();
|
||||
|
||||
// Allocate input frame
|
||||
frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to allocate input frame\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate output frame for resampled data
|
||||
resampled_frame = av_frame_alloc();
|
||||
if (!resampled_frame) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to allocate output frame\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set input frame properties
|
||||
frame->format = AV_SAMPLE_FMT_FLTP; // Input sample format (float planar)
|
||||
frame->channel_layout = AV_CH_LAYOUT_STEREO; // Input channel layout (stereo)
|
||||
frame->sample_rate = 44100; // Input sample rate (44100 Hz)
|
||||
frame->nb_samples = 1024; // Number of input samples
|
||||
|
||||
// Set output frame properties
|
||||
resampled_frame->format =
|
||||
AV_SAMPLE_FMT_S16; // Output sample format (signed 16-bit)
|
||||
resampled_frame->channel_layout =
|
||||
AV_CH_LAYOUT_STEREO; // Output channel layout (stereo)
|
||||
resampled_frame->sample_rate = 48000; // Output sample rate (48000 Hz)
|
||||
resampled_frame->nb_samples = av_rescale_rnd(
|
||||
frame->nb_samples, resampled_frame->sample_rate, frame->sample_rate,
|
||||
AV_ROUND_UP); // Number of output samples
|
||||
|
||||
// Initialize resampler context
|
||||
swr_ctx = swr_alloc_set_opts(
|
||||
NULL, av_get_default_channel_layout(resampled_frame->channel_layout),
|
||||
av_get_default_sample_fmt(resampled_frame->format),
|
||||
resampled_frame->sample_rate,
|
||||
av_get_default_channel_layout(frame->channel_layout),
|
||||
av_get_default_sample_fmt(frame->format), frame->sample_rate, 0, NULL);
|
||||
if (!swr_ctx) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to allocate resampler context\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialize and configure the resampler
|
||||
if ((ret = swr_init(swr_ctx)) < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to initialize resampler context: %s\n",
|
||||
av_err2str(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate buffer for output samples
|
||||
ret = av_samples_alloc(resampled_frame->data, resampled_frame->linesize,
|
||||
resampled_frame->channels, resampled_frame->nb_samples,
|
||||
resampled_frame->format, 0);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to allocate output samples buffer: %s\n",
|
||||
av_err2str(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Resample the input data
|
||||
ret = swr_convert(swr_ctx, resampled_frame->data, resampled_frame->nb_samples,
|
||||
(const uint8_t **)frame->data, frame->nb_samples);
|
||||
if (ret < 0) {
|
||||
av_log(NULL, AV_LOG_ERROR, "Failed to resample input data: %s\n",
|
||||
av_err2str(ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Cleanup and free resources
|
||||
swr_free(&swr_ctx);
|
||||
av_frame_free(&frame);
|
||||
av_frame_free(&resampled_frame);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavdevice/avdevice.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
static SDL_AudioDeviceID input_dev;
|
||||
static SDL_AudioDeviceID output_dev;
|
||||
|
||||
static Uint8 *buffer = 0;
|
||||
static int in_pos = 0;
|
||||
static int out_pos = 0;
|
||||
|
||||
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
|
||||
int src_rate = 48000;
|
||||
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT;
|
||||
int src_nb_channels = 0;
|
||||
uint8_t **src_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
|
||||
int src_linesize;
|
||||
int src_nb_samples = 480;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO;
|
||||
int dst_rate = 48000;
|
||||
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
int dst_nb_channels = 0;
|
||||
uint8_t **dst_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
|
||||
int dst_linesize;
|
||||
int dst_nb_samples;
|
||||
int max_dst_nb_samples;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||
const char *dst_filename = NULL; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD><EFBFBD><F3B2A5B7><EFBFBD>֤
|
||||
FILE *dst_file;
|
||||
|
||||
int dst_bufsize;
|
||||
const char *fmt;
|
||||
|
||||
// <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5>
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
double t;
|
||||
int ret;
|
||||
|
||||
char *out = "audio_old.pcm";
|
||||
FILE *outfile = fopen(out, "wb+");
|
||||
|
||||
void cb_in(void *userdata, Uint8 *stream, int len) {
|
||||
// If len < 4, the printf below will probably segfault
|
||||
{
|
||||
fwrite(stream, 1, len, outfile);
|
||||
fflush(outfile);
|
||||
}
|
||||
{
|
||||
int64_t delay = swr_get_delay(swr_ctx, src_rate);
|
||||
dst_nb_samples =
|
||||
av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
if (dst_nb_samples > max_dst_nb_samples) {
|
||||
av_freep(&dst_data[0]);
|
||||
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
|
||||
dst_nb_samples, dst_sample_fmt, 1);
|
||||
if (ret < 0) return;
|
||||
max_dst_nb_samples = dst_nb_samples;
|
||||
}
|
||||
|
||||
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
|
||||
(const uint8_t **)&stream, src_nb_samples);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error while converting\n");
|
||||
return;
|
||||
}
|
||||
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
|
||||
ret, dst_sample_fmt, 1);
|
||||
if (dst_bufsize < 0) {
|
||||
fprintf(stderr, "Could not get sample buffer size\n");
|
||||
return;
|
||||
}
|
||||
printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret);
|
||||
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
|
||||
}
|
||||
}
|
||||
|
||||
void cb_out(void *userdata, Uint8 *stream, int len) {
|
||||
// If len < 4, the printf below will probably segfault
|
||||
|
||||
SDL_memcpy(buffer + out_pos, stream, len);
|
||||
out_pos += len;
|
||||
}
|
||||
|
||||
int init() {
|
||||
dst_filename = "res.pcm";
|
||||
|
||||
dst_file = fopen(dst_filename, "wb");
|
||||
if (!dst_file) {
|
||||
fprintf(stderr, "Could not open destination file %s\n", dst_filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD>
|
||||
/* create resampler context */
|
||||
swr_ctx = swr_alloc();
|
||||
if (!swr_ctx) {
|
||||
fprintf(stderr, "Could not allocate resampler context\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/* set options */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
|
||||
|
||||
// <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
|
||||
/* initialize the resampling context */
|
||||
if ((ret = swr_init(swr_ctx)) < 0) {
|
||||
fprintf(stderr, "Failed to initialize the resampling context\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* allocate source and destination samples buffers */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD>
|
||||
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
|
||||
src_nb_channels, src_nb_samples,
|
||||
src_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate source samples\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* compute the number of converted samples: buffering is avoided
|
||||
* ensuring that the output buffer will contain at least all the
|
||||
* converted input samples */
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
max_dst_nb_samples = dst_nb_samples =
|
||||
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
|
||||
|
||||
/* buffer is going to be directly written to a rawaudio file, no alignment */
|
||||
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
|
||||
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
|
||||
dst_nb_channels, dst_nb_samples,
|
||||
dst_sample_fmt, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Could not allocate destination samples\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
SDL_Init(SDL_INIT_AUDIO);
|
||||
|
||||
// 16Mb should be enough; the test lasts 5 seconds
|
||||
buffer = (Uint8 *)malloc(16777215);
|
||||
|
||||
SDL_AudioSpec want_in, want_out, have_in, have_out;
|
||||
|
||||
SDL_zero(want_in);
|
||||
want_in.freq = 48000;
|
||||
want_in.format = AUDIO_F32LSB;
|
||||
want_in.channels = 2;
|
||||
want_in.samples = 960;
|
||||
want_in.callback = cb_in;
|
||||
|
||||
input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
|
||||
printf("%d %d %d %d\n", have_in.freq, have_in.format, have_in.channels,
|
||||
have_in.samples);
|
||||
if (input_dev == 0) {
|
||||
SDL_Log("Failed to open input: %s", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(input_dev, 0);
|
||||
SDL_PauseAudioDevice(output_dev, 0);
|
||||
|
||||
SDL_Delay(5000);
|
||||
|
||||
SDL_CloseAudioDevice(output_dev);
|
||||
SDL_CloseAudioDevice(input_dev);
|
||||
free(buffer);
|
||||
|
||||
fclose(outfile);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user