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

|
||||
|
||||
## 简介
|
||||
|
||||
CrossDesk 是一个轻量级的跨平台远程桌面软件。
|
||||
|
||||
CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)),视频软硬编解码(H264/AV1),音频编解码([Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。
|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
在菜单栏“对端ID”处输入远端桌面的ID,点击“→”即可发起远程连接。
|
||||
|
||||

|
||||
|
||||
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。
|
||||
|
||||

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

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

|
||||
|
||||
# Intro
|
||||
|
||||
CrossDesk is a lightweight cross-platform remote desktop software.
|
||||
|
||||
CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)).
|
||||
|
||||
## Usage
|
||||
|
||||
Enter the remote desktop ID in the menu bar’s “Remote ID” field and click “→” to initiate a remote connection.
|
||||
|
||||

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

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

|
||||
|
||||
## How to build
|
||||
|
||||
Requirements:
|
||||
- [xmake](https://xmake.io/#/guide/installation)
|
||||
- [cmake](https://cmake.org/download/)
|
||||
|
||||
Following packages need to be installed on Linux:
|
||||
|
||||
```
|
||||
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
Build:
|
||||
```
|
||||
git clone https://github.com/kunkundi/crossdesk.git
|
||||
|
||||
cd crossdesk
|
||||
|
||||
git submodule init
|
||||
|
||||
git submodule update
|
||||
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
Run:
|
||||
```
|
||||
xmake r crossdesk
|
||||
```
|
||||
|
||||
#### Development Without CUDA Environment
|
||||
|
||||
For **Linux developers who do not have a CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
|
||||
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
|
||||
|
||||
After entering the container, download the project and run:
|
||||
```
|
||||
export CUDA_PATH=/usr/local/cuda
|
||||
export XMAKE_GLOBALDIR=/data
|
||||
|
||||
xmake b --root -vy crossdesk
|
||||
```
|
||||
|
||||
For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment:
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
After the installation is complete, execute:
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
The output will look like this:
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
From the output above, locate the CUDA installation directory — this is the path pointed to by installdir.
|
||||
Add this path to your system environment variable CUDA_PATH, or set it in the terminal using:
|
||||
```
|
||||
set CUDA_PATH=path_to_cuda_installdir:
|
||||
```
|
||||
Then re-run:
|
||||
```
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### Notice
|
||||
If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment.
|
||||
|
||||
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
|
||||
|
||||
## About Xmake
|
||||
#### Installing Xmake
|
||||
|
||||
You can install Xmake using one of the following methods:
|
||||
|
||||
Using curl:
|
||||
```
|
||||
curl -fsSL https://xmake.io/shget.text | bash
|
||||
```
|
||||
Using wget:
|
||||
```
|
||||
wget https://xmake.io/shget.text -O - | bash
|
||||
```
|
||||
Using powershell:
|
||||
```
|
||||
irm https://xmake.io/psget.text | iex
|
||||
```
|
||||
|
||||
#### Build Options
|
||||
```
|
||||
# Switch build mode
|
||||
xmake f -m debug/release
|
||||
|
||||
# Optional build parameters
|
||||
-r : Rebuild the target
|
||||
-v : Show detailed build logs
|
||||
-y : Automatically confirm prompts
|
||||
|
||||
# Example
|
||||
xmake b -vy crossdesk
|
||||
```
|
||||
|
||||
#### Run Options
|
||||
```
|
||||
# Run in debug mode
|
||||
xmake r -d crossdesk
|
||||
```
|
||||
|
||||
For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) .
|
||||
|
||||
## Self-Hosted Server
|
||||
It is recommended to deploy CrossDesk Server using Docker.
|
||||
```
|
||||
sudo docker run -d \
|
||||
--name crossdesk_server \
|
||||
--network host \
|
||||
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
|
||||
-e CROSSDESK_SERVER_PORT=xxxx \
|
||||
-e COTURN_PORT=xxxx \
|
||||
-e MIN_PORT=xxxxx \
|
||||
-e MAX_PORT=xxxxx \
|
||||
-v /path/to/your/certs:/crossdesk-server/certs \
|
||||
-v /path/to/your/db:/crossdesk-server/db \
|
||||
-v /path/to/your/logs:/crossdesk-server/logs \
|
||||
crossdesk/crossdesk-server:v1.0.0
|
||||
```
|
||||
|
||||
The parameters you need to pay attention to are as follows:
|
||||
|
||||
- **EXTERNAL_IP**: The server's public IP, corresponding to the **Server Address** in the CrossDesk client **Self-Hosted Server Configuration**.
|
||||
|
||||
- **INTERNAL_IP**: The server's internal IP.
|
||||
|
||||
- **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
|
||||
|
||||
- **COTURN_PORT**: The port used by Coturn, corresponding to the **Relay Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
|
||||
|
||||
- **MIN_PORT** and **MAX_PORT**: The range of ports used by the self-hosted server, corresponding to the **Minimum Port** and **Maximum Port** in the CrossDesk client **Self-Hosted Server Configuration**. Example: 50000-60000. It depends on the number of devices connected to the server.
|
||||
|
||||
- **/path/to/your/certs**: Directory for certificate files.
|
||||
|
||||
- **/path/to/your/db**: CrossDesk Server device management database.
|
||||
|
||||
- **/path/to/your/logs**: Log directory.
|
||||
|
||||
**Note**:
|
||||
- **/path/to/your/ is an example path; please replace it with your actual path. The mounted directories must be created in advance, otherwise the container will fail.**
|
||||
- **The server must open the following ports: 3478/udp, 3478/tcp, 30000-60000/udp, CROSSDESK_SERVER_PORT/tcp.**
|
||||
|
||||
## Certificate Files
|
||||
The client needs to load the root certificate, and the server needs to load the server private key and server certificate.
|
||||
|
||||
If you already have an SSL certificate, you can skip the following certificate generation steps.
|
||||
|
||||
For users without a certificate, you can use the script below to generate the certificate files:
|
||||
```
|
||||
# Create certificate generation script
|
||||
vim generate_certs.sh
|
||||
```
|
||||
Copy the following into the script:
|
||||
```
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Check arguments
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <SERVER_IP>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVER_IP="$1"
|
||||
|
||||
# Filenames
|
||||
ROOT_KEY="crossdesk.cn_root.key"
|
||||
ROOT_CERT="crossdesk.cn_root.crt"
|
||||
SERVER_KEY="crossdesk.cn.key"
|
||||
SERVER_CSR="crossdesk.cn.csr"
|
||||
SERVER_CERT="crossdesk.cn_bundle.crt"
|
||||
FULLCHAIN_CERT="crossdesk.cn_fullchain.crt"
|
||||
|
||||
# Certificate subject
|
||||
SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP"
|
||||
|
||||
# 1. Generate root certificate
|
||||
echo "Generating root private key..."
|
||||
openssl genrsa -out "$ROOT_KEY" 4096
|
||||
|
||||
echo "Generating self-signed root certificate..."
|
||||
openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ"
|
||||
|
||||
# 2. Generate server private key
|
||||
echo "Generating server private key..."
|
||||
openssl genrsa -out "$SERVER_KEY" 2048
|
||||
|
||||
# 3. Generate server CSR
|
||||
echo "Generating server CSR..."
|
||||
openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ"
|
||||
|
||||
# 4. Create temporary OpenSSL config file with SAN
|
||||
SAN_CONF="san.cnf"
|
||||
cat > $SAN_CONF <<EOL
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
prompt = no
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = CN
|
||||
ST = Zhejiang
|
||||
L = Hangzhou
|
||||
O = CrossDesk
|
||||
OU = CrossDesk
|
||||
CN = $SERVER_IP
|
||||
|
||||
[ req_ext ]
|
||||
subjectAltName = IP:$SERVER_IP
|
||||
EOL
|
||||
|
||||
# 5. Sign server certificate with root certificate (including SAN)
|
||||
echo "Signing server certificate with root certificate..."
|
||||
openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \
|
||||
-out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext
|
||||
|
||||
# 6. Generate full chain certificate
|
||||
cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT"
|
||||
|
||||
# 7. Clean up intermediate files
|
||||
rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT"
|
||||
|
||||
echo "Generation complete. Deployment files:"
|
||||
echo " Client root certificate: $ROOT_CERT"
|
||||
echo " Server private key: $SERVER_KEY"
|
||||
echo " Server certificate: $SERVER_CERT"
|
||||
```
|
||||
Execute:
|
||||
```
|
||||
chmod +x generate_certs.sh
|
||||
./generate_certs.sh EXTERNAL_IP
|
||||
|
||||
# example ./generate_certs.sh 111.111.111.111
|
||||
```
|
||||
Expected output:
|
||||
```
|
||||
Generating root private key...
|
||||
Generating self-signed root certificate...
|
||||
Generating server private key...
|
||||
Generating server CSR...
|
||||
Signing server certificate with root certificate...
|
||||
Certificate request self-signature ok
|
||||
subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx
|
||||
cleaning up intermediate files...
|
||||
Generation complete. Deployment files::
|
||||
Client root certificate:: crossdesk.cn_root.crt
|
||||
Server private key: crossdesk.cn.key
|
||||
Server certificate: crossdesk.cn_bundle.crt
|
||||
```
|
||||
|
||||
#### Server Side
|
||||
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
|
||||
|
||||
#### Client Side
|
||||
1. Click the settings icon in the top-right corner to enter the settings page.<br>
|
||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
||||
|
||||
2. Click **Self-Hosted Server Configuration**.<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
||||
|
||||
3. In the **Certificate File Path** selection, locate and select the **crossdesk.cn_root.crt** file.<br><br>
|
||||
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
|
||||
|
||||
4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
|
||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
||||
|
||||
# FAQ
|
||||
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
|
||||
@@ -1,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
|
||||
33
docs/FAQ.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 常见问题(FAQ)
|
||||
|
||||
欢迎来到 **CrossDesk 常见问题** 页面!
|
||||
这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。
|
||||
|
||||
---
|
||||
|
||||
### Q1. 对等连接失败
|
||||
**A:**
|
||||
打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。
|
||||
|
||||
<img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" />
|
||||
|
||||
由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8)
|
||||
|
||||
### Q2. Windows 无 CUDA 环境下编译
|
||||
**A:**
|
||||
运行下面的命令安装 CUDA 编译环境。
|
||||
```
|
||||
xmake require -vy "cuda 12.6.3"
|
||||
```
|
||||
安装完成后执行
|
||||
```
|
||||
xmake require --info "cuda 12.6.3"
|
||||
```
|
||||
输出如下
|
||||
|
||||
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
|
||||
|
||||
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir,重新执行 xmake b -vy crossdesk 即可。
|
||||
[Issue #6](https://github.com/kunkundi/crossdesk/issues/6)
|
||||
|
||||
---
|
||||
BIN
icons/linux/crossdesk_128x128.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
icons/linux/crossdesk_16x16.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
icons/linux/crossdesk_24x24.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/linux/crossdesk_256x256.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
icons/linux/crossdesk_32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/linux/crossdesk_48x48.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
icons/linux/crossdesk_512x512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
icons/linux/crossdesk_64x64.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
icons/linux/crossdesk_96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
icons/macos/crossdesk.icns
Normal file
BIN
icons/windows/crossdesk.ico
Normal file
|
After Width: | Height: | Size: 84 KiB |
120
scripts/linux/pkg_amd64.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
|
||||
APP_VERSION="$1"
|
||||
ARCHITECTURE="amd64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR"
|
||||
|
||||
cp build/linux/x86_64/release/crossdesk "$BIN_DIR/$PKG_NAME"
|
||||
chmod +x "$BIN_DIR/$PKG_NAME"
|
||||
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
cp "icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $APP_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
|
||||
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
|
||||
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
|
||||
libsndio7.0, libxcb-shm0, libpulse0
|
||||
Recommends: nvidia-cuda-toolkit
|
||||
Priority: optional
|
||||
Section: utils
|
||||
EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
Icon=$PKG_NAME
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/bin/$PKG_NAME || true
|
||||
rm -f /usr/bin/$APP_NAME || true
|
||||
rm -f /usr/share/applications/$PKG_NAME.desktop || true
|
||||
rm -rf /opt/$PKG_NAME/certs || true
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
cat > "$DEBIAN_DIR/postinst" << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CERT_SRC="/opt/crossdesk/certs"
|
||||
CERT_FILE="crossdesk.cn_root.crt"
|
||||
|
||||
for user_home in /home/*; do
|
||||
[ -d "$user_home" ] || continue
|
||||
username=$(basename "$user_home")
|
||||
config_dir="$user_home/.config/CrossDesk/certs"
|
||||
target="$config_dir/$CERT_FILE"
|
||||
|
||||
if [ ! -f "$target" ]; then
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$target" || true
|
||||
chown -R "$username:$username" "$user_home/.config/CrossDesk" || true
|
||||
echo "✔ Installed cert for $username at $target"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "/root" ]; then
|
||||
config_dir="/root/.config/CrossDesk/certs"
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true
|
||||
chown -R root:root /root/.config/CrossDesk || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
OUTPUT_FILE="${PKG_NAME}-linux-${ARCHITECTURE}-${APP_VERSION}.deb"
|
||||
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package created: $OUTPUT_FILE"
|
||||
120
scripts/linux/pkg_arm64.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PKG_NAME="crossdesk"
|
||||
APP_NAME="CrossDesk"
|
||||
|
||||
APP_VERSION="$1"
|
||||
ARCHITECTURE="arm64"
|
||||
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
|
||||
DESCRIPTION="A simple cross-platform remote desktop client."
|
||||
|
||||
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
|
||||
DEBIAN_DIR="$DEB_DIR/DEBIAN"
|
||||
BIN_DIR="$DEB_DIR/usr/bin"
|
||||
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
|
||||
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
|
||||
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR"
|
||||
|
||||
cp build/linux/arm64/release/crossdesk "$BIN_DIR"
|
||||
chmod +x "$BIN_DIR/$PKG_NAME"
|
||||
|
||||
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
|
||||
|
||||
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
|
||||
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
|
||||
cp "icons/linux/crossdesk_${size}x${size}.png" \
|
||||
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
|
||||
done
|
||||
|
||||
cat > "$DEBIAN_DIR/control" << EOF
|
||||
Package: $PKG_NAME
|
||||
Version: $APP_VERSION
|
||||
Architecture: $ARCHITECTURE
|
||||
Maintainer: $MAINTAINER
|
||||
Description: $DESCRIPTION
|
||||
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
|
||||
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
|
||||
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
|
||||
libsndio7.0, libxcb-shm0, libpulse0
|
||||
Priority: optional
|
||||
Section: utils
|
||||
EOF
|
||||
|
||||
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Version=$APP_VERSION
|
||||
Name=$APP_NAME
|
||||
Comment=$DESCRIPTION
|
||||
Exec=/usr/bin/$PKG_NAME
|
||||
Icon=$PKG_NAME
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
cat > "$DEBIAN_DIR/postrm" << EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
|
||||
rm -f /usr/bin/$PKG_NAME || true
|
||||
rm -f /usr/bin/$APP_NAME || true
|
||||
rm -f /usr/share/applications/$PKG_NAME.desktop || true
|
||||
rm -rf /opt/$PKG_NAME/certs || true
|
||||
for size in 16 24 32 48 64 96 128 256; do
|
||||
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
|
||||
done
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$DEBIAN_DIR/postrm"
|
||||
|
||||
cat > "$DEBIAN_DIR/postinst" << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CERT_SRC="/opt/crossdesk/certs"
|
||||
CERT_FILE="crossdesk.cn_root.crt"
|
||||
|
||||
for user_home in /home/*; do
|
||||
[ -d "$user_home" ] || continue
|
||||
username=$(basename "$user_home")
|
||||
config_dir="$user_home/.config/CrossDesk/certs"
|
||||
target="$config_dir/$CERT_FILE"
|
||||
|
||||
if [ ! -f "$target" ]; then
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$target" || true
|
||||
chown -R "$username:$username" "$user_home/.config/CrossDesk" || true
|
||||
echo "✔ Installed cert for $username at $target"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "/root" ]; then
|
||||
config_dir="/root/.config/CrossDesk/certs"
|
||||
mkdir -p "$config_dir" || true
|
||||
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true
|
||||
chown -R root:root /root/.config/CrossDesk || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x "$DEBIAN_DIR/postinst"
|
||||
|
||||
dpkg-deb --build "$DEB_DIR"
|
||||
|
||||
OUTPUT_FILE="crossdesk-linux-arm64-$APP_VERSION.deb"
|
||||
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
|
||||
|
||||
rm -rf "$DEB_DIR"
|
||||
|
||||
echo "✅ Deb package created: $OUTPUT_FILE"
|
||||
125
scripts/macosx/pkg_arm64.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
|
||||
APP_VERSION="$1"
|
||||
PLATFORM="macos"
|
||||
ARCH="arm64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
CERTS_SOURCE="certs"
|
||||
CERT_NAME="crossdesk.cn_root.crt"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
mkdir -p scripts
|
||||
|
||||
cat > scripts/postinstall <<'EOF'
|
||||
#!/bin/bash
|
||||
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
|
||||
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
|
||||
|
||||
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
|
||||
|
||||
mkdir -p "$DEST"
|
||||
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x scripts/postinstall
|
||||
|
||||
pkgbuild \
|
||||
--root "${CERTS_SOURCE}" \
|
||||
--identifier "${IDENTIFIER}.certs" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Library/Application Support/CrossDesk/certs" \
|
||||
--scripts scripts \
|
||||
build_pkg_temp/${APP_NAME}-certs.pkg
|
||||
|
||||
productbuild \
|
||||
--package build_pkg_temp/${APP_NAME}-component.pkg \
|
||||
--package build_pkg_temp/${APP_NAME}-certs.pkg \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
rm -rf build_pkg_temp scripts ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
125
scripts/macosx/pkg_x64.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME="crossdesk"
|
||||
APP_NAME_UPPER="CrossDesk"
|
||||
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
|
||||
APP_VERSION="$1"
|
||||
PLATFORM="macos"
|
||||
ARCH="x64"
|
||||
IDENTIFIER="cn.crossdesk.app"
|
||||
ICON_PATH="icons/macos/crossdesk.icns"
|
||||
MACOS_MIN_VERSION="10.12"
|
||||
|
||||
CERTS_SOURCE="certs"
|
||||
CERT_NAME="crossdesk.cn_root.crt"
|
||||
|
||||
APP_BUNDLE="${APP_NAME_UPPER}.app"
|
||||
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
||||
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
||||
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
||||
|
||||
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
|
||||
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
|
||||
VOL_NAME="Install ${APP_NAME_UPPER}"
|
||||
|
||||
echo "delete old files"
|
||||
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
|
||||
|
||||
mkdir -p build_pkg_temp
|
||||
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
||||
|
||||
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
|
||||
|
||||
if [ -f "${ICON_PATH}" ]; then
|
||||
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
|
||||
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
|
||||
else
|
||||
ICON_KEY=""
|
||||
fi
|
||||
|
||||
echo "generate Info.plist"
|
||||
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${IDENTIFIER}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APP_VERSION}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME_UPPER}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
${ICON_KEY}
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOS_MIN_VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要访问摄像头</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要访问麦克风</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>应用需要发送 Apple 事件</string>
|
||||
<key>NSScreenCaptureUsageDescription</key>
|
||||
<string>应用需要录屏权限以捕获屏幕内容</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
echo ".app created successfully."
|
||||
|
||||
echo "building pkg..."
|
||||
pkgbuild \
|
||||
--identifier "${IDENTIFIER}" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Applications" \
|
||||
--component "${APP_BUNDLE}" \
|
||||
build_pkg_temp/${APP_NAME}-component.pkg
|
||||
|
||||
mkdir -p scripts
|
||||
|
||||
cat > scripts/postinstall <<'EOF'
|
||||
#!/bin/bash
|
||||
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
|
||||
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
|
||||
|
||||
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
|
||||
|
||||
mkdir -p "$DEST"
|
||||
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x scripts/postinstall
|
||||
|
||||
pkgbuild \
|
||||
--root "${CERTS_SOURCE}" \
|
||||
--identifier "${IDENTIFIER}.certs" \
|
||||
--version "${APP_VERSION}" \
|
||||
--install-location "/Library/Application Support/CrossDesk/certs" \
|
||||
--scripts scripts \
|
||||
build_pkg_temp/${APP_NAME}-certs.pkg
|
||||
|
||||
productbuild \
|
||||
--package build_pkg_temp/${APP_NAME}-component.pkg \
|
||||
--package build_pkg_temp/${APP_NAME}-certs.pkg \
|
||||
"${PKG_NAME}"
|
||||
|
||||
echo "PKG package created: ${PKG_NAME}"
|
||||
|
||||
rm -rf build_pkg_temp scripts ${APP_BUNDLE}
|
||||
|
||||
echo "PKG package created successfully."
|
||||
echo "package ${APP_BUNDLE}"
|
||||
echo "installer ${PKG_NAME}"
|
||||
43
scripts/windows/crossdesk.manifest
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<!-- 应用程序标识 -->
|
||||
<assemblyIdentity
|
||||
version="1.0.0.0"
|
||||
processorArchitecture="*"
|
||||
name="CrossDesk"
|
||||
type="win32" />
|
||||
|
||||
<!-- 描述信息 -->
|
||||
<description>CrossDesk Application</description>
|
||||
|
||||
<!-- 权限:要求管理员运行 -->
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<!-- DPI 感知设置:支持高分屏 -->
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- Windows Vista/7 风格 DPI 感知 -->
|
||||
<dpiAware>true/pm</dpiAware>
|
||||
<!-- Windows 10/11 高级 DPI 感知 -->
|
||||
<dpiAwareness>PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<!-- Windows 兼容性声明 -->
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- 支持 Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- 支持 Windows 11(向下兼容 Win10 GUID) -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
</assembly>
|
||||
BIN
scripts/windows/nsProcess.dll
Normal file
164
scripts/windows/nsis_script.nsi
Normal file
@@ -0,0 +1,164 @@
|
||||
; Set search path
|
||||
!addincludedir "${__FILEDIR__}"
|
||||
|
||||
; Installer initial constants
|
||||
!define PRODUCT_NAME "CrossDesk"
|
||||
!define PRODUCT_VERSION "${VERSION}"
|
||||
!define PRODUCT_PUBLISHER "CrossDesk"
|
||||
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
|
||||
!define APP_NAME "CrossDesk"
|
||||
!define UNINSTALL_REG_KEY "CrossDesk"
|
||||
|
||||
; Installer icon path
|
||||
!define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico"
|
||||
|
||||
; Certificate path
|
||||
!define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt"
|
||||
|
||||
; Compression settings
|
||||
SetCompressor /FINAL lzma
|
||||
|
||||
; Request admin privileges (needed to write HKLM)
|
||||
RequestExecutionLevel admin
|
||||
|
||||
; ------ MUI Modern UI Definition ------
|
||||
!include "MUI.nsh"
|
||||
!define MUI_ABORTWARNING
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
; Add run-after-install option
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}"
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
|
||||
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
|
||||
; ------ End of MUI Definition ------
|
||||
|
||||
; Include LogicLib for process handling
|
||||
!include "LogicLib.nsh"
|
||||
|
||||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||
OutFile "crossdesk-win-x64-${PRODUCT_VERSION}.exe"
|
||||
InstallDir "$PROGRAMFILES\CrossDesk"
|
||||
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" "InstallDir"
|
||||
ShowInstDetails show
|
||||
|
||||
Section "MainSection"
|
||||
; Check if CrossDesk is running
|
||||
StrCpy $1 "crossdesk.exe"
|
||||
|
||||
nsProcess::_FindProcess "$1"
|
||||
Pop $R0
|
||||
${If} $R0 = 0 ;
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and continue the installation?" IDYES closeApp IDNO cancelInstall
|
||||
${Else}
|
||||
Goto installApp
|
||||
${EndIf}
|
||||
|
||||
closeApp:
|
||||
nsProcess::_KillProcess "$1"
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
Goto installApp
|
||||
|
||||
cancelInstall:
|
||||
SetDetailsPrint both
|
||||
MessageBox MB_ICONEXCLAMATION|MB_OK "Installation has been aborted."
|
||||
Abort
|
||||
|
||||
installApp:
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite ifnewer
|
||||
|
||||
; Main application executable path
|
||||
File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
|
||||
|
||||
; Copy icon file to installation directory
|
||||
File "${MUI_ICON}"
|
||||
|
||||
; Write uninstall information
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\crossdesk.ico"
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1
|
||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1
|
||||
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "InstallDir" "$INSTDIR"
|
||||
SectionEnd
|
||||
|
||||
; After installation
|
||||
Section -Post
|
||||
ExecWait '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\mt.exe" -manifest "$INSTDIR\crossdesk.manifest" -outputresource:"$INSTDIR\crossdesk.exe";1'
|
||||
SectionEnd
|
||||
|
||||
Section "Cert"
|
||||
SetOutPath "$APPDATA\CrossDesk\certs"
|
||||
File /r "${CERT_FILE}"
|
||||
SectionEnd
|
||||
|
||||
Section -AdditionalIcons
|
||||
; Desktop shortcut
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
|
||||
|
||||
; Start menu shortcut
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
; Check if CrossDesk is running
|
||||
StrCpy $1 "crossdesk.exe"
|
||||
|
||||
nsProcess::_FindProcess "$1"
|
||||
Pop $R0
|
||||
${If} $R0 = 0
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and uninstall?" IDYES closeApp IDNO cancelUninstall
|
||||
${Else}
|
||||
Goto uninstallApp
|
||||
${EndIf}
|
||||
|
||||
closeApp:
|
||||
nsProcess::_KillProcess "$1"
|
||||
Pop $R0
|
||||
Sleep 500
|
||||
Goto uninstallApp
|
||||
|
||||
cancelUninstall:
|
||||
SetDetailsPrint both
|
||||
MessageBox MB_ICONEXCLAMATION|MB_OK "Uninstallation has been aborted."
|
||||
Abort
|
||||
|
||||
uninstallApp:
|
||||
; Delete main executable and uninstaller
|
||||
Delete "$INSTDIR\crossdesk.exe"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
; Recursively delete installation directory
|
||||
RMDir /r "$INSTDIR"
|
||||
|
||||
; Delete desktop and start menu shortcuts
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
|
||||
|
||||
; Delete registry uninstall entry
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
|
||||
|
||||
; Delete remembered install dir
|
||||
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
|
||||
|
||||
; Recursively delete CrossDesk folder in user AppData
|
||||
RMDir /r "$APPDATA\CrossDesk"
|
||||
RMDir /r "$LOCALAPPDATA\CrossDesk"
|
||||
SectionEnd
|
||||
|
||||
; ------ Functions ------
|
||||
Function LaunchApp
|
||||
Exec "$INSTDIR\crossdesk.exe"
|
||||
FunctionEnd
|
||||
17
src/app/main.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifdef _WIN32
|
||||
#ifdef DESK_PORT_DEBUG
|
||||
#pragma comment(linker, "/subsystem:\"console\"")
|
||||
#else
|
||||
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) {
|
||||
Render render;
|
||||
render.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
44
src/common/display_info.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-05-15
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DISPLAY_INFO_H_
|
||||
#define _DISPLAY_INFO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
class DisplayInfo {
|
||||
public:
|
||||
DisplayInfo(std::string name, int left, int top, int right, int bottom)
|
||||
: name(name), left(left), top(top), right(right), bottom(bottom) {
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
}
|
||||
DisplayInfo(void* handle, std::string name, bool is_primary, int left,
|
||||
int top, int right, int bottom)
|
||||
: handle(handle),
|
||||
name(name),
|
||||
is_primary(is_primary),
|
||||
left(left),
|
||||
top(top),
|
||||
right(right),
|
||||
bottom(bottom) {
|
||||
width = right - left;
|
||||
height = bottom - top;
|
||||
}
|
||||
~DisplayInfo() {}
|
||||
|
||||
void* handle = nullptr;
|
||||
std::string name = "";
|
||||
bool is_primary = false;
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
int right = 0;
|
||||
int bottom = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
125
src/common/platform.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#elif __APPLE__
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#elif __linux__
|
||||
#include <fcntl.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
std::string GetMac() {
|
||||
char mac_addr[16];
|
||||
int len = 0;
|
||||
#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_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
|
||||
adapter->Address[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif __APPLE__
|
||||
std::string if_name = "en0";
|
||||
|
||||
struct ifaddrs *addrs;
|
||||
struct ifaddrs *cursor;
|
||||
const struct sockaddr_dl *dlAddr;
|
||||
|
||||
if (!getifaddrs(&addrs)) {
|
||||
cursor = addrs;
|
||||
while (cursor != 0) {
|
||||
const struct sockaddr_dl *socAddr =
|
||||
(const struct sockaddr_dl *)cursor->ifa_addr;
|
||||
if ((cursor->ifa_addr->sa_family == AF_LINK) &&
|
||||
(socAddr->sdl_type == IFT_ETHER) &&
|
||||
strcmp(if_name.c_str(), cursor->ifa_name) == 0) {
|
||||
dlAddr = (const struct sockaddr_dl *)cursor->ifa_addr;
|
||||
const unsigned char *base =
|
||||
(const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen];
|
||||
for (int i = 0; i < dlAddr->sdl_alen; i++) {
|
||||
len +=
|
||||
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
|
||||
}
|
||||
}
|
||||
cursor = cursor->ifa_next;
|
||||
}
|
||||
freeifaddrs(addrs);
|
||||
}
|
||||
#elif __linux__
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0) {
|
||||
return "";
|
||||
}
|
||||
struct ifreq ifr;
|
||||
struct ifconf ifc;
|
||||
char buf[1024];
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
|
||||
close(sock);
|
||||
return "";
|
||||
}
|
||||
struct ifreq *it = ifc.ifc_req;
|
||||
const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq));
|
||||
for (; it != end; ++it) {
|
||||
std::strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (ifr.ifr_flags & IFF_LOOPBACK) {
|
||||
continue;
|
||||
}
|
||||
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
|
||||
continue;
|
||||
}
|
||||
std::string mac_address;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
15
src/common/platform.h
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-18
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#define _PLATFORM_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
std::string GetMac();
|
||||
std::string GetHostName();
|
||||
|
||||
#endif
|
||||
3644
src/config_center/SimpleIni.h
Normal file
266
src/config_center/config_center.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "config_center.h"
|
||||
|
||||
ConfigCenter::ConfigCenter(const std::string& config_path,
|
||||
const std::string& cert_file_path)
|
||||
: config_path_(config_path),
|
||||
cert_file_path_(cert_file_path),
|
||||
cert_file_path_default_(cert_file_path) {
|
||||
ini_.SetUnicode(true);
|
||||
Load();
|
||||
}
|
||||
|
||||
ConfigCenter::~ConfigCenter() {}
|
||||
|
||||
int ConfigCenter::Load() {
|
||||
SI_Error rc = ini_.LoadFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
Save();
|
||||
return -1;
|
||||
}
|
||||
|
||||
language_ = static_cast<LANGUAGE>(
|
||||
ini_.GetLongValue(section_, "language", static_cast<long>(language_)));
|
||||
|
||||
video_quality_ = static_cast<VIDEO_QUALITY>(ini_.GetLongValue(
|
||||
section_, "video_quality", static_cast<long>(video_quality_)));
|
||||
|
||||
video_frame_rate_ = static_cast<VIDEO_FRAME_RATE>(ini_.GetLongValue(
|
||||
section_, "video_frame_rate", static_cast<long>(video_frame_rate_)));
|
||||
|
||||
video_encode_format_ = static_cast<VIDEO_ENCODE_FORMAT>(
|
||||
ini_.GetLongValue(section_, "video_encode_format",
|
||||
static_cast<long>(video_encode_format_)));
|
||||
|
||||
hardware_video_codec_ = ini_.GetBoolValue(section_, "hardware_video_codec",
|
||||
hardware_video_codec_);
|
||||
|
||||
enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_);
|
||||
enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_);
|
||||
signal_server_host_ = ini_.GetValue(section_, "signal_server_host",
|
||||
signal_server_host_.c_str());
|
||||
signal_server_port_ = static_cast<int>(
|
||||
ini_.GetLongValue(section_, "signal_server_port", signal_server_port_));
|
||||
coturn_server_port_ = static_cast<int>(
|
||||
ini_.GetLongValue(section_, "coturn_server_port", coturn_server_port_));
|
||||
cert_file_path_ =
|
||||
ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
enable_self_hosted_ =
|
||||
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||
|
||||
enable_minimize_to_tray_ = ini_.GetBoolValue(
|
||||
section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::Save() {
|
||||
ini_.SetLongValue(section_, "language", static_cast<long>(language_));
|
||||
ini_.SetLongValue(section_, "video_quality",
|
||||
static_cast<long>(video_quality_));
|
||||
ini_.SetLongValue(section_, "video_frame_rate",
|
||||
static_cast<long>(video_frame_rate_));
|
||||
ini_.SetLongValue(section_, "video_encode_format",
|
||||
static_cast<long>(video_encode_format_));
|
||||
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
|
||||
ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
|
||||
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
|
||||
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
|
||||
ini_.SetLongValue(section_, "signal_server_port",
|
||||
static_cast<long>(signal_server_port_));
|
||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||
ini_.SetBoolValue(section_, "enable_minimize_to_tray",
|
||||
enable_minimize_to_tray_);
|
||||
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// setters
|
||||
|
||||
int ConfigCenter::SetLanguage(LANGUAGE language) {
|
||||
language_ = language;
|
||||
ini_.SetLongValue(section_, "language", static_cast<long>(language_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) {
|
||||
video_quality_ = video_quality;
|
||||
ini_.SetLongValue(section_, "video_quality",
|
||||
static_cast<long>(video_quality_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate) {
|
||||
video_frame_rate_ = video_frame_rate;
|
||||
ini_.SetLongValue(section_, "video_frame_rate",
|
||||
static_cast<long>(video_frame_rate_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetVideoEncodeFormat(
|
||||
VIDEO_ENCODE_FORMAT video_encode_format) {
|
||||
video_encode_format_ = video_encode_format;
|
||||
ini_.SetLongValue(section_, "video_encode_format",
|
||||
static_cast<long>(video_encode_format_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) {
|
||||
hardware_video_codec_ = hardware_video_codec;
|
||||
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetTurn(bool enable_turn) {
|
||||
enable_turn_ = enable_turn;
|
||||
ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetSrtp(bool enable_srtp) {
|
||||
enable_srtp_ = enable_srtp;
|
||||
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetServerHost(const std::string& signal_server_host) {
|
||||
signal_server_host_ = signal_server_host;
|
||||
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetServerPort(int signal_server_port) {
|
||||
signal_server_port_ = signal_server_port;
|
||||
ini_.SetLongValue(section_, "signal_server_port",
|
||||
static_cast<long>(signal_server_port_));
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetCoturnServerPort(int coturn_server_port) {
|
||||
coturn_server_port_ = coturn_server_port;
|
||||
SI_Error rc = ini_.SetLongValue(section_, "coturn_server_port",
|
||||
static_cast<long>(coturn_server_port_));
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) {
|
||||
cert_file_path_ = cert_file_path;
|
||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
||||
enable_self_hosted_ = enable_self_hosted;
|
||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) {
|
||||
enable_minimize_to_tray_ = enable_minimize_to_tray;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
|
||||
|
||||
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() const {
|
||||
return video_quality_;
|
||||
}
|
||||
|
||||
ConfigCenter::VIDEO_FRAME_RATE ConfigCenter::GetVideoFrameRate() const {
|
||||
return video_frame_rate_;
|
||||
}
|
||||
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() const {
|
||||
return video_encode_format_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsHardwareVideoCodec() const {
|
||||
return hardware_video_codec_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsEnableTurn() const { return enable_turn_; }
|
||||
|
||||
bool ConfigCenter::IsEnableSrtp() const { return enable_srtp_; }
|
||||
|
||||
std::string ConfigCenter::GetSignalServerHost() const {
|
||||
return signal_server_host_;
|
||||
}
|
||||
|
||||
int ConfigCenter::GetSignalServerPort() const { return signal_server_port_; }
|
||||
|
||||
int ConfigCenter::GetCoturnServerPort() const { return coturn_server_port_; }
|
||||
|
||||
std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; }
|
||||
|
||||
std::string ConfigCenter::GetDefaultServerHost() const {
|
||||
return signal_server_host_default_;
|
||||
}
|
||||
|
||||
int ConfigCenter::GetDefaultSignalServerPort() const {
|
||||
return server_port_default_;
|
||||
}
|
||||
|
||||
int ConfigCenter::GetDefaultCoturnServerPort() const {
|
||||
return coturn_server_port_default_;
|
||||
}
|
||||
|
||||
std::string ConfigCenter::GetDefaultCertFilePath() const {
|
||||
return cert_file_path_default_;
|
||||
}
|
||||
|
||||
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
|
||||
|
||||
bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; }
|
||||
89
src/config_center/config_center.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CONFIG_CENTER_H_
|
||||
#define _CONFIG_CENTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "SimpleIni.h"
|
||||
|
||||
class ConfigCenter {
|
||||
public:
|
||||
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 };
|
||||
enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 };
|
||||
enum class VIDEO_FRAME_RATE { FPS_30 = 0, FPS_60 = 1 };
|
||||
enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 };
|
||||
|
||||
public:
|
||||
explicit ConfigCenter(
|
||||
const std::string& config_path = "config.ini",
|
||||
const std::string& cert_file_path = "crossdesk.cn_root.crt");
|
||||
~ConfigCenter();
|
||||
|
||||
// write config
|
||||
int SetLanguage(LANGUAGE language);
|
||||
int SetVideoQuality(VIDEO_QUALITY video_quality);
|
||||
int SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate);
|
||||
int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format);
|
||||
int SetHardwareVideoCodec(bool hardware_video_codec);
|
||||
int SetTurn(bool enable_turn);
|
||||
int SetSrtp(bool enable_srtp);
|
||||
int SetServerHost(const std::string& signal_server_host);
|
||||
int SetServerPort(int signal_server_port);
|
||||
int SetCoturnServerPort(int coturn_server_port);
|
||||
int SetCertFilePath(const std::string& cert_file_path);
|
||||
int SetSelfHosted(bool enable_self_hosted);
|
||||
int SetMinimizeToTray(bool enable_minimize_to_tray);
|
||||
|
||||
// read config
|
||||
|
||||
LANGUAGE GetLanguage() const;
|
||||
VIDEO_QUALITY GetVideoQuality() const;
|
||||
VIDEO_FRAME_RATE GetVideoFrameRate() const;
|
||||
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat() const;
|
||||
bool IsHardwareVideoCodec() const;
|
||||
bool IsEnableTurn() const;
|
||||
bool IsEnableSrtp() const;
|
||||
std::string GetSignalServerHost() const;
|
||||
int GetSignalServerPort() const;
|
||||
int GetCoturnServerPort() const;
|
||||
std::string GetCertFilePath() const;
|
||||
std::string GetDefaultServerHost() const;
|
||||
int GetDefaultSignalServerPort() const;
|
||||
int GetDefaultCoturnServerPort() const;
|
||||
std::string GetDefaultCertFilePath() const;
|
||||
bool IsSelfHosted() const;
|
||||
bool IsMinimizeToTray() const;
|
||||
|
||||
int Load();
|
||||
int Save();
|
||||
|
||||
private:
|
||||
std::string config_path_;
|
||||
std::string cert_file_path_;
|
||||
CSimpleIniA ini_;
|
||||
const char* section_ = "Settings";
|
||||
|
||||
LANGUAGE language_ = LANGUAGE::CHINESE;
|
||||
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
|
||||
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30;
|
||||
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
|
||||
bool hardware_video_codec_ = false;
|
||||
bool enable_turn_ = false;
|
||||
bool enable_srtp_ = false;
|
||||
std::string signal_server_host_ = "api.crossdesk.cn";
|
||||
std::string signal_server_host_default_ = "api.crossdesk.cn";
|
||||
int signal_server_port_ = 9099;
|
||||
int server_port_default_ = 9099;
|
||||
int coturn_server_port_ = 3478;
|
||||
int coturn_server_port_default_ = 3478;
|
||||
std::string cert_file_path_default_ = "";
|
||||
bool enable_self_hosted_ = false;
|
||||
bool enable_minimize_to_tray_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
83
src/device_controller/device_controller.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DEVICE_CONTROLLER_H_
|
||||
#define _DEVICE_CONTROLLER_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#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 {
|
||||
float x;
|
||||
float y;
|
||||
int s;
|
||||
MouseFlag flag;
|
||||
} Mouse;
|
||||
|
||||
typedef struct {
|
||||
size_t key_value;
|
||||
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);
|
||||
// virtual int Destroy();
|
||||
// virtual int SendMouseCommand(RemoteAction remote_action);
|
||||
|
||||
// virtual int Hook();
|
||||
// virtual int Unhook();
|
||||
};
|
||||
|
||||
#endif
|
||||
34
src/device_controller/device_controller_factory.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DEVICE_CONTROLLER_FACTORY_H_
|
||||
#define _DEVICE_CONTROLLER_FACTORY_H_
|
||||
|
||||
#include "device_controller.h"
|
||||
#include "keyboard_capturer.h"
|
||||
#include "mouse_controller.h"
|
||||
|
||||
class DeviceControllerFactory {
|
||||
public:
|
||||
enum Device { Mouse = 0, Keyboard };
|
||||
|
||||
public:
|
||||
virtual ~DeviceControllerFactory() {}
|
||||
|
||||
public:
|
||||
DeviceController* Create(Device device) {
|
||||
switch (device) {
|
||||
case Mouse:
|
||||
return new MouseController();
|
||||
case Keyboard:
|
||||
return new KeyboardCapturer();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
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
@@ -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
|
||||
167
src/device_controller/keyboard/mac/keyboard_capturer.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
inline bool IsFunctionKey(int key_code) {
|
||||
switch (key_code) {
|
||||
case 0x7A:
|
||||
case 0x78:
|
||||
case 0x63:
|
||||
case 0x76:
|
||||
case 0x60:
|
||||
case 0x61:
|
||||
case 0x62:
|
||||
case 0x64:
|
||||
case 0x65:
|
||||
case 0x6D:
|
||||
case 0x67:
|
||||
case 0x6F:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
|
||||
if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) {
|
||||
CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code];
|
||||
CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down);
|
||||
CGEventRef clearFlags =
|
||||
CGEventCreateKeyboardEvent(NULL, (CGKeyCode)0, true);
|
||||
CGEventSetFlags(clearFlags, 0);
|
||||
CGEventPost(kCGHIDEventTap, event);
|
||||
CFRelease(event);
|
||||
|
||||
// F1-F12 keys often require the FN key to be pressed on Mac keyboards, so
|
||||
// we simulate the FN key release when an F1-F12 key is released.
|
||||
if (IsFunctionKey(cg_key_code) && !is_down) {
|
||||
CGEventRef fn_release_event =
|
||||
CGEventCreateKeyboardEvent(NULL, fn_key_code_, false);
|
||||
CGEventPost(kCGHIDEventTap, fn_release_event);
|
||||
CFRelease(fn_release_event);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
37
src/device_controller/keyboard/mac/keyboard_capturer.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* @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;
|
||||
int fn_key_code_ = 0x3F;
|
||||
};
|
||||
|
||||
#endif
|
||||
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
@@ -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
|
||||
740
src/device_controller/keyboard_converter.h
Normal file
@@ -0,0 +1,740 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-11-25
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _KEYBOARD_CONVERTER_H_
|
||||
#define _KEYBOARD_CONVERTER_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
// Windows vkCode to macOS CGKeyCode (104 keys)
|
||||
std::map<int, int> vkCodeToCGKeyCode = {
|
||||
// A-Z
|
||||
{0x41, 0x00}, // A
|
||||
{0x42, 0x0B}, // B
|
||||
{0x43, 0x08}, // C
|
||||
{0x44, 0x02}, // D
|
||||
{0x45, 0x0E}, // E
|
||||
{0x46, 0x03}, // F
|
||||
{0x47, 0x05}, // G
|
||||
{0x48, 0x04}, // H
|
||||
{0x49, 0x22}, // I
|
||||
{0x4A, 0x26}, // J
|
||||
{0x4B, 0x28}, // K
|
||||
{0x4C, 0x25}, // L
|
||||
{0x4D, 0x2E}, // M
|
||||
{0x4E, 0x2D}, // N
|
||||
{0x4F, 0x1F}, // O
|
||||
{0x50, 0x23}, // P
|
||||
{0x51, 0x0C}, // Q
|
||||
{0x52, 0x0F}, // R
|
||||
{0x53, 0x01}, // S
|
||||
{0x54, 0x11}, // T
|
||||
{0x55, 0x20}, // U
|
||||
{0x56, 0x09}, // V
|
||||
{0x57, 0x0D}, // W
|
||||
{0x58, 0x07}, // X
|
||||
{0x59, 0x10}, // Y
|
||||
{0x5A, 0x06}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x30, 0x1D}, // 0
|
||||
{0x31, 0x12}, // 1
|
||||
{0x32, 0x13}, // 2
|
||||
{0x33, 0x14}, // 3
|
||||
{0x34, 0x15}, // 4
|
||||
{0x35, 0x17}, // 5
|
||||
{0x36, 0x16}, // 6
|
||||
{0x37, 0x1A}, // 7
|
||||
{0x38, 0x1C}, // 8
|
||||
{0x39, 0x19}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x70, 0x7A}, // F1
|
||||
{0x71, 0x78}, // F2
|
||||
{0x72, 0x63}, // F3
|
||||
{0x73, 0x76}, // F4
|
||||
{0x74, 0x60}, // F5
|
||||
{0x75, 0x61}, // F6
|
||||
{0x76, 0x62}, // F7
|
||||
{0x77, 0x64}, // F8
|
||||
{0x78, 0x65}, // F9
|
||||
{0x79, 0x6D}, // F10
|
||||
{0x7A, 0x67}, // F11
|
||||
{0x7B, 0x6F}, // F12
|
||||
|
||||
// control keys
|
||||
{0x1B, 0x35}, // Escape
|
||||
{0x0D, 0x24}, // Enter
|
||||
{0x20, 0x31}, // Space
|
||||
{0x08, 0x33}, // Backspace
|
||||
{0x09, 0x30}, // Tab
|
||||
{0x2C, 0x74}, // Print Screen
|
||||
{0x2D, 0x72}, // Insert
|
||||
{0x2E, 0x75}, // Delete
|
||||
{0x24, 0x73}, // Home
|
||||
{0x23, 0x77}, // End
|
||||
{0x21, 0x79}, // Page Up
|
||||
{0x22, 0x7A}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x25, 0x7B}, // Left Arrow
|
||||
{0x27, 0x7C}, // Right Arrow
|
||||
{0x26, 0x7E}, // Up Arrow
|
||||
{0x28, 0x7D}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x60, 0x52}, // Numpad 0
|
||||
{0x61, 0x53}, // Numpad 1
|
||||
{0x62, 0x54}, // Numpad 2
|
||||
{0x63, 0x55}, // Numpad 3
|
||||
{0x64, 0x56}, // Numpad 4
|
||||
{0x65, 0x57}, // Numpad 5
|
||||
{0x66, 0x58}, // Numpad 6
|
||||
{0x67, 0x59}, // Numpad 7
|
||||
{0x68, 0x5B}, // Numpad 8
|
||||
{0x69, 0x5C}, // Numpad 9
|
||||
{0x6E, 0x41}, // Numpad .
|
||||
{0x6F, 0x4B}, // Numpad /
|
||||
{0x6A, 0x43}, // Numpad *
|
||||
{0x6D, 0x4E}, // Numpad -
|
||||
{0x6B, 0x45}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0xBA, 0x29}, // ; (Semicolon)
|
||||
{0xDE, 0x27}, // ' (Quote)
|
||||
{0xC0, 0x32}, // ` (Backtick)
|
||||
{0xBC, 0x2B}, // , (Comma)
|
||||
{0xBE, 0x2F}, // . (Period)
|
||||
{0xBF, 0x2C}, // / (Slash)
|
||||
{0xDC, 0x2A}, // \ (Backslash)
|
||||
{0xDB, 0x21}, // [ (Left Bracket)
|
||||
{0xDD, 0x1E}, // ] (Right Bracket)
|
||||
{0xBD, 0x1B}, // - (Minus)
|
||||
{0xBB, 0x18}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x14, 0x39}, // Caps Lock
|
||||
{0xA0, 0x38}, // Shift (Left)
|
||||
{0xA1, 0x3C}, // Shift (Right)
|
||||
{0xA2, 0x3B}, // Ctrl (Left)
|
||||
{0xA3, 0x3E}, // Ctrl (Right)
|
||||
{0xA4, 0x3A}, // Alt (Left)
|
||||
{0xA5, 0x3D}, // Alt (Right)
|
||||
{0x5B, 0x37}, // Left Command (Windows key)
|
||||
{0x5C, 0x36}, // Right Command
|
||||
};
|
||||
|
||||
// macOS CGKeyCode to Windows vkCode
|
||||
std::map<int, int> CGKeyCodeToVkCode = {
|
||||
// A-Z
|
||||
{0x00, 0x41}, // A
|
||||
{0x0B, 0x42}, // B
|
||||
{0x08, 0x43}, // C
|
||||
{0x02, 0x44}, // D
|
||||
{0x0E, 0x45}, // E
|
||||
{0x03, 0x46}, // F
|
||||
{0x05, 0x47}, // G
|
||||
{0x04, 0x48}, // H
|
||||
{0x22, 0x49}, // I
|
||||
{0x26, 0x4A}, // J
|
||||
{0x28, 0x4B}, // K
|
||||
{0x25, 0x4C}, // L
|
||||
{0x2E, 0x4D}, // M
|
||||
{0x2D, 0x4E}, // N
|
||||
{0x1F, 0x4F}, // O
|
||||
{0x23, 0x50}, // P
|
||||
{0x0C, 0x51}, // Q
|
||||
{0x0F, 0x52}, // R
|
||||
{0x01, 0x53}, // S
|
||||
{0x11, 0x54}, // T
|
||||
{0x20, 0x55}, // U
|
||||
{0x09, 0x56}, // V
|
||||
{0x0D, 0x57}, // W
|
||||
{0x07, 0x58}, // X
|
||||
{0x10, 0x59}, // Y
|
||||
{0x06, 0x5A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x1D, 0x30}, // 0
|
||||
{0x12, 0x31}, // 1
|
||||
{0x13, 0x32}, // 2
|
||||
{0x14, 0x33}, // 3
|
||||
{0x15, 0x34}, // 4
|
||||
{0x17, 0x35}, // 5
|
||||
{0x16, 0x36}, // 6
|
||||
{0x1A, 0x37}, // 7
|
||||
{0x1C, 0x38}, // 8
|
||||
{0x19, 0x39}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x7A, 0x70}, // F1
|
||||
{0x78, 0x71}, // F2
|
||||
{0x63, 0x72}, // F3
|
||||
{0x76, 0x73}, // F4
|
||||
{0x60, 0x74}, // F5
|
||||
{0x61, 0x75}, // F6
|
||||
{0x62, 0x76}, // F7
|
||||
{0x64, 0x77}, // F8
|
||||
{0x65, 0x78}, // F9
|
||||
{0x6D, 0x79}, // F10
|
||||
{0x67, 0x7A}, // F11
|
||||
{0x6F, 0x7B}, // F12
|
||||
|
||||
// control keys
|
||||
{0x35, 0x1B}, // Escape
|
||||
{0x24, 0x0D}, // Enter
|
||||
{0x31, 0x20}, // Space
|
||||
{0x33, 0x08}, // Backspace
|
||||
{0x30, 0x09}, // Tab
|
||||
{0x74, 0x2C}, // Print Screen
|
||||
{0x72, 0x2D}, // Insert
|
||||
{0x75, 0x2E}, // Delete
|
||||
{0x73, 0x24}, // Home
|
||||
{0x77, 0x23}, // End
|
||||
{0x79, 0x21}, // Page Up
|
||||
{0x7A, 0x22}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x7B, 0x25}, // Left Arrow
|
||||
{0x7C, 0x27}, // Right Arrow
|
||||
{0x7E, 0x26}, // Up Arrow
|
||||
{0x7D, 0x28}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x52, 0x60}, // Numpad 0
|
||||
{0x53, 0x61}, // Numpad 1
|
||||
{0x54, 0x62}, // Numpad 2
|
||||
{0x55, 0x63}, // Numpad 3
|
||||
{0x56, 0x64}, // Numpad 4
|
||||
{0x57, 0x65}, // Numpad 5
|
||||
{0x58, 0x66}, // Numpad 6
|
||||
{0x59, 0x67}, // Numpad 7
|
||||
{0x5B, 0x68}, // Numpad 8
|
||||
{0x5C, 0x69}, // Numpad 9
|
||||
{0x41, 0x6E}, // Numpad .
|
||||
{0x4B, 0x6F}, // Numpad /
|
||||
{0x43, 0x6A}, // Numpad *
|
||||
{0x4E, 0x6D}, // Numpad -
|
||||
{0x45, 0x6B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x29, 0xBA}, // ; (Semicolon)
|
||||
{0x27, 0xDE}, // ' (Quote)
|
||||
{0x32, 0xC0}, // ` (Backtick)
|
||||
{0x2B, 0xBC}, // , (Comma)
|
||||
{0x2F, 0xBE}, // . (Period)
|
||||
{0x2C, 0xBF}, // / (Slash)
|
||||
{0x2A, 0xDC}, // \ (Backslash)
|
||||
{0x21, 0xDB}, // [ (Left Bracket)
|
||||
{0x1E, 0xDD}, // ] (Right Bracket)
|
||||
{0x1B, 0xBD}, // - (Minus)
|
||||
{0x18, 0xBB}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x39, 0x14}, // Caps Lock
|
||||
{0x38, 0xA0}, // Shift (Left)
|
||||
{0x3C, 0xA1}, // Shift (Right)
|
||||
{0x3B, 0xA2}, // Control (Left)
|
||||
{0x3E, 0xA3}, // Control (Right)
|
||||
{0x3A, 0xA4}, // Alt (Left)
|
||||
{0x3D, 0xA5}, // Alt (Right)
|
||||
{0x37, 0x5B}, // Left Command (Windows key)
|
||||
{0x36, 0x5C}, // Right Command
|
||||
};
|
||||
|
||||
// Windows vkCode to X11 KeySym
|
||||
std::map<int, int> vkCodeToX11KeySym = {
|
||||
// A-Z
|
||||
{0x41, 0x0041}, // A
|
||||
{0x42, 0x0042}, // B
|
||||
{0x43, 0x0043}, // C
|
||||
{0x44, 0x0044}, // D
|
||||
{0x45, 0x0045}, // E
|
||||
{0x46, 0x0046}, // F
|
||||
{0x47, 0x0047}, // G
|
||||
{0x48, 0x0048}, // H
|
||||
{0x49, 0x0049}, // I
|
||||
{0x4A, 0x004A}, // J
|
||||
{0x4B, 0x004B}, // K
|
||||
{0x4C, 0x004C}, // L
|
||||
{0x4D, 0x004D}, // M
|
||||
{0x4E, 0x004E}, // N
|
||||
{0x4F, 0x004F}, // O
|
||||
{0x50, 0x0050}, // P
|
||||
{0x51, 0x0051}, // Q
|
||||
{0x52, 0x0052}, // R
|
||||
{0x53, 0x0053}, // S
|
||||
{0x54, 0x0054}, // T
|
||||
{0x55, 0x0055}, // U
|
||||
{0x56, 0x0056}, // V
|
||||
{0x57, 0x0057}, // W
|
||||
{0x58, 0x0058}, // X
|
||||
{0x59, 0x0059}, // Y
|
||||
{0x5A, 0x005A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x30, 0x0030}, // 0
|
||||
{0x31, 0x0031}, // 1
|
||||
{0x32, 0x0032}, // 2
|
||||
{0x33, 0x0033}, // 3
|
||||
{0x34, 0x0034}, // 4
|
||||
{0x35, 0x0035}, // 5
|
||||
{0x36, 0x0036}, // 6
|
||||
{0x37, 0x0037}, // 7
|
||||
{0x38, 0x0038}, // 8
|
||||
{0x39, 0x0039}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x70, 0xFFBE}, // F1
|
||||
{0x71, 0xFFBF}, // F2
|
||||
{0x72, 0xFFC0}, // F3
|
||||
{0x73, 0xFFC1}, // F4
|
||||
{0x74, 0xFFC2}, // F5
|
||||
{0x75, 0xFFC3}, // F6
|
||||
{0x76, 0xFFC4}, // F7
|
||||
{0x77, 0xFFC5}, // F8
|
||||
{0x78, 0xFFC6}, // F9
|
||||
{0x79, 0xFFC7}, // F10
|
||||
{0x7A, 0xFFC8}, // F11
|
||||
{0x7B, 0xFFC9}, // F12
|
||||
|
||||
// control keys
|
||||
{0x1B, 0xFF1B}, // Escape
|
||||
{0x0D, 0xFF0D}, // Enter
|
||||
{0x20, 0x0020}, // Space
|
||||
{0x08, 0xFF08}, // Backspace
|
||||
{0x09, 0xFF09}, // Tab
|
||||
{0x2C, 0xFF15}, // Print Screen
|
||||
{0x91, 0xFF14}, // Scroll Lock
|
||||
{0x13, 0xFF13}, // Pause/Break
|
||||
{0x2D, 0xFF63}, // Insert
|
||||
{0x2E, 0xFFFF}, // Delete
|
||||
{0x24, 0xFF50}, // Home
|
||||
{0x23, 0xFF57}, // End
|
||||
{0x21, 0xFF55}, // Page Up
|
||||
{0x22, 0xFF56}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x25, 0xFF51}, // Left Arrow
|
||||
{0x27, 0xFF53}, // Right Arrow
|
||||
{0x26, 0xFF52}, // Up Arrow
|
||||
{0x28, 0xFF54}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x60, 0x0030}, // Numpad 0
|
||||
{0x61, 0x0031}, // Numpad 1
|
||||
{0x62, 0x0032}, // Numpad 2
|
||||
{0x63, 0x0033}, // Numpad 3
|
||||
{0x64, 0x0034}, // Numpad 4
|
||||
{0x65, 0x0035}, // Numpad 5
|
||||
{0x66, 0x0036}, // Numpad 6
|
||||
{0x67, 0x0037}, // Numpad 7
|
||||
{0x68, 0x0038}, // Numpad 8
|
||||
{0x69, 0x0039}, // Numpad 9
|
||||
{0x6E, 0x003A}, // Numpad .
|
||||
{0x6F, 0x002F}, // Numpad /
|
||||
{0x6A, 0x002A}, // Numpad *
|
||||
{0x6D, 0x002D}, // Numpad -
|
||||
{0x6B, 0x002B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0xBA, 0x003B}, // ; (Semicolon)
|
||||
{0xDE, 0x0027}, // ' (Quote)
|
||||
{0xC0, 0x007E}, // ` (Grave)
|
||||
{0xBC, 0x002C}, // , (Comma)
|
||||
{0xBE, 0x002E}, // . (Period)
|
||||
{0xBF, 0x002F}, // / (Slash)
|
||||
{0xDC, 0x005C}, // \ (Backslash)
|
||||
{0xDB, 0x005B}, // [ (Left Bracket)
|
||||
{0xDD, 0x005D}, // ] (Right Bracket)
|
||||
{0xBD, 0x002D}, // - (Minus)
|
||||
{0xBB, 0x003D}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x14, 0xFFE5}, // Caps Lock
|
||||
{0xA0, 0xFFE1}, // Shift (Left)
|
||||
{0xA1, 0xFFE2}, // Shift (Right)
|
||||
{0xA2, 0xFFE3}, // Ctrl (Left)
|
||||
{0xA3, 0xFFE4}, // Ctrl (Right)
|
||||
{0xA4, 0xFFE9}, // Alt (Left)
|
||||
{0xA5, 0xFFEA}, // Alt (Right)
|
||||
{0x5B, 0xFFEB}, // Left Command (Windows key)
|
||||
{0x5C, 0xFFEC}, // Right Command
|
||||
};
|
||||
|
||||
// X11 KeySym to Windows vkCode
|
||||
// std::map<int, int> x11KeySymToVkCode = []() {
|
||||
// std::map<int, int> result;
|
||||
// for (const auto& pair : vkCodeToX11KeySym) {
|
||||
// result[pair.second] = pair.first;
|
||||
// }
|
||||
// return result;
|
||||
// }();
|
||||
|
||||
std::map<int, int> x11KeySymToVkCode = {
|
||||
// A-Z
|
||||
{0x0041, 0x41}, // A
|
||||
{0x0042, 0x42}, // B
|
||||
{0x0043, 0x43}, // C
|
||||
{0x0044, 0x44}, // D
|
||||
{0x0045, 0x45}, // E
|
||||
{0x0046, 0x46}, // F
|
||||
{0x0047, 0x47}, // G
|
||||
{0x0048, 0x48}, // H
|
||||
{0x0049, 0x49}, // I
|
||||
{0x004A, 0x4A}, // J
|
||||
{0x004B, 0x4B}, // K
|
||||
{0x004C, 0x4C}, // L
|
||||
{0x004D, 0x4D}, // M
|
||||
{0x004E, 0x4E}, // N
|
||||
{0x004F, 0x4F}, // O
|
||||
{0x0050, 0x50}, // P
|
||||
{0x0051, 0x51}, // Q
|
||||
{0x0052, 0x52}, // R
|
||||
{0x0053, 0x53}, // S
|
||||
{0x0054, 0x54}, // T
|
||||
{0x0055, 0x55}, // U
|
||||
{0x0056, 0x56}, // V
|
||||
{0x0057, 0x57}, // W
|
||||
{0x0058, 0x58}, // X
|
||||
{0x0059, 0x59}, // Y
|
||||
{0x005A, 0x5A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x0030, 0x30}, // 0
|
||||
{0x0031, 0x31}, // 1
|
||||
{0x0032, 0x32}, // 2
|
||||
{0x0033, 0x33}, // 3
|
||||
{0x0034, 0x34}, // 4
|
||||
{0x0035, 0x35}, // 5
|
||||
{0x0036, 0x36}, // 6
|
||||
{0x0037, 0x37}, // 7
|
||||
{0x0038, 0x38}, // 8
|
||||
{0x0039, 0x39}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0xFFBE, 0x70}, // F1
|
||||
{0xFFBF, 0x71}, // F2
|
||||
{0xFFC0, 0x72}, // F3
|
||||
{0xFFC1, 0x73}, // F4
|
||||
{0xFFC2, 0x74}, // F5
|
||||
{0xFFC3, 0x75}, // F6
|
||||
{0xFFC4, 0x76}, // F7
|
||||
{0xFFC5, 0x77}, // F8
|
||||
{0xFFC6, 0x78}, // F9
|
||||
{0xFFC7, 0x79}, // F10
|
||||
{0xFFC8, 0x7A}, // F11
|
||||
{0xFFC9, 0x7B}, // F12
|
||||
|
||||
// control keys
|
||||
{0xFF1B, 0x1B}, // Escape
|
||||
{0xFF0D, 0x0D}, // Enter
|
||||
{0x0020, 0x20}, // Space
|
||||
{0xFF08, 0x08}, // Backspace
|
||||
{0xFF09, 0x09}, // Tab
|
||||
{0xFF15, 0x2C}, // Print Screen
|
||||
{0xFF14, 0x91}, // Scroll Lock
|
||||
{0xFF13, 0x13}, // Pause/Break
|
||||
{0xFF63, 0x2D}, // Insert
|
||||
{0xFFFF, 0x2E}, // Delete
|
||||
{0xFF50, 0x24}, // Home
|
||||
{0xFF57, 0x23}, // End
|
||||
{0xFF55, 0x21}, // Page Up
|
||||
{0xFF56, 0x22}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0xFF51, 0x25}, // Left Arrow
|
||||
{0xFF53, 0x27}, // Right Arrow
|
||||
{0xFF52, 0x26}, // Up Arrow
|
||||
{0xFF54, 0x28}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x0030, 0x60}, // Numpad 0
|
||||
{0x0031, 0x61}, // Numpad 1
|
||||
{0x0032, 0x62}, // Numpad 2
|
||||
{0x0033, 0x63}, // Numpad 3
|
||||
{0x0034, 0x64}, // Numpad 4
|
||||
{0x0035, 0x65}, // Numpad 5
|
||||
{0x0036, 0x66}, // Numpad 6
|
||||
{0x0037, 0x67}, // Numpad 7
|
||||
{0x0038, 0x68}, // Numpad 8
|
||||
{0x0039, 0x69}, // Numpad 9
|
||||
{0x003A, 0x6E}, // Numpad .
|
||||
{0x002F, 0x6F}, // Numpad /
|
||||
{0x002A, 0x6A}, // Numpad *
|
||||
{0x002D, 0x6D}, // Numpad -
|
||||
{0x002B, 0x6B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x003B, 0xBA}, // ; (Semicolon)
|
||||
{0x0027, 0xDE}, // ' (Quote)
|
||||
{0x007E, 0xC0}, // ` (Grave)
|
||||
{0x002C, 0xBC}, // , (Comma)
|
||||
{0x002E, 0xBE}, // . (Period)
|
||||
{0x002F, 0xBF}, // / (Slash)
|
||||
{0x005C, 0xDC}, // \ (Backslash)
|
||||
{0x005B, 0xDB}, // [ (Left Bracket)
|
||||
{0x005D, 0xDD}, // ] (Right Bracket)
|
||||
{0x002D, 0xBD}, // - (Minus)
|
||||
{0x003D, 0xBB}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0xFFE5, 0x14}, // Caps Lock
|
||||
{0xFFE1, 0xA0}, // Shift (Left)
|
||||
{0xFFE2, 0xA1}, // Shift (Right)
|
||||
{0xFFE3, 0xA2}, // Ctrl (Left)
|
||||
{0xFFE4, 0xA3}, // Ctrl (Right)
|
||||
{0xFFE9, 0xA4}, // Alt (Left)
|
||||
{0xFFEA, 0xA5}, // Alt (Right)
|
||||
{0xFFEB, 0x5B}, // Left Command (Windows key)
|
||||
{0xFFEC, 0x5C}, // Right Command
|
||||
};
|
||||
|
||||
// macOS CGKeyCode to X11 KeySym
|
||||
std::map<int, int> cgKeyCodeToX11KeySym = {
|
||||
// A-Z
|
||||
{0x00, 0x0041}, // A
|
||||
{0x0B, 0x0042}, // B
|
||||
{0x08, 0x0043}, // C
|
||||
{0x02, 0x0044}, // D
|
||||
{0x0E, 0x0045}, // E
|
||||
{0x03, 0x0046}, // F
|
||||
{0x05, 0x0047}, // G
|
||||
{0x04, 0x0048}, // H
|
||||
{0x22, 0x0049}, // I
|
||||
{0x26, 0x004A}, // J
|
||||
{0x28, 0x004B}, // K
|
||||
{0x25, 0x004C}, // L
|
||||
{0x2E, 0x004D}, // M
|
||||
{0x2D, 0x004E}, // N
|
||||
{0x1F, 0x004F}, // O
|
||||
{0x23, 0x0050}, // P
|
||||
{0x0C, 0x0051}, // Q
|
||||
{0x0F, 0x0052}, // R
|
||||
{0x01, 0x0053}, // S
|
||||
{0x11, 0x0054}, // T
|
||||
{0x20, 0x0055}, // U
|
||||
{0x09, 0x0056}, // V
|
||||
{0x0D, 0x0057}, // W
|
||||
{0x07, 0x0058}, // X
|
||||
{0x10, 0x0059}, // Y
|
||||
{0x06, 0x005A}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x1D, 0x0030}, // 0
|
||||
{0x12, 0x0031}, // 1
|
||||
{0x13, 0x0032}, // 2
|
||||
{0x14, 0x0033}, // 3
|
||||
{0x15, 0x0034}, // 4
|
||||
{0x17, 0x0035}, // 5
|
||||
{0x16, 0x0036}, // 6
|
||||
{0x1A, 0x0037}, // 7
|
||||
{0x1C, 0x0038}, // 8
|
||||
{0x19, 0x0039}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0x7A, 0xFFBE}, // F1
|
||||
{0x78, 0xFFBF}, // F2
|
||||
{0x63, 0xFFC0}, // F3
|
||||
{0x76, 0xFFC1}, // F4
|
||||
{0x60, 0xFFC2}, // F5
|
||||
{0x61, 0xFFC3}, // F6
|
||||
{0x62, 0xFFC4}, // F7
|
||||
{0x64, 0xFFC5}, // F8
|
||||
{0x65, 0xFFC6}, // F9
|
||||
{0x6D, 0xFFC7}, // F10
|
||||
{0x67, 0xFFC8}, // F11
|
||||
{0x6F, 0xFFC9}, // F12
|
||||
|
||||
// control keys
|
||||
{0x35, 0xFF1B}, // Escape
|
||||
{0x24, 0xFF0D}, // Enter
|
||||
{0x31, 0x0020}, // Space
|
||||
{0x33, 0xFF08}, // Backspace
|
||||
{0x30, 0xFF09}, // Tab
|
||||
{0x74, 0xFF15}, // Print Screen
|
||||
{0x72, 0xFF63}, // Insert
|
||||
{0x75, 0xFFFF}, // Delete
|
||||
{0x73, 0xFF50}, // Home
|
||||
{0x77, 0xFF57}, // End
|
||||
{0x79, 0xFF55}, // Page Up
|
||||
{0x7A, 0xFF56}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0x7B, 0xFF51}, // Left Arrow
|
||||
{0x7C, 0xFF53}, // Right Arrow
|
||||
{0x7E, 0xFF52}, // Up Arrow
|
||||
{0x7D, 0xFF54}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x52, 0x0030}, // Numpad 0
|
||||
{0x53, 0x0031}, // Numpad 1
|
||||
{0x54, 0x0032}, // Numpad 2
|
||||
{0x55, 0x0033}, // Numpad 3
|
||||
{0x56, 0x0034}, // Numpad 4
|
||||
{0x57, 0x0035}, // Numpad 5
|
||||
{0x58, 0x0036}, // Numpad 6
|
||||
{0x59, 0x0037}, // Numpad 7
|
||||
{0x5B, 0x0038}, // Numpad 8
|
||||
{0x5C, 0x0039}, // Numpad 9
|
||||
{0x41, 0x003A}, // Numpad .
|
||||
{0x4B, 0x002F}, // Numpad /
|
||||
{0x43, 0x002A}, // Numpad *
|
||||
{0x4E, 0x002D}, // Numpad -
|
||||
{0x45, 0x002B}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x29, 0x003B}, // ; (Semicolon)
|
||||
{0x27, 0x0027}, // ' (Quote)
|
||||
{0x32, 0x007E}, // ` (Backtick)
|
||||
{0x2B, 0x002C}, // , (Comma)
|
||||
{0x2F, 0x002E}, // . (Period)
|
||||
{0x2C, 0x002F}, // / (Slash)
|
||||
{0x2A, 0x005C}, // \ (Backslash)
|
||||
{0x21, 0x005B}, // [ (Left Bracket)
|
||||
{0x1E, 0x005D}, // ] (Right Bracket)
|
||||
{0x1B, 0x002D}, // - (Minus)
|
||||
{0x18, 0x003D}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0x39, 0xFFE5}, // Caps Lock
|
||||
{0x38, 0xFFE1}, // Shift (Left)
|
||||
{0x3C, 0xFFE2}, // Shift (Right)
|
||||
{0x3B, 0xFFE3}, // Control (Left)
|
||||
{0x3E, 0xFFE4}, // Control (Right)
|
||||
{0x3A, 0xFFE9}, // Alt (Left)
|
||||
{0x3D, 0xFFEA}, // Alt (Right)
|
||||
{0x37, 0xFFEB}, // Left Command (Windows key)
|
||||
{0x36, 0xFFEC}, // Right Command
|
||||
};
|
||||
|
||||
// X11 KeySym to macOS CGKeyCode
|
||||
// std::map<int, int> x11KeySymToCgKeyCode = []() {
|
||||
// std::map<int, int> result;
|
||||
// for (const auto& pair : cgKeyCodeToX11KeySym) {
|
||||
// result[pair.second] = pair.first;
|
||||
// }
|
||||
// return result;
|
||||
// }();
|
||||
|
||||
std::map<int, int> x11KeySymToCgKeyCode = {
|
||||
// A-Z
|
||||
{0x0041, 0x00}, // A
|
||||
{0x0042, 0x0B}, // B
|
||||
{0x0043, 0x08}, // C
|
||||
{0x0044, 0x02}, // D
|
||||
{0x0045, 0x0E}, // E
|
||||
{0x0046, 0x03}, // F
|
||||
{0x0047, 0x05}, // G
|
||||
{0x0048, 0x04}, // H
|
||||
{0x0049, 0x22}, // I
|
||||
{0x004A, 0x26}, // J
|
||||
{0x004B, 0x28}, // K
|
||||
{0x004C, 0x25}, // L
|
||||
{0x004D, 0x2E}, // M
|
||||
{0x004E, 0x2D}, // N
|
||||
{0x004F, 0x1F}, // O
|
||||
{0x0050, 0x23}, // P
|
||||
{0x0051, 0x0C}, // Q
|
||||
{0x0052, 0x0F}, // R
|
||||
{0x0053, 0x01}, // S
|
||||
{0x0054, 0x11}, // T
|
||||
{0x0055, 0x20}, // U
|
||||
{0x0056, 0x09}, // V
|
||||
{0x0057, 0x0D}, // W
|
||||
{0x0058, 0x07}, // X
|
||||
{0x0059, 0x10}, // Y
|
||||
{0x005A, 0x06}, // Z
|
||||
|
||||
// 0-9
|
||||
{0x0030, 0x1D}, // 0
|
||||
{0x0031, 0x12}, // 1
|
||||
{0x0032, 0x13}, // 2
|
||||
{0x0033, 0x14}, // 3
|
||||
{0x0034, 0x15}, // 4
|
||||
{0x0035, 0x17}, // 5
|
||||
{0x0036, 0x16}, // 6
|
||||
{0x0037, 0x1A}, // 7
|
||||
{0x0038, 0x1C}, // 8
|
||||
{0x0039, 0x19}, // 9
|
||||
|
||||
// F1-F12
|
||||
{0xFFBE, 0x7A}, // F1
|
||||
{0xFFBF, 0x78}, // F2
|
||||
{0xFFC0, 0x63}, // F3
|
||||
{0xFFC1, 0x76}, // F4
|
||||
{0xFFC2, 0x60}, // F5
|
||||
{0xFFC3, 0x61}, // F6
|
||||
{0xFFC4, 0x62}, // F7
|
||||
{0xFFC5, 0x64}, // F8
|
||||
{0xFFC6, 0x65}, // F9
|
||||
{0xFFC7, 0x6D}, // F10
|
||||
{0xFFC8, 0x67}, // F11
|
||||
{0xFFC9, 0x6F}, // F12
|
||||
|
||||
// control keys
|
||||
{0xFF1B, 0x35}, // Escape
|
||||
{0xFF0D, 0x24}, // Enter
|
||||
{0x0020, 0x31}, // Space
|
||||
{0xFF08, 0x33}, // Backspace
|
||||
{0xFF09, 0x30}, // Tab
|
||||
{0xFF15, 0x74}, // Print Screen
|
||||
{0xFF63, 0x72}, // Insert
|
||||
{0xFFFF, 0x75}, // Delete
|
||||
{0xFF50, 0x73}, // Home
|
||||
{0xFF57, 0x77}, // End
|
||||
{0xFF55, 0x79}, // Page Up
|
||||
{0xFF56, 0x7A}, // Page Down
|
||||
|
||||
// arrow keys
|
||||
{0xFF51, 0x7B}, // Left Arrow
|
||||
{0xFF53, 0x7C}, // Right Arrow
|
||||
{0xFF52, 0x7E}, // Up Arrow
|
||||
{0xFF54, 0x7D}, // Down Arrow
|
||||
|
||||
// numpad
|
||||
{0x0030, 0x52}, // Numpad 0
|
||||
{0x0031, 0x53}, // Numpad 1
|
||||
{0x0032, 0x54}, // Numpad 2
|
||||
{0x0033, 0x55}, // Numpad 3
|
||||
{0x0034, 0x56}, // Numpad 4
|
||||
{0x0035, 0x57}, // Numpad 5
|
||||
{0x0036, 0x58}, // Numpad 6
|
||||
{0x0037, 0x59}, // Numpad 7
|
||||
{0x0038, 0x5B}, // Numpad 8
|
||||
{0x0039, 0x5C}, // Numpad 9
|
||||
{0x003A, 0x41}, // Numpad .
|
||||
{0x002F, 0x4B}, // Numpad /
|
||||
{0x002A, 0x43}, // Numpad *
|
||||
{0x002D, 0x4E}, // Numpad -
|
||||
{0x002B, 0x45}, // Numpad +
|
||||
|
||||
// symbol keys
|
||||
{0x003B, 0x29}, // ; (Semicolon)
|
||||
{0x0027, 0x27}, // ' (Quote)
|
||||
{0x007E, 0x32}, // ` (Backtick)
|
||||
{0x002C, 0x2B}, // , (Comma)
|
||||
{0x002E, 0x2F}, // . (Period)
|
||||
{0x002F, 0x2C}, // / (Slash)
|
||||
{0x005C, 0x2A}, // \ (Backslash)
|
||||
{0x005B, 0x21}, // [ (Left Bracket)
|
||||
{0x005D, 0x1E}, // ] (Right Bracket)
|
||||
{0x002D, 0x1B}, // - (Minus)
|
||||
{0x003D, 0x18}, // = (Equals)
|
||||
|
||||
// modifier keys
|
||||
{0xFFE5, 0x39}, // Caps Lock
|
||||
{0xFFE1, 0x38}, // Shift (Left)
|
||||
{0xFFE2, 0x3C}, // Shift (Right)
|
||||
{0xFFE3, 0x3B}, // Control (Left)
|
||||
{0xFFE4, 0x3E}, // Control (Right)
|
||||
{0xFFE9, 0x3A}, // Alt (Left)
|
||||
{0xFFEA, 0x3D}, // Alt (Right)
|
||||
{0xFFEB, 0x37}, // Left Command
|
||||
{0xFFEC, 0x36}, // Right Command
|
||||
};
|
||||
|
||||
#endif
|
||||
2802
src/device_controller/linux_keycode.h
Normal file
861
src/device_controller/macos_keycode.h
Normal file
@@ -0,0 +1,861 @@
|
||||
/*
|
||||
File: HIToolbox/Events.h
|
||||
|
||||
Contains: Event Manager Interfaces.
|
||||
|
||||
Copyright: 锟<> 1985-2008 by Apple Computer, Inc., all rights reserved
|
||||
|
||||
Bugs?: For bug reports, consult the following page on
|
||||
the World Wide Web:
|
||||
|
||||
http://developer.apple.com/bugreporter/
|
||||
|
||||
*/
|
||||
#ifndef __EVENTS__
|
||||
#define __EVENTS__
|
||||
|
||||
#ifndef __APPLICATIONSERVICES__
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#endif
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
#if PRAGMA_ONCE
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 2)
|
||||
|
||||
typedef UInt16 EventKind;
|
||||
typedef UInt16 EventMask;
|
||||
enum {
|
||||
nullEvent = 0,
|
||||
mouseDown = 1,
|
||||
mouseUp = 2,
|
||||
keyDown = 3,
|
||||
keyUp = 4,
|
||||
autoKey = 5,
|
||||
updateEvt = 6,
|
||||
diskEvt = 7, /* Not sent in Carbon. See kEventClassVolume in CarbonEvents.h*/
|
||||
activateEvt = 8,
|
||||
osEvt = 15,
|
||||
kHighLevelEvent = 23
|
||||
};
|
||||
|
||||
enum {
|
||||
mDownMask = 1 << mouseDown, /* mouse button pressed*/
|
||||
mUpMask = 1 << mouseUp, /* mouse button released*/
|
||||
keyDownMask = 1 << keyDown, /* key pressed*/
|
||||
keyUpMask = 1 << keyUp, /* key released*/
|
||||
autoKeyMask = 1 << autoKey, /* key repeatedly held down*/
|
||||
updateMask = 1 << updateEvt, /* window needs updating*/
|
||||
diskMask = 1 << diskEvt, /* disk inserted*/
|
||||
activMask = 1 << activateEvt, /* activate/deactivate window*/
|
||||
highLevelEventMask = 0x0400, /* high-level events (includes AppleEvents)*/
|
||||
osMask = 1 << osEvt, /* operating system events (suspend, resume)*/
|
||||
everyEvent = 0xFFFF /* all of the above*/
|
||||
};
|
||||
|
||||
enum {
|
||||
charCodeMask = 0x000000FF,
|
||||
keyCodeMask = 0x0000FF00,
|
||||
adbAddrMask = 0x00FF0000,
|
||||
osEvtMessageMask = (UInt32)0xFF000000
|
||||
};
|
||||
|
||||
enum {
|
||||
/* OS event messages. Event (sub)code is in the high byte of the message
|
||||
field.*/
|
||||
mouseMovedMessage = 0x00FA,
|
||||
suspendResumeMessage = 0x0001
|
||||
};
|
||||
|
||||
enum {
|
||||
resumeFlag = 1 /* Bit 0 of message indicates resume vs suspend*/
|
||||
};
|
||||
|
||||
#if CALL_NOT_IN_CARBON
|
||||
/* convertClipboardFlag is not ever set under Carbon. This is because scrap
|
||||
* conversion is */
|
||||
/* not tied to suspend/resume events any longer. Your application should
|
||||
* instead use the */
|
||||
/* scrap promise mechanism and fulfill scrap requests only when your promise
|
||||
* keeper proc */
|
||||
/* is called. If you need to know if the scrap has changed, you can cache the
|
||||
* last */
|
||||
/* ScrapRef you received and compare it with the current ScrapRef */
|
||||
enum {
|
||||
convertClipboardFlag =
|
||||
2 /* Bit 1 in resume message indicates clipboard change*/
|
||||
};
|
||||
|
||||
#endif /* CALL_NOT_IN_CARBON */
|
||||
|
||||
/*
|
||||
CARBON ALERT! BATTLESTATIONS!
|
||||
|
||||
The EventModifiers bits defined here are also used in the newer Carbon Event
|
||||
key modifiers parameters. There are two main differences:
|
||||
|
||||
1) The Carbon key modifiers parameter is a UInt32, not a UInt16. Never try
|
||||
to extract the key modifiers parameter from a Carbon Event into an
|
||||
EventModifiers type. You will probably get your stack trashed. 2) The Carbon
|
||||
key modifiers is just that: key modifiers. That parameter will never contain
|
||||
the button state bit.
|
||||
*/
|
||||
typedef UInt16 EventModifiers;
|
||||
enum {
|
||||
/* modifiers */
|
||||
activeFlagBit = 0, /* activate? (activateEvt and mouseDown)*/
|
||||
btnStateBit = 7, /* state of button?*/
|
||||
cmdKeyBit = 8, /* command key down?*/
|
||||
shiftKeyBit = 9, /* shift key down?*/
|
||||
alphaLockBit = 10, /* alpha lock down?*/
|
||||
optionKeyBit = 11, /* option key down?*/
|
||||
controlKeyBit = 12, /* control key down?*/
|
||||
rightShiftKeyBit = 13, /* right shift key down? Not supported on Mac OS X.*/
|
||||
rightOptionKeyBit = 14, /* right Option key down? Not supported on Mac OS X.*/
|
||||
rightControlKeyBit =
|
||||
15 /* right Control key down? Not supported on Mac OS X.*/
|
||||
};
|
||||
|
||||
enum {
|
||||
activeFlag = 1 << activeFlagBit,
|
||||
btnState = 1 << btnStateBit,
|
||||
cmdKey = 1 << cmdKeyBit,
|
||||
shiftKey = 1 << shiftKeyBit,
|
||||
alphaLock = 1 << alphaLockBit,
|
||||
optionKey = 1 << optionKeyBit,
|
||||
controlKey = 1 << controlKeyBit,
|
||||
rightShiftKey = 1 << rightShiftKeyBit, /* Not supported on Mac OS X.*/
|
||||
rightOptionKey = 1 << rightOptionKeyBit, /* Not supported on Mac OS X.*/
|
||||
rightControlKey = 1 << rightControlKeyBit /* Not supported on Mac OS X.*/
|
||||
};
|
||||
|
||||
/* MacRoman character codes*/
|
||||
enum {
|
||||
kNullCharCode = 0,
|
||||
kHomeCharCode = 1,
|
||||
kEnterCharCode = 3,
|
||||
kEndCharCode = 4,
|
||||
kHelpCharCode = 5,
|
||||
kBellCharCode = 7,
|
||||
kBackspaceCharCode = 8,
|
||||
kTabCharCode = 9,
|
||||
kLineFeedCharCode = 10,
|
||||
kVerticalTabCharCode = 11,
|
||||
kPageUpCharCode = 11,
|
||||
kFormFeedCharCode = 12,
|
||||
kPageDownCharCode = 12,
|
||||
kReturnCharCode = 13,
|
||||
kFunctionKeyCharCode = 16,
|
||||
kCommandCharCode = 17, /* glyph available only in system fonts*/
|
||||
kCheckCharCode = 18, /* glyph available only in system fonts*/
|
||||
kDiamondCharCode = 19, /* glyph available only in system fonts*/
|
||||
kAppleLogoCharCode = 20, /* glyph available only in system fonts*/
|
||||
kEscapeCharCode = 27,
|
||||
kClearCharCode = 27,
|
||||
kLeftArrowCharCode = 28,
|
||||
kRightArrowCharCode = 29,
|
||||
kUpArrowCharCode = 30,
|
||||
kDownArrowCharCode = 31,
|
||||
kSpaceCharCode = 32,
|
||||
kDeleteCharCode = 127,
|
||||
kBulletCharCode = 165,
|
||||
kNonBreakingSpaceCharCode = 202
|
||||
};
|
||||
|
||||
/* useful Unicode code points*/
|
||||
enum {
|
||||
kShiftUnicode = 0x21E7, /* Unicode UPWARDS WHITE ARROW*/
|
||||
kControlUnicode = 0x2303, /* Unicode UP ARROWHEAD*/
|
||||
kOptionUnicode = 0x2325, /* Unicode OPTION KEY*/
|
||||
kCommandUnicode = 0x2318, /* Unicode PLACE OF INTEREST SIGN*/
|
||||
kPencilUnicode = 0x270E, /* Unicode LOWER RIGHT PENCIL; actually pointed left
|
||||
until Mac OS X 10.3*/
|
||||
kPencilLeftUnicode = 0xF802, /* Unicode LOWER LEFT PENCIL; available in Mac OS
|
||||
X 10.3 and later*/
|
||||
kCheckUnicode = 0x2713, /* Unicode CHECK MARK*/
|
||||
kDiamondUnicode = 0x25C6, /* Unicode BLACK DIAMOND*/
|
||||
kBulletUnicode = 0x2022, /* Unicode BULLET*/
|
||||
kAppleLogoUnicode = 0xF8FF /* Unicode APPLE LOGO*/
|
||||
};
|
||||
|
||||
/*
|
||||
* Summary:
|
||||
* Virtual keycodes
|
||||
*
|
||||
* Discussion:
|
||||
* These constants are the virtual keycodes defined originally in
|
||||
* Inside Mac Volume V, pg. V-191. They identify physical keys on a
|
||||
* keyboard. Those constants with "ANSI" in the name are labeled
|
||||
* according to the key position on an ANSI-standard US keyboard.
|
||||
* For example, kVK_ANSI_A indicates the virtual keycode for the key
|
||||
* with the letter 'A' in the US keyboard layout. Other keyboard
|
||||
* layouts may have the 'A' key label on a different physical key;
|
||||
* in this case, pressing 'A' will generate a different virtual
|
||||
* keycode.
|
||||
*/
|
||||
enum {
|
||||
kVK_ANSI_A = 0x00,
|
||||
kVK_ANSI_S = 0x01,
|
||||
kVK_ANSI_D = 0x02,
|
||||
kVK_ANSI_F = 0x03,
|
||||
kVK_ANSI_H = 0x04,
|
||||
kVK_ANSI_G = 0x05,
|
||||
kVK_ANSI_Z = 0x06,
|
||||
kVK_ANSI_X = 0x07,
|
||||
kVK_ANSI_C = 0x08,
|
||||
kVK_ANSI_V = 0x09,
|
||||
kVK_ANSI_B = 0x0B,
|
||||
kVK_ANSI_Q = 0x0C,
|
||||
kVK_ANSI_W = 0x0D,
|
||||
kVK_ANSI_E = 0x0E,
|
||||
kVK_ANSI_R = 0x0F,
|
||||
kVK_ANSI_Y = 0x10,
|
||||
kVK_ANSI_T = 0x11,
|
||||
kVK_ANSI_1 = 0x12,
|
||||
kVK_ANSI_2 = 0x13,
|
||||
kVK_ANSI_3 = 0x14,
|
||||
kVK_ANSI_4 = 0x15,
|
||||
kVK_ANSI_6 = 0x16,
|
||||
kVK_ANSI_5 = 0x17,
|
||||
kVK_ANSI_Equal = 0x18,
|
||||
kVK_ANSI_9 = 0x19,
|
||||
kVK_ANSI_7 = 0x1A,
|
||||
kVK_ANSI_Minus = 0x1B,
|
||||
kVK_ANSI_8 = 0x1C,
|
||||
kVK_ANSI_0 = 0x1D,
|
||||
kVK_ANSI_RightBracket = 0x1E,
|
||||
kVK_ANSI_O = 0x1F,
|
||||
kVK_ANSI_U = 0x20,
|
||||
kVK_ANSI_LeftBracket = 0x21,
|
||||
kVK_ANSI_I = 0x22,
|
||||
kVK_ANSI_P = 0x23,
|
||||
kVK_ANSI_L = 0x25,
|
||||
kVK_ANSI_J = 0x26,
|
||||
kVK_ANSI_Quote = 0x27,
|
||||
kVK_ANSI_K = 0x28,
|
||||
kVK_ANSI_Semicolon = 0x29,
|
||||
kVK_ANSI_Backslash = 0x2A,
|
||||
kVK_ANSI_Comma = 0x2B,
|
||||
kVK_ANSI_Slash = 0x2C,
|
||||
kVK_ANSI_N = 0x2D,
|
||||
kVK_ANSI_M = 0x2E,
|
||||
kVK_ANSI_Period = 0x2F,
|
||||
kVK_ANSI_Grave = 0x32,
|
||||
kVK_ANSI_KeypadDecimal = 0x41,
|
||||
kVK_ANSI_KeypadMultiply = 0x43,
|
||||
kVK_ANSI_KeypadPlus = 0x45,
|
||||
kVK_ANSI_KeypadClear = 0x47,
|
||||
kVK_ANSI_KeypadDivide = 0x4B,
|
||||
kVK_ANSI_KeypadEnter = 0x4C,
|
||||
kVK_ANSI_KeypadMinus = 0x4E,
|
||||
kVK_ANSI_KeypadEquals = 0x51,
|
||||
kVK_ANSI_Keypad0 = 0x52,
|
||||
kVK_ANSI_Keypad1 = 0x53,
|
||||
kVK_ANSI_Keypad2 = 0x54,
|
||||
kVK_ANSI_Keypad3 = 0x55,
|
||||
kVK_ANSI_Keypad4 = 0x56,
|
||||
kVK_ANSI_Keypad5 = 0x57,
|
||||
kVK_ANSI_Keypad6 = 0x58,
|
||||
kVK_ANSI_Keypad7 = 0x59,
|
||||
kVK_ANSI_Keypad8 = 0x5B,
|
||||
kVK_ANSI_Keypad9 = 0x5C
|
||||
};
|
||||
|
||||
/* keycodes for keys that are independent of keyboard layout*/
|
||||
enum {
|
||||
kVK_Return = 0x24,
|
||||
kVK_Tab = 0x30,
|
||||
kVK_Space = 0x31,
|
||||
kVK_Delete = 0x33,
|
||||
kVK_Escape = 0x35,
|
||||
kVK_Command = 0x37,
|
||||
kVK_Shift = 0x38,
|
||||
kVK_CapsLock = 0x39,
|
||||
kVK_Option = 0x3A,
|
||||
kVK_Control = 0x3B,
|
||||
kVK_RightCommand = 0x36,
|
||||
kVK_RightShift = 0x3C,
|
||||
kVK_RightOption = 0x3D,
|
||||
kVK_RightControl = 0x3E,
|
||||
kVK_Function = 0x3F,
|
||||
kVK_F17 = 0x40,
|
||||
kVK_VolumeUp = 0x48,
|
||||
kVK_VolumeDown = 0x49,
|
||||
kVK_Mute = 0x4A,
|
||||
kVK_F18 = 0x4F,
|
||||
kVK_F19 = 0x50,
|
||||
kVK_F20 = 0x5A,
|
||||
kVK_F5 = 0x60,
|
||||
kVK_F6 = 0x61,
|
||||
kVK_F7 = 0x62,
|
||||
kVK_F3 = 0x63,
|
||||
kVK_F8 = 0x64,
|
||||
kVK_F9 = 0x65,
|
||||
kVK_F11 = 0x67,
|
||||
kVK_F13 = 0x69,
|
||||
kVK_F16 = 0x6A,
|
||||
kVK_F14 = 0x6B,
|
||||
kVK_F10 = 0x6D,
|
||||
kVK_ContextualMenu = 0x6E,
|
||||
kVK_F12 = 0x6F,
|
||||
kVK_F15 = 0x71,
|
||||
kVK_Help = 0x72,
|
||||
kVK_Home = 0x73,
|
||||
kVK_PageUp = 0x74,
|
||||
kVK_ForwardDelete = 0x75,
|
||||
kVK_F4 = 0x76,
|
||||
kVK_End = 0x77,
|
||||
kVK_F2 = 0x78,
|
||||
kVK_PageDown = 0x79,
|
||||
kVK_F1 = 0x7A,
|
||||
kVK_LeftArrow = 0x7B,
|
||||
kVK_RightArrow = 0x7C,
|
||||
kVK_DownArrow = 0x7D,
|
||||
kVK_UpArrow = 0x7E
|
||||
};
|
||||
|
||||
/* ISO keyboards only*/
|
||||
enum { kVK_ISO_Section = 0x0A };
|
||||
|
||||
/* JIS keyboards only*/
|
||||
enum {
|
||||
kVK_JIS_Yen = 0x5D,
|
||||
kVK_JIS_Underscore = 0x5E,
|
||||
kVK_JIS_KeypadComma = 0x5F,
|
||||
kVK_JIS_Eisu = 0x66,
|
||||
kVK_JIS_Kana = 0x68
|
||||
};
|
||||
|
||||
struct EventRecord {
|
||||
EventKind what;
|
||||
unsigned long message;
|
||||
UInt32 when;
|
||||
Point where;
|
||||
EventModifiers modifiers;
|
||||
};
|
||||
typedef struct EventRecord EventRecord;
|
||||
typedef CALLBACK_API(void, FKEYProcPtr)(void);
|
||||
typedef STACK_UPP_TYPE(FKEYProcPtr) FKEYUPP;
|
||||
/*
|
||||
* NewFKEYUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* DisposeFKEYUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* InvokeFKEYUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* GetMouse() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use HIGetMousePosition instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.5 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void GetMouse(Point *mouseLoc)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* Button() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use GetCurrentButtonState or GetCurrentEventButtonState instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework but
|
||||
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
|
||||
* CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean Button(void)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* StillDown()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean StillDown(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* WaitMouseUp()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean WaitMouseUp(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* KeyTranslate() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use UCKeyTranslate instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt32 KeyTranslate(const void *transData, UInt16 keycode, UInt32 *state)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* GetCaretTime()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt32 GetCaretTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
QuickTime 3.0 supports GetKeys() on unix and win32
|
||||
But, on little endian machines you will have to be
|
||||
careful about bit numberings and/or use a KeyMapByteArray
|
||||
instead.
|
||||
*/
|
||||
#if TARGET_API_MAC_OS8
|
||||
|
||||
typedef UInt32 KeyMap[4];
|
||||
#else
|
||||
typedef BigEndianUInt32 KeyMap[4];
|
||||
#endif /* TARGET_API_MAC_OS8 */
|
||||
|
||||
typedef UInt8 KeyMapByteArray[16];
|
||||
/*
|
||||
* GetKeys()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void GetKeys(KeyMap theKeys) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/* Obsolete event types & masks */
|
||||
enum {
|
||||
networkEvt = 10,
|
||||
driverEvt = 11,
|
||||
app1Evt = 12,
|
||||
app2Evt = 13,
|
||||
app3Evt = 14,
|
||||
app4Evt = 15,
|
||||
networkMask = 0x0400,
|
||||
driverMask = 0x0800,
|
||||
app1Mask = 0x1000,
|
||||
app2Mask = 0x2000,
|
||||
app3Mask = 0x4000,
|
||||
app4Mask = 0x8000
|
||||
};
|
||||
|
||||
struct EvQEl {
|
||||
QElemPtr qLink;
|
||||
SInt16 qType;
|
||||
EventKind
|
||||
evtQWhat; /* this part is identical to the EventRecord as defined above */
|
||||
unsigned long evtQMessage;
|
||||
UInt32 evtQWhen;
|
||||
Point evtQWhere;
|
||||
EventModifiers evtQModifiers;
|
||||
};
|
||||
typedef struct EvQEl EvQEl;
|
||||
typedef EvQEl *EvQElPtr;
|
||||
typedef CALLBACK_API(void, GetNextEventFilterProcPtr)(EventRecord *theEvent,
|
||||
Boolean *result);
|
||||
typedef STACK_UPP_TYPE(GetNextEventFilterProcPtr) GetNextEventFilterUPP;
|
||||
/*
|
||||
* NewGetNextEventFilterUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* DisposeGetNextEventFilterUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
/*
|
||||
* InvokeGetNextEventFilterUPP()
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: not available
|
||||
* CarbonLib: not available
|
||||
* Non-Carbon CFM: available as macro/inline
|
||||
*/
|
||||
|
||||
typedef GetNextEventFilterUPP GNEFilterUPP;
|
||||
#if !__LP64__
|
||||
/*
|
||||
* GetDblTime()
|
||||
*
|
||||
* Summary:
|
||||
* Returns the maximum time (in units of 1/60th of a second) allowed
|
||||
* between two consecutive mouse-down events in order for the second
|
||||
* click to be considered a double-click.
|
||||
*
|
||||
* Discussion:
|
||||
* In 64-bit applications, you may replace calls to this API with
|
||||
* calls to NXClickTime (declared in
|
||||
* <IOKit/hidsystem/event_status_driver.h>) or with +[NSEvent
|
||||
* doubleClickInterval] (available in Mac OS X 10.6 and later).
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Result:
|
||||
* The maximum time between mouse-downs allowed for a double-click.
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt32 GetDblTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* SetEventMask()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void SetEventMask(EventMask value)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
/*
|
||||
* GetNextEvent() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use ReceiveNextEvent instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean GetNextEvent(EventMask eventMask, EventRecord *theEvent)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* WaitNextEvent() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use ReceiveNextEvent instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean WaitNextEvent(EventMask eventMask, EventRecord *theEvent,
|
||||
UInt32 sleep, RgnHandle mouseRgn) /* can be NULL */
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* EventAvail() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use FindSpecificEventInQueue instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean EventAvail(EventMask eventMask, EventRecord *theEvent)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
* PostEvent() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use PostEventToQueue or CGEventPost instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern OSErr PostEvent(EventKind eventNum, UInt32 eventMsg)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* FlushEvents() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use FlushEventsMatchingListFromQueue,
|
||||
* FlushSpecificEventsFromQueue, or FlushEventQueue instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework but
|
||||
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
|
||||
* CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void FlushEvents(EventMask whichMask, EventMask stopMask)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
#if OLDROUTINENAMES
|
||||
#define KeyTrans(transData, keycode, state) \
|
||||
KeyTranslate(transData, keycode, state)
|
||||
#endif /* OLDROUTINENAMES */
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* KeyScript() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use TISSelectInputSource API for positive verbs (ScriptCode).
|
||||
* Use TSMDocument properties to restrict input sources:
|
||||
* kTSMDocumentEnabledInputSourcesPropertyTag
|
||||
* kTSMDocumentInputSourceOverridePropertyTag
|
||||
*
|
||||
* Summary:
|
||||
* Switch to the specified script's default (last used) input source.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] but deprecated in 10.5 CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void KeyScript(short code)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* IsCmdChar() *** DEPRECATED ***
|
||||
*
|
||||
* Deprecated:
|
||||
* Use IsUserCancelEventRef or CheckEventQueueForUserCancel instead.
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework but
|
||||
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
|
||||
* CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern Boolean IsCmdChar(const EventRecord *event, short test)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
|
||||
|
||||
/*
|
||||
LowMem accessor functions previously in LowMem.h
|
||||
*/
|
||||
/*
|
||||
* LMGetKeyThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern SInt16 LMGetKeyThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKeyThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKeyThresh(SInt16 value)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* LMGetKeyRepThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern SInt16 LMGetKeyRepThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKeyRepThresh()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKeyRepThresh(SInt16 value)
|
||||
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* LMGetKbdLast()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt8 LMGetKbdLast(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKbdLast()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKbdLast(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
/*
|
||||
* LMGetKbdType()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework
|
||||
* CarbonLib: in CarbonLib 1.0 and later
|
||||
* Non-Carbon CFM: in InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern UInt8 LMGetKbdType(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#if !__LP64__
|
||||
/*
|
||||
* LMSetKbdType()
|
||||
*
|
||||
* Mac OS X threading:
|
||||
* Not thread safe
|
||||
*
|
||||
* Availability:
|
||||
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
|
||||
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
|
||||
* InterfaceLib 7.1 and later
|
||||
*/
|
||||
extern void LMSetKbdType(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
|
||||
|
||||
#endif /* !__LP64__ */
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __EVENTS__ */
|
||||
124
src/device_controller/mouse/linux/mouse_controller.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() { Destroy(); }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
root_ = DefaultRootWindow(display_);
|
||||
|
||||
int event_base, error_base, major_version, minor_version;
|
||||
if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version,
|
||||
&minor_version)) {
|
||||
LOG_ERROR("XTest extension not available");
|
||||
XCloseDisplay(display_);
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() {
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
display_ = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
int display_index) {
|
||||
switch (remote_action.type) {
|
||||
case mouse:
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::move:
|
||||
SetMousePosition(
|
||||
static_cast<int>(remote_action.m.x *
|
||||
display_info_list_[display_index].width +
|
||||
display_info_list_[display_index].left),
|
||||
static_cast<int>(remote_action.m.y *
|
||||
display_info_list_[display_index].height +
|
||||
display_info_list_[display_index].top));
|
||||
break;
|
||||
case MouseFlag::left_down:
|
||||
XTestFakeButtonEvent(display_, 1, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
XTestFakeButtonEvent(display_, 1, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
XTestFakeButtonEvent(display_, 3, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
XTestFakeButtonEvent(display_, 3, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
XTestFakeButtonEvent(display_, 2, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
XTestFakeButtonEvent(display_, 2, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::wheel_vertical: {
|
||||
if (remote_action.m.s > 0) {
|
||||
SimulateMouseWheel(4, remote_action.m.s);
|
||||
} else if (remote_action.m.s < 0) {
|
||||
SimulateMouseWheel(5, -remote_action.m.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MouseFlag::wheel_horizontal: {
|
||||
if (remote_action.m.s > 0) {
|
||||
SimulateMouseWheel(6, remote_action.m.s);
|
||||
} else if (remote_action.m.s < 0) {
|
||||
SimulateMouseWheel(7, -remote_action.m.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SetMousePosition(int x, int y) {
|
||||
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyDown(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyUp(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateMouseWheel(int direction_button, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
XTestFakeButtonEvent(display_, direction_button, True, CurrentTime);
|
||||
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
|
||||
}
|
||||
XFlush(display_);
|
||||
}
|
||||
41
src/device_controller/mouse/linux/mouse_controller.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-05-07
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class MouseController : public DeviceController {
|
||||
public:
|
||||
MouseController();
|
||||
virtual ~MouseController();
|
||||
|
||||
public:
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
|
||||
private:
|
||||
void SimulateKeyDown(int kval);
|
||||
void SimulateKeyUp(int kval);
|
||||
void SetMousePosition(int x, int y);
|
||||
void SimulateMouseWheel(int direction_button, int count);
|
||||
|
||||
Display* display_ = nullptr;
|
||||
Window root_ = 0;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
int screen_width_ = 0;
|
||||
int screen_height_ = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
101
src/device_controller/mouse/mac/mouse_controller.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {}
|
||||
|
||||
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
|
||||
display_info_list_ = display_info_list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
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 = nullptr;
|
||||
CGEventType mouse_type;
|
||||
CGMouseButton mouse_button;
|
||||
CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (mouse_event) {
|
||||
CGEventPost(kCGHIDEventTap, mouse_event);
|
||||
CFRelease(mouse_event);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
30
src/device_controller/mouse/mac/mouse_controller.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class MouseController : public DeviceController {
|
||||
public:
|
||||
MouseController();
|
||||
virtual ~MouseController();
|
||||
|
||||
public:
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
|
||||
private:
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
bool left_dragging_ = false;
|
||||
bool right_dragging_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
72
src/device_controller/mouse/windows/mouse_controller.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {}
|
||||
|
||||
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
|
||||
display_info_list_ = display_info_list;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
int display_index) {
|
||||
INPUT ip;
|
||||
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
ip.type = INPUT_MOUSE;
|
||||
ip.mi.dx =
|
||||
(LONG)(remote_action.m.x * 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.time = 0;
|
||||
|
||||
SetCursorPos(ip.mi.dx, ip.mi.dy);
|
||||
|
||||
if (ip.mi.dwFlags != MOUSEEVENTF_MOVE) {
|
||||
SendInput(1, &ip, sizeof(INPUT));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
src/device_controller/mouse/windows/mouse_controller.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class MouseController : public DeviceController {
|
||||
public:
|
||||
MouseController();
|
||||
virtual ~MouseController();
|
||||
|
||||
public:
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
|
||||
private:
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
};
|
||||
|
||||
#endif
|
||||
206
src/device_controller/windows_keycode.h
Normal file
@@ -0,0 +1,206 @@
|
||||
// virtual_key_codes.h
|
||||
#ifndef VIRTUAL_KEY_CODES_H
|
||||
#define VIRTUAL_KEY_CODES_H
|
||||
|
||||
#define VK_LBUTTON 0x01 // Left mouse button
|
||||
#define VK_RBUTTON 0x02 // Right mouse button
|
||||
#define VK_CANCEL 0x03 // Control-break processing
|
||||
#define VK_MBUTTON 0x04 // Middle mouse button
|
||||
#define VK_XBUTTON1 0x05 // X1 mouse button
|
||||
#define VK_XBUTTON2 0x06 // X2 mouse button
|
||||
// 0x07 Reserved
|
||||
#define VK_BACK 0x08 // Backspace key
|
||||
#define VK_TAB 0x09 // Tab key
|
||||
// 0x0A-0B Reserved
|
||||
#define VK_CLEAR 0x0C // Clear key
|
||||
#define VK_RETURN 0x0D // Enter key
|
||||
// 0x0E-0F Unassigned
|
||||
#define VK_SHIFT 0x10 // Shift key
|
||||
#define VK_CONTROL 0x11 // Ctrl key
|
||||
#define VK_MENU 0x12 // Alt key
|
||||
#define VK_PAUSE 0x13 // Pause key
|
||||
#define VK_CAPITAL 0x14 // Caps lock key
|
||||
#define VK_KANA 0x15 // IME Kana mode
|
||||
#define VK_HANGUL 0x15 // IME Hangul mode
|
||||
#define VK_IME_ON 0x16 // IME On
|
||||
#define VK_JUNJA 0x17 // IME Junja mode
|
||||
#define VK_FINAL 0x18 // IME final mode
|
||||
#define VK_HANJA 0x19 // IME Hanja mode
|
||||
#define VK_KANJI 0x19 // IME Kanji mode
|
||||
#define VK_IME_OFF 0x1A // IME Off
|
||||
#define VK_ESCAPE 0x1B // Esc key
|
||||
#define VK_CONVERT 0x1C // IME convert
|
||||
#define VK_NONCONVERT 0x1D // IME nonconvert
|
||||
#define VK_ACCEPT 0x1E // IME accept
|
||||
#define VK_MODECHANGE 0x1F // IME mode change request
|
||||
#define VK_SPACE 0x20 // Spacebar key
|
||||
#define VK_PRIOR 0x21 // Page up key
|
||||
#define VK_NEXT 0x22 // Page down key
|
||||
#define VK_END 0x23 // End key
|
||||
#define VK_HOME 0x24 // Home key
|
||||
#define VK_LEFT 0x25 // Left arrow key
|
||||
#define VK_UP 0x26 // Up arrow key
|
||||
#define VK_RIGHT 0x27 // Right arrow key
|
||||
#define VK_DOWN 0x28 // Down arrow key
|
||||
#define VK_SELECT 0x29 // Select key
|
||||
#define VK_PRINT 0x2A // Print key
|
||||
#define VK_EXECUTE 0x2B // Execute key
|
||||
#define VK_SNAPSHOT 0x2C // Print screen key
|
||||
#define VK_INSERT 0x2D // Insert key
|
||||
#define VK_DELETE 0x2E // Delete key
|
||||
#define VK_HELP 0x2F // Help key
|
||||
|
||||
#define VK_0 0x30 // 0 key
|
||||
#define VK_1 0x31 // 1 key
|
||||
#define VK_2 0x32 // 2 key
|
||||
#define VK_3 0x33 // 3 key
|
||||
#define VK_4 0x34 // 4 key
|
||||
#define VK_5 0x35 // 5 key
|
||||
#define VK_6 0x36 // 6 key
|
||||
#define VK_7 0x37 // 7 key
|
||||
#define VK_8 0x38 // 8 key
|
||||
#define VK_9 0x39 // 9 key
|
||||
// 0x3A-40 Undefined
|
||||
|
||||
#define VK_A 0x41 // A key
|
||||
#define VK_B 0x42 // B key
|
||||
#define VK_C 0x43 // C key
|
||||
#define VK_D 0x44 // D key
|
||||
#define VK_E 0x45 // E key
|
||||
#define VK_F 0x46 // F key
|
||||
#define VK_G 0x47 // G key
|
||||
#define VK_H 0x48 // H key
|
||||
#define VK_I 0x49 // I key
|
||||
#define VK_J 0x4A // J key
|
||||
#define VK_K 0x4B // K key
|
||||
#define VK_L 0x4C // L key
|
||||
#define VK_M 0x4D // M key
|
||||
#define VK_N 0x4E // N key
|
||||
#define VK_O 0x4F // O key
|
||||
#define VK_P 0x50 // P key
|
||||
#define VK_Q 0x51 // Q key
|
||||
#define VK_R 0x52 // R key
|
||||
#define VK_S 0x53 // S key
|
||||
#define VK_T 0x54 // T key
|
||||
#define VK_U 0x55 // U key
|
||||
#define VK_V 0x56 // V key
|
||||
#define VK_W 0x57 // W key
|
||||
#define VK_X 0x58 // X key
|
||||
#define VK_Y 0x59 // Y key
|
||||
#define VK_Z 0x5A // Z key
|
||||
|
||||
#define VK_LWIN 0x5B // Left Windows logo key
|
||||
#define VK_RWIN 0x5C // Right Windows logo key
|
||||
#define VK_APPS 0x5D // Application key
|
||||
// 0x5E Reserved
|
||||
#define VK_SLEEP 0x5F // Computer Sleep key
|
||||
|
||||
#define VK_NUMPAD0 0x60 // Numeric keypad 0 key
|
||||
#define VK_NUMPAD1 0x61 // Numeric keypad 1 key
|
||||
#define VK_NUMPAD2 0x62 // Numeric keypad 2 key
|
||||
#define VK_NUMPAD3 0x63 // Numeric keypad 3 key
|
||||
#define VK_NUMPAD4 0x64 // Numeric keypad 4 key
|
||||
#define VK_NUMPAD5 0x65 // Numeric keypad 5 key
|
||||
#define VK_NUMPAD6 0x66 // Numeric keypad 6 key
|
||||
#define VK_NUMPAD7 0x67 // Numeric keypad 7 key
|
||||
#define VK_NUMPAD8 0x68 // Numeric keypad 8 key
|
||||
#define VK_NUMPAD9 0x69 // Numeric keypad 9 key
|
||||
#define VK_MULTIPLY 0x6A // Multiply key
|
||||
#define VK_ADD 0x6B // Add key
|
||||
#define VK_SEPARATOR 0x6C // Separator key
|
||||
#define VK_SUBTRACT 0x6D // Subtract key
|
||||
#define VK_DECIMAL 0x6E // Decimal key
|
||||
#define VK_DIVIDE 0x6F // Divide key
|
||||
|
||||
#define VK_F1 0x70 // F1 key
|
||||
#define VK_F2 0x71 // F2 key
|
||||
#define VK_F3 0x72 // F3 key
|
||||
#define VK_F4 0x73 // F4 key
|
||||
#define VK_F5 0x74 // F5 key
|
||||
#define VK_F6 0x75 // F6 key
|
||||
#define VK_F7 0x76 // F7 key
|
||||
#define VK_F8 0x77 // F8 key
|
||||
#define VK_F9 0x78 // F9 key
|
||||
#define VK_F10 0x79 // F10 key
|
||||
#define VK_F11 0x7A // F11 key
|
||||
#define VK_F12 0x7B // F12 key
|
||||
#define VK_F13 0x7C // F13 key
|
||||
#define VK_F14 0x7D // F14 key
|
||||
#define VK_F15 0x7E // F15 key
|
||||
#define VK_F16 0x7F // F16 key
|
||||
#define VK_F17 0x80 // F17 key
|
||||
#define VK_F18 0x81 // F18 key
|
||||
#define VK_F19 0x82 // F19 key
|
||||
#define VK_F20 0x83 // F20 key
|
||||
#define VK_F21 0x84 // F21 key
|
||||
#define VK_F22 0x85 // F22 key
|
||||
#define VK_F23 0x86 // F23 key
|
||||
#define VK_F24 0x87 // F24 key
|
||||
// 0x88–0x8F Reserved
|
||||
|
||||
#define VK_NUMLOCK 0x90 // Num lock key
|
||||
#define VK_SCROLL 0x91 // Scroll lock key
|
||||
// 0x92–0x96 OEM specific
|
||||
// 0x97–0x9F Unassigned
|
||||
|
||||
#define VK_LSHIFT 0xA0 // Left Shift key
|
||||
#define VK_RSHIFT 0xA1 // Right Shift key
|
||||
#define VK_LCONTROL 0xA2 // Left Ctrl key
|
||||
#define VK_RCONTROL 0xA3 // Right Ctrl key
|
||||
#define VK_LMENU 0xA4 // Left Alt key
|
||||
#define VK_RMENU 0xA5 // Right Alt key
|
||||
|
||||
#define VK_BROWSER_BACK 0xA6 // Browser Back key
|
||||
#define VK_BROWSER_FORWARD 0xA7 // Browser Forward key
|
||||
#define VK_BROWSER_REFRESH 0xA8 // Browser Refresh key
|
||||
#define VK_BROWSER_STOP 0xA9 // Browser Stop key
|
||||
#define VK_BROWSER_SEARCH 0xAA // Browser Search key
|
||||
#define VK_BROWSER_FAVORITES 0xAB // Browser Favorites key
|
||||
#define VK_BROWSER_HOME 0xAC // Browser Start and Home key
|
||||
#define VK_VOLUME_MUTE 0xAD // Volume Mute key
|
||||
#define VK_VOLUME_DOWN 0xAE // Volume Down key
|
||||
#define VK_VOLUME_UP 0xAF // Volume Up key
|
||||
|
||||
#define VK_MEDIA_NEXT_TRACK 0xB0 // Next Track key
|
||||
#define VK_MEDIA_PREV_TRACK 0xB1 // Previous Track key
|
||||
#define VK_MEDIA_STOP 0xB2 // Stop Media key
|
||||
#define VK_MEDIA_PLAY_PAUSE 0xB3 // Play/Pause Media key
|
||||
#define VK_LAUNCH_MAIL 0xB4 // Start Mail key
|
||||
#define VK_LAUNCH_MEDIA_SELECT 0xB5 // Select Media key
|
||||
#define VK_LAUNCH_APP1 0xB6 // Start Application 1 key
|
||||
#define VK_LAUNCH_APP2 0xB7 // Start Application 2 key
|
||||
// 0xB8–0xB9 Reserved
|
||||
|
||||
#define VK_OEM_1 0xBA // For US: Semicolon/Colon key
|
||||
#define VK_OEM_PLUS 0xBB // Equals/Plus key
|
||||
#define VK_OEM_COMMA 0xBC // Comma/Less Than key
|
||||
#define VK_OEM_MINUS 0xBD // Dash/Underscore key
|
||||
#define VK_OEM_PERIOD 0xBE // Period/Greater Than key
|
||||
#define VK_OEM_2 0xBF // Slash/Question Mark key
|
||||
#define VK_OEM_3 0xC0 // Grave Accent/Tilde key
|
||||
// 0xC1–0xDA Reserved
|
||||
|
||||
#define VK_OEM_4 0xDB // Left Brace key
|
||||
#define VK_OEM_5 0xDC // Backslash/Pipe key
|
||||
#define VK_OEM_6 0xDD // Right Brace key
|
||||
#define VK_OEM_7 0xDE // Apostrophe/Quote key
|
||||
#define VK_OEM_8 0xDF // (Canadian CSA: Right Ctrl key)
|
||||
// 0xE0 Reserved
|
||||
#define VK_OEM_102 0xE2 // (European ISO: Backslash/Pipe key)
|
||||
// 0xE3–E4 OEM specific
|
||||
#define VK_PROCESSKEY 0xE5 // IME PROCESS key
|
||||
// 0xE6 OEM specific
|
||||
#define VK_PACKET 0xE7 // Unicode characters as keystrokes
|
||||
// 0xE8 Unassigned
|
||||
// 0xE9–F5 OEM specific
|
||||
#define VK_ATTN 0xF6 // Attn key
|
||||
#define VK_CRSEL 0xF7 // CrSel key
|
||||
#define VK_EXSEL 0xF8 // ExSel key
|
||||
#define VK_EREOF 0xF9 // Erase EOF key
|
||||
#define VK_PLAY 0xFA // Play key
|
||||
#define VK_ZOOM 0xFB // Zoom key
|
||||
#define VK_NONAME 0xFC // Reserved
|
||||
#define VK_PA1 0xFD // PA1 key
|
||||
#define VK_OEM_CLEAR 0xFE // Clear key
|
||||
|
||||
#endif // VIRTUAL_KEY_CODES_H
|
||||
2909
src/gui/assets/fonts/OPPOSans_Regular.h
Normal file
5668
src/gui/assets/fonts/fa_regular_400.h
Normal file
35041
src/gui/assets/fonts/fa_solid_900.h
Normal file
1408
src/gui/assets/icons/IconsFontAwesome6.h
Normal file
63
src/gui/assets/layouts/layout.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-06-14
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _LAYOUT_STYLE_H_
|
||||
#define _LAYOUT_STYLE_H_
|
||||
|
||||
#define MENU_WINDOW_WIDTH_CN 300
|
||||
#define MENU_WINDOW_HEIGHT_CN 280
|
||||
#define LOCAL_WINDOW_WIDTH_CN 300
|
||||
#define LOCAL_WINDOW_HEIGHT_CN 280
|
||||
#define REMOTE_WINDOW_WIDTH_CN 300
|
||||
#define REMOTE_WINDOW_HEIGHT_CN 280
|
||||
#define MENU_WINDOW_WIDTH_EN 190
|
||||
#define MENU_WINDOW_HEIGHT_EN 245
|
||||
#define IPUT_WINDOW_WIDTH 160
|
||||
#define INPUT_WINDOW_PADDING_CN 66
|
||||
#define INPUT_WINDOW_PADDING_EN 96
|
||||
#define SETTINGS_WINDOW_WIDTH_CN 202
|
||||
#define SETTINGS_WINDOW_WIDTH_EN 248
|
||||
#if _WIN32
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 345
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 345
|
||||
#else
|
||||
#define SETTINGS_WINDOW_HEIGHT_CN 315
|
||||
#define SETTINGS_WINDOW_HEIGHT_EN 315
|
||||
#endif
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 195
|
||||
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 195
|
||||
#define LANGUAGE_SELECT_WINDOW_PADDING_CN 120
|
||||
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 167
|
||||
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120
|
||||
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167
|
||||
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 120
|
||||
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167
|
||||
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 120
|
||||
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167
|
||||
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_TURN_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_TURN_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_SRTP_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
|
||||
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
|
||||
#define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171
|
||||
#define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218
|
||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
|
||||
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN 137
|
||||
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90
|
||||
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN 137
|
||||
#define SETTINGS_SELECT_WINDOW_WIDTH 73
|
||||
#define SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH 130
|
||||
#define SETTINGS_OK_BUTTON_PADDING_CN 65
|
||||
#define SETTINGS_OK_BUTTON_PADDING_EN 83
|
||||
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78
|
||||
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91
|
||||
|
||||
#endif
|
||||
172
src/gui/assets/localization/localization.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
#ifndef _LOCALIZATION_H_
|
||||
#define _LOCALIZATION_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
namespace localization {
|
||||
|
||||
static std::vector<std::string> local_desktop = {
|
||||
reinterpret_cast<const char*>(u8"本桌面"), "Local Desktop"};
|
||||
static std::vector<std::string> local_id = {
|
||||
reinterpret_cast<const char*>(u8"本机ID"), "Local ID"};
|
||||
static std::vector<std::string> local_id_copied_to_clipboard = {
|
||||
reinterpret_cast<const char*>(u8"已复制到剪贴板"), "Copied to clipboard"};
|
||||
static std::vector<std::string> password = {
|
||||
reinterpret_cast<const char*>(u8"密码"), "Password"};
|
||||
static std::vector<std::string> max_password_len = {
|
||||
reinterpret_cast<const char*>(u8"最大6个字符"), "Max 6 chars"};
|
||||
|
||||
static std::vector<std::string> remote_desktop = {
|
||||
reinterpret_cast<const char*>(u8"控制远程桌面"), "Control Remote Desktop"};
|
||||
static std::vector<std::string> remote_id = {
|
||||
reinterpret_cast<const char*>(u8"对端ID"), "Remote ID"};
|
||||
static std::vector<std::string> connect = {
|
||||
reinterpret_cast<const char*>(u8"连接"), "Connect"};
|
||||
static std::vector<std::string> recent_connections = {
|
||||
reinterpret_cast<const char*>(u8"近期连接"), "Recent Connections"};
|
||||
static std::vector<std::string> disconnect = {
|
||||
reinterpret_cast<const char*>(u8"断开连接"), "Disconnect"};
|
||||
static std::vector<std::string> fullscreen = {
|
||||
reinterpret_cast<const char*>(u8"全屏"), " Fullscreen"};
|
||||
static std::vector<std::string> show_net_traffic_stats = {
|
||||
reinterpret_cast<const char*>(u8"显示流量统计"), "Show Net Traffic Stats"};
|
||||
static std::vector<std::string> hide_net_traffic_stats = {
|
||||
reinterpret_cast<const char*>(u8"隐藏流量统计"), "Hide Net Traffic Stats"};
|
||||
static std::vector<std::string> video = {
|
||||
reinterpret_cast<const char*>(u8"视频"), "Video"};
|
||||
static std::vector<std::string> audio = {
|
||||
reinterpret_cast<const char*>(u8"音频"), "Audio"};
|
||||
static std::vector<std::string> data = {reinterpret_cast<const char*>(u8"数据"),
|
||||
"Data"};
|
||||
static std::vector<std::string> total = {
|
||||
reinterpret_cast<const char*>(u8"总计"), "Total"};
|
||||
static std::vector<std::string> in = {reinterpret_cast<const char*>(u8"输入"),
|
||||
"In"};
|
||||
static std::vector<std::string> out = {reinterpret_cast<const char*>(u8"输出"),
|
||||
"Out"};
|
||||
static std::vector<std::string> loss_rate = {
|
||||
reinterpret_cast<const char*>(u8"丢包率"), "Loss Rate"};
|
||||
static std::vector<std::string> exit_fullscreen = {
|
||||
reinterpret_cast<const char*>(u8"退出全屏"), "Exit fullscreen"};
|
||||
static std::vector<std::string> control_mouse = {
|
||||
reinterpret_cast<const char*>(u8"控制"), "Control"};
|
||||
static std::vector<std::string> release_mouse = {
|
||||
reinterpret_cast<const char*>(u8"释放"), "Release"};
|
||||
static std::vector<std::string> audio_capture = {
|
||||
reinterpret_cast<const char*>(u8"声音"), "Audio"};
|
||||
static std::vector<std::string> mute = {
|
||||
reinterpret_cast<const char*>(u8" 静音"), " Mute"};
|
||||
static std::vector<std::string> settings = {
|
||||
reinterpret_cast<const char*>(u8"设置"), "Settings"};
|
||||
static std::vector<std::string> language = {
|
||||
reinterpret_cast<const char*>(u8"语言:"), "Language:"};
|
||||
static std::vector<std::string> language_zh = {
|
||||
reinterpret_cast<const char*>(u8"中文"), "Chinese"};
|
||||
static std::vector<std::string> language_en = {
|
||||
reinterpret_cast<const char*>(u8"英文"), "English"};
|
||||
static std::vector<std::string> video_quality = {
|
||||
reinterpret_cast<const char*>(u8"视频质量:"), "Video Quality:"};
|
||||
static std::vector<std::string> video_frame_rate = {
|
||||
reinterpret_cast<const char*>(u8"画面采集帧率:"),
|
||||
"Video Capture Frame Rate:"};
|
||||
static std::vector<std::string> video_quality_high = {
|
||||
reinterpret_cast<const char*>(u8"高"), "High"};
|
||||
static std::vector<std::string> video_quality_medium = {
|
||||
reinterpret_cast<const char*>(u8"中"), "Medium"};
|
||||
static std::vector<std::string> video_quality_low = {
|
||||
reinterpret_cast<const char*>(u8"低"), "Low"};
|
||||
static std::vector<std::string> video_encode_format = {
|
||||
reinterpret_cast<const char*>(u8"视频编码格式:"), "Video Encode Format:"};
|
||||
static std::vector<std::string> av1 = {reinterpret_cast<const char*>(u8"AV1"),
|
||||
"AV1"};
|
||||
static std::vector<std::string> h264 = {
|
||||
reinterpret_cast<const char*>(u8"H.264"), "H.264"};
|
||||
static std::vector<std::string> enable_hardware_video_codec = {
|
||||
reinterpret_cast<const char*>(u8"启用硬件编解码器:"),
|
||||
"Enable Hardware Video Codec:"};
|
||||
static std::vector<std::string> enable_turn = {
|
||||
reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"};
|
||||
static std::vector<std::string> enable_srtp = {
|
||||
reinterpret_cast<const char*>(u8"启用SRTP:"), "Enable SRTP:"};
|
||||
static std::vector<std::string> self_hosted_server_config = {
|
||||
reinterpret_cast<const char*>(u8"自托管服务器配置"),
|
||||
"Self-Hosted Server Config"};
|
||||
static std::vector<std::string> self_hosted_server_settings = {
|
||||
reinterpret_cast<const char*>(u8"自托管服务器设置"),
|
||||
"Self-Hosted Server Settings"};
|
||||
static std::vector<std::string> self_hosted_server_address = {
|
||||
reinterpret_cast<const char*>(u8"服务器地址:"), "Server Address:"};
|
||||
static std::vector<std::string> self_hosted_server_port = {
|
||||
reinterpret_cast<const char*>(u8"信令服务端口:"), "Signal Service Port:"};
|
||||
static std::vector<std::string> self_hosted_server_coturn_server_port = {
|
||||
reinterpret_cast<const char*>(u8"中继服务端口:"), "Relay Service Port:"};
|
||||
static std::vector<std::string> self_hosted_server_certificate_path = {
|
||||
reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"};
|
||||
static std::vector<std::string> select_a_file = {
|
||||
reinterpret_cast<const char*>(u8"请选择文件"), "Please select a file"};
|
||||
static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"),
|
||||
"OK"};
|
||||
static std::vector<std::string> cancel = {
|
||||
reinterpret_cast<const char*>(u8"取消"), "Cancel"};
|
||||
|
||||
static std::vector<std::string> new_password = {
|
||||
reinterpret_cast<const char*>(u8"请输入六位密码:"),
|
||||
"Please input a six-char password:"};
|
||||
|
||||
static std::vector<std::string> input_password = {
|
||||
reinterpret_cast<const char*>(u8"请输入密码:"), "Please input password:"};
|
||||
static std::vector<std::string> validate_password = {
|
||||
reinterpret_cast<const char*>(u8"验证密码中..."), "Validate password ..."};
|
||||
static std::vector<std::string> reinput_password = {
|
||||
reinterpret_cast<const char*>(u8"请重新输入密码"),
|
||||
"Please input password again"};
|
||||
|
||||
static std::vector<std::string> remember_password = {
|
||||
reinterpret_cast<const char*>(u8"记住密码"), "Remember password"};
|
||||
|
||||
static std::vector<std::string> signal_connected = {
|
||||
reinterpret_cast<const char*>(u8"已连接服务器"), "Connected"};
|
||||
static std::vector<std::string> signal_disconnected = {
|
||||
reinterpret_cast<const char*>(u8"未连接服务器"), "Disconnected"};
|
||||
|
||||
static std::vector<std::string> p2p_connected = {
|
||||
reinterpret_cast<const char*>(u8"对等连接已建立"), "P2P Connected"};
|
||||
static std::vector<std::string> p2p_disconnected = {
|
||||
reinterpret_cast<const char*>(u8"对等连接已断开"), "P2P Disconnected"};
|
||||
static std::vector<std::string> p2p_connecting = {
|
||||
reinterpret_cast<const char*>(u8"正在建立对等连接..."),
|
||||
"P2P Connecting ..."};
|
||||
static std::vector<std::string> p2p_failed = {
|
||||
reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"};
|
||||
static std::vector<std::string> p2p_closed = {
|
||||
reinterpret_cast<const char*>(u8"对等连接已关闭"), "P2P closed"};
|
||||
|
||||
static std::vector<std::string> no_such_id = {
|
||||
reinterpret_cast<const char*>(u8"无此ID"), "No such ID"};
|
||||
|
||||
static std::vector<std::string> about = {
|
||||
reinterpret_cast<const char*>(u8"关于"), "About"};
|
||||
static std::vector<std::string> version = {
|
||||
reinterpret_cast<const char*>(u8"版本"), "Version"};
|
||||
|
||||
static std::vector<std::string> confirm_delete_connection = {
|
||||
reinterpret_cast<const char*>(u8"确认删除此连接"),
|
||||
"Confirm to delete this connection"};
|
||||
#if _WIN32
|
||||
|
||||
static std::vector<std::string> minimize_to_tray = {
|
||||
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
|
||||
"Minimize to system tray when exit:"};
|
||||
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
|
||||
#endif
|
||||
} // namespace localization
|
||||
#endif
|
||||
1241
src/gui/main.cpp
290
src/gui/panels/local_peer_panel.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
#include <random>
|
||||
|
||||
#include "layout.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(22, 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(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;
|
||||
}
|
||||
286
src/gui/panels/recent_connections_panel.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::GetWindowDrawList()->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;
|
||||
}
|
||||
181
src/gui/panels/remote_peer_panel.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "layout.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);
|
||||
focused_remote_id_ = remote_id;
|
||||
|
||||
if (client_properties_.find(remote_id) == client_properties_.end()) {
|
||||
client_properties_[remote_id] =
|
||||
std::make_shared<SubStreamWindowProperties>();
|
||||
auto props = client_properties_[remote_id];
|
||||
props->local_id_ = "C-" + std::string(client_id_);
|
||||
props->remote_id_ = remote_id;
|
||||
memcpy(&props->params_, ¶ms_, sizeof(Params));
|
||||
props->params_.user_id = props->local_id_.c_str();
|
||||
props->peer_ = CreatePeer(&props->params_);
|
||||
AddAudioStream(props->peer_, props->audio_label_.c_str());
|
||||
AddDataStream(props->peer_, props->data_label_.c_str());
|
||||
|
||||
if (props->peer_) {
|
||||
LOG_INFO("[{}] Create peer instance successful", props->local_id_);
|
||||
Init(props->peer_);
|
||||
LOG_INFO("[{}] Peer init finish", props->local_id_);
|
||||
} else {
|
||||
LOG_INFO("Create peer [{}] 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;
|
||||
}
|
||||
1434
src/gui/render.cpp
Normal file
473
src/gui/render.h
Normal file
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MAIN_WINDOW_H_
|
||||
#define _MAIN_WINDOW_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "IconsFontAwesome6.h"
|
||||
#include "config_center.h"
|
||||
#include "device_controller_factory.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
#include "imgui_impl_sdlrenderer3.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "minirtc.h"
|
||||
#include "path_manager.h"
|
||||
#include "screen_capturer_factory.h"
|
||||
#include "speaker_capturer_factory.h"
|
||||
#include "thumbnail.h"
|
||||
#if _WIN32
|
||||
#include "win_tray.h"
|
||||
#endif
|
||||
|
||||
class Render {
|
||||
public:
|
||||
struct SubStreamWindowProperties {
|
||||
Params params_;
|
||||
PeerPtr* peer_ = nullptr;
|
||||
std::string audio_label_ = "control_audio";
|
||||
std::string data_label_ = "control_data";
|
||||
std::string local_id_ = "";
|
||||
std::string remote_id_ = "";
|
||||
bool exit_ = false;
|
||||
bool signal_connected_ = false;
|
||||
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
||||
bool connection_established_ = false;
|
||||
bool rejoin_ = false;
|
||||
bool net_traffic_stats_button_pressed_ = false;
|
||||
bool mouse_control_button_pressed_ = false;
|
||||
bool mouse_controller_is_started_ = false;
|
||||
bool audio_capture_button_pressed_ = false;
|
||||
bool control_mouse_ = false;
|
||||
bool streaming_ = false;
|
||||
bool is_control_bar_in_left_ = true;
|
||||
bool control_bar_hovered_ = false;
|
||||
bool display_selectable_hovered_ = false;
|
||||
bool control_bar_expand_ = true;
|
||||
bool reset_control_bar_pos_ = false;
|
||||
bool control_window_width_is_changing_ = false;
|
||||
bool control_window_height_is_changing_ = false;
|
||||
bool p2p_mode_ = true;
|
||||
bool remember_password_ = false;
|
||||
char remote_password_[7] = "";
|
||||
float sub_stream_window_width_ = 1280;
|
||||
float sub_stream_window_height_ = 720;
|
||||
float control_window_min_width_ = 20;
|
||||
float control_window_max_width_ = 230;
|
||||
float control_window_min_height_ = 40;
|
||||
float control_window_max_height_ = 170;
|
||||
float control_window_width_ = 230;
|
||||
float control_window_height_ = 40;
|
||||
float control_bar_pos_x_ = 0;
|
||||
float control_bar_pos_y_ = 30;
|
||||
float mouse_diff_control_bar_pos_x_ = 0;
|
||||
float mouse_diff_control_bar_pos_y_ = 0;
|
||||
double control_bar_button_pressed_time_ = 0;
|
||||
double net_traffic_stats_button_pressed_time_ = 0;
|
||||
unsigned char* dst_buffer_ = nullptr;
|
||||
size_t dst_buffer_capacity_ = 0;
|
||||
float mouse_pos_x_ = 0;
|
||||
float mouse_pos_y_ = 0;
|
||||
float mouse_pos_x_last_ = 0;
|
||||
float mouse_pos_y_last_ = 0;
|
||||
int texture_width_ = 1280;
|
||||
int texture_height_ = 720;
|
||||
int video_width_ = 0;
|
||||
int video_height_ = 0;
|
||||
int video_width_last_ = 0;
|
||||
int video_height_last_ = 0;
|
||||
int selected_display_ = 0;
|
||||
size_t video_size_ = 0;
|
||||
bool tab_selected_ = false;
|
||||
bool tab_opened_ = true;
|
||||
std::optional<float> pos_x_before_docked_;
|
||||
std::optional<float> pos_y_before_docked_;
|
||||
float render_window_x_ = 0;
|
||||
float render_window_y_ = 0;
|
||||
float render_window_width_ = 0;
|
||||
float render_window_height_ = 0;
|
||||
std::string fullscreen_button_label_ = "Fullscreen";
|
||||
std::string net_traffic_stats_button_label_ = "Show Net Traffic Stats";
|
||||
std::string mouse_control_button_label_ = "Mouse Control";
|
||||
std::string audio_capture_button_label_ = "Audio Capture";
|
||||
std::string remote_host_name_ = "";
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
SDL_Texture* stream_texture_ = nullptr;
|
||||
uint8_t* argb_buffer_ = nullptr;
|
||||
int argb_buffer_size_ = 0;
|
||||
SDL_Rect stream_render_rect_;
|
||||
SDL_Rect stream_render_rect_last_;
|
||||
ImVec2 control_window_pos_;
|
||||
ConnectionStatus connection_status_ = ConnectionStatus::Closed;
|
||||
TraversalMode traversal_mode_ = TraversalMode::UnknownMode;
|
||||
int fps_ = 0;
|
||||
int frame_count_ = 0;
|
||||
std::chrono::steady_clock::time_point last_time_;
|
||||
XNetTrafficStats net_traffic_stats_;
|
||||
};
|
||||
|
||||
public:
|
||||
Render();
|
||||
~Render();
|
||||
|
||||
public:
|
||||
int Run();
|
||||
|
||||
private:
|
||||
void InitializeLogger();
|
||||
void InitializeSettings();
|
||||
void InitializeSDL();
|
||||
void InitializeModules();
|
||||
void InitializeMainWindow();
|
||||
void MainLoop();
|
||||
void UpdateLabels();
|
||||
void UpdateInteractions();
|
||||
void HandleRecentConnections();
|
||||
void HandleStreamWindow();
|
||||
void Cleanup();
|
||||
void CleanupFactories();
|
||||
void CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props);
|
||||
void CleanupPeers();
|
||||
void CleanSubStreamWindowProperties(
|
||||
std::shared_ptr<SubStreamWindowProperties> props);
|
||||
void UpdateRenderRect();
|
||||
void ProcessSdlEvent(const SDL_Event& event);
|
||||
|
||||
private:
|
||||
int CreateStreamRenderWindow();
|
||||
int TitleBar(bool main_window);
|
||||
int MainWindow();
|
||||
int StreamWindow();
|
||||
int LocalWindow();
|
||||
int RemoteWindow();
|
||||
int RecentConnectionsWindow();
|
||||
int SettingWindow();
|
||||
int SelfHostedServerWindow();
|
||||
int ShowSimpleFileBrowser();
|
||||
int ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
int ControlBar(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
int AboutWindow();
|
||||
int StatusBar();
|
||||
bool ConnectionStatusWindow(
|
||||
std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
int ShowRecentConnections();
|
||||
|
||||
private:
|
||||
int ConnectTo(const std::string& remote_id, const char* password,
|
||||
bool remember_password);
|
||||
int CreateMainWindow();
|
||||
int DestroyMainWindow();
|
||||
int CreateStreamWindow();
|
||||
int DestroyStreamWindow();
|
||||
int SetupFontAndStyle();
|
||||
int SetupMainWindow();
|
||||
int DestroyMainWindowContext();
|
||||
int SetupStreamWindow();
|
||||
int DestroyStreamWindowContext();
|
||||
int DrawMainWindow();
|
||||
int DrawStreamWindow();
|
||||
int ConfirmDeleteConnection();
|
||||
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
void DrawConnectionStatusText(
|
||||
std::shared_ptr<SubStreamWindowProperties>& props);
|
||||
|
||||
public:
|
||||
static void OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static void OnReceiveAudioBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static void OnReceiveDataBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static void OnSignalStatusCb(SignalStatus status, const char* user_id,
|
||||
size_t user_id_size, void* user_data);
|
||||
|
||||
static void OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||
size_t user_id_size, void* user_data);
|
||||
|
||||
static void NetStatusReport(const char* client_id, size_t client_id_size,
|
||||
TraversalMode mode,
|
||||
const XNetTrafficStats* net_traffic_stats,
|
||||
const char* user_id, const size_t user_id_size,
|
||||
void* user_data);
|
||||
|
||||
static SDL_HitTestResult HitTestCallback(SDL_Window* window,
|
||||
const SDL_Point* area, void* data);
|
||||
|
||||
static std::vector<char> SerializeRemoteAction(const RemoteAction& action);
|
||||
|
||||
static bool DeserializeRemoteAction(const char* data, size_t size,
|
||||
RemoteAction& out);
|
||||
|
||||
static void FreeRemoteAction(RemoteAction& action);
|
||||
|
||||
private:
|
||||
int SendKeyCommand(int key_code, bool is_down);
|
||||
int ProcessMouseEvent(const SDL_Event& event);
|
||||
|
||||
static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len);
|
||||
static void SdlCaptureAudioOut(void* userdata, Uint8* stream, int len);
|
||||
|
||||
private:
|
||||
int SaveSettingsIntoCacheFile();
|
||||
int LoadSettingsFromCacheFile();
|
||||
|
||||
int ScreenCapturerInit();
|
||||
int StartScreenCapturer();
|
||||
int StopScreenCapturer();
|
||||
|
||||
int StartSpeakerCapturer();
|
||||
int StopSpeakerCapturer();
|
||||
|
||||
int StartMouseController();
|
||||
int StopMouseController();
|
||||
|
||||
int StartKeyboardCapturer();
|
||||
int StopKeyboardCapturer();
|
||||
|
||||
int CreateConnectionPeer();
|
||||
|
||||
int AudioDeviceInit();
|
||||
int AudioDeviceDestroy();
|
||||
|
||||
private:
|
||||
struct CDCache {
|
||||
char client_id_with_password[17];
|
||||
int language;
|
||||
int video_quality;
|
||||
int video_frame_rate;
|
||||
int video_encode_format;
|
||||
bool enable_hardware_video_codec;
|
||||
bool enable_turn;
|
||||
bool enable_srtp;
|
||||
|
||||
unsigned char key[16];
|
||||
unsigned char iv[16];
|
||||
};
|
||||
|
||||
private:
|
||||
CDCache cd_cache_;
|
||||
std::mutex cd_cache_mutex_;
|
||||
std::unique_ptr<ConfigCenter> config_center_;
|
||||
ConfigCenter::LANGUAGE localization_language_ =
|
||||
ConfigCenter::LANGUAGE::CHINESE;
|
||||
std::unique_ptr<PathManager> path_manager_;
|
||||
std::string cert_path_;
|
||||
std::string exec_log_path_;
|
||||
std::string dll_log_path_;
|
||||
std::string cache_path_;
|
||||
int localization_language_index_ = -1;
|
||||
int localization_language_index_last_ = -1;
|
||||
bool modules_inited_ = false;
|
||||
/* ------ all windows property start ------ */
|
||||
float title_bar_width_ = 640;
|
||||
float title_bar_height_ = 30;
|
||||
/* ------ all windows property end ------ */
|
||||
|
||||
/* ------ main window property start ------ */
|
||||
// thumbnail
|
||||
unsigned char aes128_key_[16];
|
||||
unsigned char aes128_iv_[16];
|
||||
std::unique_ptr<Thumbnail> thumbnail_;
|
||||
|
||||
// recent connections
|
||||
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>
|
||||
recent_connections_;
|
||||
int recent_connection_image_width_ = 160;
|
||||
int recent_connection_image_height_ = 90;
|
||||
uint32_t recent_connection_image_save_time_ = 0;
|
||||
|
||||
// main window render
|
||||
SDL_Window* main_window_ = nullptr;
|
||||
SDL_Renderer* main_renderer_ = nullptr;
|
||||
ImGuiContext* main_ctx_ = nullptr;
|
||||
bool exit_ = false;
|
||||
const int sdl_refresh_ms_ = 16; // ~60 FPS
|
||||
#if _WIN32
|
||||
std::unique_ptr<WinTray> tray_;
|
||||
#endif
|
||||
|
||||
// main window properties
|
||||
bool start_mouse_controller_ = false;
|
||||
bool mouse_controller_is_started_ = false;
|
||||
bool start_screen_capturer_ = false;
|
||||
bool screen_capturer_is_started_ = false;
|
||||
bool start_keyboard_capturer_ = false;
|
||||
bool keyboard_capturer_is_started_ = false;
|
||||
bool foucs_on_main_window_ = false;
|
||||
bool foucs_on_stream_window_ = false;
|
||||
bool audio_capture_ = false;
|
||||
int main_window_width_real_ = 720;
|
||||
int main_window_height_real_ = 540;
|
||||
float main_window_dpi_scaling_w_ = 1.0f;
|
||||
float main_window_dpi_scaling_h_ = 1.0f;
|
||||
float main_window_width_default_ = 640;
|
||||
float main_window_height_default_ = 480;
|
||||
float main_window_width_ = 640;
|
||||
float main_window_height_ = 480;
|
||||
float main_window_width_last_ = 640;
|
||||
float main_window_height_last_ = 480;
|
||||
float local_window_width_ = 320;
|
||||
float local_window_height_ = 235;
|
||||
float remote_window_width_ = 320;
|
||||
float remote_window_height_ = 235;
|
||||
float local_child_window_width_ = 266;
|
||||
float local_child_window_height_ = 180;
|
||||
float remote_child_window_width_ = 266;
|
||||
float remote_child_window_height_ = 180;
|
||||
float main_window_text_y_padding_ = 10;
|
||||
float main_child_window_x_padding_ = 27;
|
||||
float main_child_window_y_padding_ = 45;
|
||||
float status_bar_height_ = 22;
|
||||
float connection_status_window_width_ = 200;
|
||||
float connection_status_window_height_ = 150;
|
||||
float notification_window_width_ = 200;
|
||||
float notification_window_height_ = 80;
|
||||
float about_window_width_ = 300;
|
||||
float about_window_height_ = 170;
|
||||
int screen_width_ = 1280;
|
||||
int screen_height_ = 720;
|
||||
int selected_display_ = 0;
|
||||
std::string connect_button_label_ = "Connect";
|
||||
char input_password_tmp_[7] = "";
|
||||
char input_password_[7] = "";
|
||||
std::string random_password_ = "";
|
||||
char new_password_[7] = "";
|
||||
char remote_id_display_[12] = "";
|
||||
unsigned char audio_buffer_[720];
|
||||
int audio_len_ = 0;
|
||||
bool audio_buffer_fresh_ = false;
|
||||
bool need_to_rejoin_ = false;
|
||||
bool just_created_ = false;
|
||||
std::string controlled_remote_id_ = "";
|
||||
std::string focused_remote_id_ = "";
|
||||
bool need_to_send_host_info_ = false;
|
||||
SDL_Event last_mouse_event;
|
||||
SDL_AudioStream* output_stream_;
|
||||
uint32_t STREAM_REFRESH_EVENT = 0;
|
||||
|
||||
// stream window render
|
||||
SDL_Window* stream_window_ = nullptr;
|
||||
SDL_Renderer* stream_renderer_ = nullptr;
|
||||
ImGuiContext* stream_ctx_ = nullptr;
|
||||
|
||||
// stream window properties
|
||||
bool need_to_create_stream_window_ = false;
|
||||
bool stream_window_created_ = false;
|
||||
bool stream_window_inited_ = false;
|
||||
bool window_maximized_ = false;
|
||||
bool stream_window_grabbed_ = false;
|
||||
bool control_mouse_ = false;
|
||||
int stream_window_width_default_ = 1280;
|
||||
int stream_window_height_default_ = 720;
|
||||
float stream_window_width_ = 1280;
|
||||
float stream_window_height_ = 720;
|
||||
SDL_PixelFormat stream_pixformat_ = SDL_PIXELFORMAT_NV12;
|
||||
int stream_window_width_real_ = 1280;
|
||||
int stream_window_height_real_ = 720;
|
||||
float stream_window_dpi_scaling_w_ = 1.0f;
|
||||
float stream_window_dpi_scaling_h_ = 1.0f;
|
||||
|
||||
bool label_inited_ = false;
|
||||
bool connect_button_pressed_ = false;
|
||||
bool password_validating_ = false;
|
||||
uint32_t password_validating_time_ = 0;
|
||||
bool show_settings_window_ = false;
|
||||
bool show_self_hosted_server_config_window_ = false;
|
||||
bool rejoin_ = false;
|
||||
bool local_id_copied_ = false;
|
||||
bool show_password_ = true;
|
||||
bool show_about_window_ = false;
|
||||
bool show_connection_status_window_ = false;
|
||||
bool show_reset_password_window_ = false;
|
||||
bool fullscreen_button_pressed_ = false;
|
||||
bool focus_on_input_widget_ = true;
|
||||
bool is_client_mode_ = false;
|
||||
bool reload_recent_connections_ = true;
|
||||
bool show_confirm_delete_connection_ = false;
|
||||
bool delete_connection_ = false;
|
||||
bool is_tab_bar_hovered_ = false;
|
||||
std::string delete_connection_name_ = "";
|
||||
bool re_enter_remote_id_ = false;
|
||||
double copy_start_time_ = 0;
|
||||
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
||||
std::string signal_status_str_ = "";
|
||||
bool signal_connected_ = false;
|
||||
PeerPtr* peer_ = nullptr;
|
||||
PeerPtr* peer_reserved_ = nullptr;
|
||||
std::string video_primary_label_ = "primary_display";
|
||||
std::string video_secondary_label_ = "secondary_display";
|
||||
std::string audio_label_ = "audio";
|
||||
std::string data_label_ = "data";
|
||||
Params params_;
|
||||
SDL_AudioDeviceID input_dev_;
|
||||
SDL_AudioDeviceID output_dev_;
|
||||
ScreenCapturerFactory* screen_capturer_factory_ = nullptr;
|
||||
ScreenCapturer* screen_capturer_ = nullptr;
|
||||
SpeakerCapturerFactory* speaker_capturer_factory_ = nullptr;
|
||||
SpeakerCapturer* speaker_capturer_ = nullptr;
|
||||
DeviceControllerFactory* device_controller_factory_ = nullptr;
|
||||
MouseController* mouse_controller_ = nullptr;
|
||||
KeyboardCapturer* keyboard_capturer_ = nullptr;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
uint64_t last_frame_time_;
|
||||
char client_id_[10] = "";
|
||||
char client_id_display_[12] = "";
|
||||
char client_id_with_password_[17] = "";
|
||||
char password_saved_[7] = "";
|
||||
int language_button_value_ = 0;
|
||||
int video_quality_button_value_ = 0;
|
||||
int video_frame_rate_button_value_ = 0;
|
||||
int video_encode_format_button_value_ = 0;
|
||||
bool enable_hardware_video_codec_ = false;
|
||||
bool enable_turn_ = false;
|
||||
bool enable_srtp_ = false;
|
||||
char signal_server_ip_[256] = "api.crossdesk.cn";
|
||||
char signal_server_port_[6] = "9099";
|
||||
char coturn_server_port_[6] = "3478";
|
||||
char cert_file_path_[256] = "";
|
||||
bool enable_self_hosted_server_ = false;
|
||||
int language_button_value_last_ = 0;
|
||||
int video_quality_button_value_last_ = 0;
|
||||
int video_encode_format_button_value_last_ = 0;
|
||||
bool enable_hardware_video_codec_last_ = false;
|
||||
bool enable_turn_last_ = false;
|
||||
bool enable_srtp_last_ = false;
|
||||
bool enable_minimize_to_tray_ = false;
|
||||
bool enable_minimize_to_tray_last_ = false;
|
||||
char signal_server_ip_self_[256] = "";
|
||||
char signal_server_port_self_[6] = "";
|
||||
char coturn_server_port_self_[6] = "";
|
||||
std::string tls_cert_path_self_ = "";
|
||||
bool settings_window_pos_reset_ = true;
|
||||
bool self_hosted_server_config_window_pos_reset_ = true;
|
||||
std::string selected_current_file_path_ = "";
|
||||
bool show_file_browser_ = 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
|
||||
545
src/gui/render_callback.cpp
Normal file
@@ -0,0 +1,545 @@
|
||||
#include "device_controller.h"
|
||||
#include "localization.h"
|
||||
#include "platform.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
|
||||
#ifdef DESK_PORT_DEBUG
|
||||
#else
|
||||
#define MOUSE_CONTROL 1
|
||||
#endif
|
||||
|
||||
int Render::SendKeyCommand(int key_code, bool is_down) {
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::keyboard;
|
||||
if (is_down) {
|
||||
remote_action.k.flag = KeyFlag::key_down;
|
||||
} else {
|
||||
remote_action.k.flag = KeyFlag::key_up;
|
||||
}
|
||||
remote_action.k.key_value = key_code;
|
||||
|
||||
if (!controlled_remote_id_.empty()) {
|
||||
if (client_properties_.find(controlled_remote_id_) !=
|
||||
client_properties_.end()) {
|
||||
auto props = client_properties_[controlled_remote_id_];
|
||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ProcessMouseEvent(const SDL_Event& event) {
|
||||
controlled_remote_id_ = "";
|
||||
int video_width, video_height = 0;
|
||||
int render_width, render_height = 0;
|
||||
float ratio_x, ratio_y = 0;
|
||||
RemoteAction remote_action;
|
||||
|
||||
for (auto& it : client_properties_) {
|
||||
auto props = it.second;
|
||||
if (!props->control_mouse_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.button.x >= props->stream_render_rect_.x &&
|
||||
event.button.x <=
|
||||
props->stream_render_rect_.x + props->stream_render_rect_.w &&
|
||||
event.button.y >= props->stream_render_rect_.y &&
|
||||
event.button.y <=
|
||||
props->stream_render_rect_.y + props->stream_render_rect_.h) {
|
||||
controlled_remote_id_ = it.first;
|
||||
render_width = props->stream_render_rect_.w;
|
||||
render_height = props->stream_render_rect_.h;
|
||||
last_mouse_event.button.x = event.button.x;
|
||||
last_mouse_event.button.y = event.button.y;
|
||||
|
||||
remote_action.m.x =
|
||||
(float)(event.button.x - props->stream_render_rect_.x) / render_width;
|
||||
remote_action.m.y =
|
||||
(float)(event.button.y - props->stream_render_rect_.y) /
|
||||
render_height;
|
||||
|
||||
if (SDL_EVENT_MOUSE_BUTTON_DOWN == event.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (SDL_BUTTON_LEFT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::left_down;
|
||||
} else if (SDL_BUTTON_RIGHT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::right_down;
|
||||
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::middle_down;
|
||||
}
|
||||
} else if (SDL_EVENT_MOUSE_BUTTON_UP == event.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (SDL_BUTTON_LEFT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::left_up;
|
||||
} else if (SDL_BUTTON_RIGHT == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::right_up;
|
||||
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
|
||||
remote_action.m.flag = MouseFlag::middle_up;
|
||||
}
|
||||
} else if (SDL_EVENT_MOUSE_MOTION == event.type) {
|
||||
remote_action.type = ControlType::mouse;
|
||||
remote_action.m.flag = MouseFlag::move;
|
||||
}
|
||||
|
||||
if (props->control_bar_hovered_ || props->display_selectable_hovered_) {
|
||||
remote_action.m.flag = MouseFlag::move;
|
||||
}
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
} else if (SDL_EVENT_MOUSE_WHEEL == event.type &&
|
||||
last_mouse_event.button.x >= props->stream_render_rect_.x &&
|
||||
last_mouse_event.button.x <= props->stream_render_rect_.x +
|
||||
props->stream_render_rect_.w &&
|
||||
last_mouse_event.button.y >= props->stream_render_rect_.y &&
|
||||
last_mouse_event.button.y <= props->stream_render_rect_.y +
|
||||
props->stream_render_rect_.h) {
|
||||
int scroll_x = event.wheel.x;
|
||||
int scroll_y = event.wheel.y;
|
||||
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
|
||||
scroll_x = -scroll_x;
|
||||
scroll_y = -scroll_y;
|
||||
}
|
||||
|
||||
remote_action.type = ControlType::mouse;
|
||||
if (scroll_x == 0) {
|
||||
remote_action.m.flag = MouseFlag::wheel_vertical;
|
||||
remote_action.m.s = scroll_y;
|
||||
} else if (scroll_y == 0) {
|
||||
remote_action.m.flag = MouseFlag::wheel_horizontal;
|
||||
remote_action.m.s = scroll_x;
|
||||
}
|
||||
|
||||
render_width = props->stream_render_rect_.w;
|
||||
render_height = props->stream_render_rect_.h;
|
||||
remote_action.m.x =
|
||||
(float)(event.button.x - props->stream_render_rect_.x) / render_width;
|
||||
remote_action.m.y =
|
||||
(float)(event.button.y - props->stream_render_rect_.y) /
|
||||
render_height;
|
||||
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Render::SdlCaptureAudioIn(void* userdata, Uint8* stream, int len) {
|
||||
Render* render = (Render*)userdata;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (1) {
|
||||
for (auto it : render->client_properties_) {
|
||||
auto props = it.second;
|
||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
||||
SendAudioFrame(props->peer_, (const char*)stream, len,
|
||||
render->audio_label_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
memcpy(render->audio_buffer_, stream, len);
|
||||
render->audio_len_ = len;
|
||||
SDL_Delay(10);
|
||||
render->audio_buffer_fresh_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Render::SdlCaptureAudioOut([[maybe_unused]] void* userdata,
|
||||
[[maybe_unused]] Uint8* stream,
|
||||
[[maybe_unused]] int len) {
|
||||
// Render *render = (Render *)userdata;
|
||||
// for (auto it : render->client_properties_) {
|
||||
// auto props = it.second;
|
||||
// if (props->connection_status_ == SignalStatus::SignalConnected) {
|
||||
// SendAudioFrame(props->peer_, (const char *)stream, len);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!render->audio_buffer_fresh_) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// SDL_memset(stream, 0, len);
|
||||
|
||||
// if (render->audio_len_ == 0) {
|
||||
// return;
|
||||
// } else {
|
||||
// }
|
||||
|
||||
// len = (len > render->audio_len_ ? render->audio_len_ : len);
|
||||
// SDL_MixAudioFormat(stream, render->audio_buffer_, AUDIO_S16LSB, len,
|
||||
// SDL_MIX_MAXVOLUME);
|
||||
// render->audio_buffer_fresh_ = false;
|
||||
}
|
||||
|
||||
void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
if (render->client_properties_.find(remote_id) ==
|
||||
render->client_properties_.end()) {
|
||||
return;
|
||||
}
|
||||
SubStreamWindowProperties* props =
|
||||
render->client_properties_.find(remote_id)->second.get();
|
||||
|
||||
if (props->connection_established_) {
|
||||
if (!props->dst_buffer_) {
|
||||
props->dst_buffer_capacity_ = video_frame->size;
|
||||
props->dst_buffer_ = new unsigned char[video_frame->size];
|
||||
}
|
||||
|
||||
if (props->dst_buffer_capacity_ < video_frame->size) {
|
||||
delete props->dst_buffer_;
|
||||
props->dst_buffer_capacity_ = video_frame->size;
|
||||
props->dst_buffer_ = new unsigned char[video_frame->size];
|
||||
}
|
||||
|
||||
memcpy(props->dst_buffer_, video_frame->data, video_frame->size);
|
||||
bool need_to_update_render_rect = false;
|
||||
if (props->video_width_ != props->video_width_last_ ||
|
||||
props->video_height_ != props->video_height_last_) {
|
||||
need_to_update_render_rect = true;
|
||||
props->video_width_last_ = props->video_width_;
|
||||
props->video_height_last_ = props->video_height_;
|
||||
}
|
||||
props->video_width_ = video_frame->width;
|
||||
props->video_height_ = video_frame->height;
|
||||
props->video_size_ = video_frame->size;
|
||||
|
||||
if (need_to_update_render_rect) {
|
||||
render->UpdateRenderRect();
|
||||
}
|
||||
|
||||
SDL_Event event;
|
||||
event.type = render->STREAM_REFRESH_EVENT;
|
||||
event.user.data1 = props;
|
||||
SDL_PushEvent(&event);
|
||||
props->streaming_ = true;
|
||||
|
||||
if (props->net_traffic_stats_button_pressed_) {
|
||||
props->frame_count_++;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - props->last_time_)
|
||||
.count();
|
||||
|
||||
if (elapsed >= 1000) {
|
||||
props->fps_ = props->frame_count_ * 1000 / elapsed;
|
||||
props->frame_count_ = 0;
|
||||
props->last_time_ = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnReceiveAudioBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
render->audio_buffer_fresh_ = true;
|
||||
|
||||
if (render->output_stream_) {
|
||||
int pushed = SDL_PutAudioStreamData(
|
||||
render->output_stream_, (const Uint8*)data, static_cast<int>(size));
|
||||
if (pushed < 0) {
|
||||
LOG_ERROR("Failed to push audio data: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
||||
const char* user_id, size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteAction remote_action;
|
||||
memcpy(&remote_action, data, size);
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
if (render->client_properties_.find(remote_id) !=
|
||||
render->client_properties_.end()) {
|
||||
// local
|
||||
auto props = render->client_properties_.find(remote_id)->second;
|
||||
RemoteAction host_info;
|
||||
if (DeserializeRemoteAction(data, size, host_info)) {
|
||||
if (ControlType::host_infomation == host_info.type &&
|
||||
props->remote_host_name_.empty()) {
|
||||
props->remote_host_name_ =
|
||||
std::string(host_info.i.host_name, host_info.i.host_name_size);
|
||||
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
|
||||
|
||||
for (int i = 0; i < host_info.i.display_num; i++) {
|
||||
props->display_info_list_.push_back(DisplayInfo(
|
||||
std::string(host_info.i.display_list[i]), host_info.i.left[i],
|
||||
host_info.i.top[i], host_info.i.right[i], host_info.i.bottom[i]));
|
||||
LOG_INFO("Remote display [{}:{}], bound [({}, {}) ({}, {})]", i + 1,
|
||||
props->display_info_list_[i].name,
|
||||
props->display_info_list_[i].left,
|
||||
props->display_info_list_[i].top,
|
||||
props->display_info_list_[i].right,
|
||||
props->display_info_list_[i].bottom);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
props->remote_host_name_ = std::string(remote_action.i.host_name,
|
||||
remote_action.i.host_name_size);
|
||||
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
|
||||
LOG_ERROR("No remote display detected");
|
||||
}
|
||||
FreeRemoteAction(host_info);
|
||||
} else {
|
||||
// remote
|
||||
if (ControlType::mouse == remote_action.type && render->mouse_controller_) {
|
||||
render->mouse_controller_->SendMouseCommand(remote_action,
|
||||
render->selected_display_);
|
||||
} else if (ControlType::audio_capture == remote_action.type) {
|
||||
if (remote_action.a) {
|
||||
render->StartSpeakerCapturer();
|
||||
render->audio_capture_ = true;
|
||||
} else {
|
||||
render->StopSpeakerCapturer();
|
||||
render->audio_capture_ = false;
|
||||
}
|
||||
} else if (ControlType::keyboard == remote_action.type &&
|
||||
render->keyboard_capturer_) {
|
||||
render->keyboard_capturer_->SendKeyboardCommand(
|
||||
(int)remote_action.k.key_value,
|
||||
remote_action.k.flag == KeyFlag::key_down);
|
||||
} else if (ControlType::display_id == remote_action.type) {
|
||||
if (render->screen_capturer_) {
|
||||
render->selected_display_ = remote_action.d;
|
||||
render->screen_capturer_->SwitchTo(remote_action.d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
|
||||
size_t user_id_size, void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string client_id(user_id, user_id_size);
|
||||
if (client_id == render->client_id_) {
|
||||
render->signal_status_ = status;
|
||||
if (SignalStatus::SignalConnecting == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalConnected == status) {
|
||||
render->signal_connected_ = true;
|
||||
LOG_INFO("[{}] connected to signal server", client_id);
|
||||
} else if (SignalStatus::SignalFailed == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalClosed == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalReconnecting == status) {
|
||||
render->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalServerClosed == status) {
|
||||
render->signal_connected_ = false;
|
||||
}
|
||||
} else {
|
||||
if (client_id.rfind("C-", 0) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string remote_id(client_id.begin() + 2, client_id.end());
|
||||
if (render->client_properties_.find(remote_id) ==
|
||||
render->client_properties_.end()) {
|
||||
return;
|
||||
}
|
||||
auto props = render->client_properties_.find(remote_id)->second;
|
||||
props->signal_status_ = status;
|
||||
if (SignalStatus::SignalConnecting == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalConnected == status) {
|
||||
props->signal_connected_ = true;
|
||||
LOG_INFO("[{}] connected to signal server", remote_id);
|
||||
} else if (SignalStatus::SignalFailed == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalClosed == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalReconnecting == status) {
|
||||
props->signal_connected_ = false;
|
||||
} else if (SignalStatus::SignalServerClosed == status) {
|
||||
props->signal_connected_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||
const size_t user_id_size, void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) return;
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
auto it = render->client_properties_.find(remote_id);
|
||||
auto props = (it != render->client_properties_.end()) ? it->second : nullptr;
|
||||
|
||||
if (props) {
|
||||
render->is_client_mode_ = true;
|
||||
render->show_connection_status_window_ = true;
|
||||
props->connection_status_ = status;
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatus::Connected:
|
||||
if (!render->need_to_create_stream_window_ &&
|
||||
!render->client_properties_.empty()) {
|
||||
render->need_to_create_stream_window_ = true;
|
||||
}
|
||||
props->connection_established_ = true;
|
||||
props->stream_render_rect_ = {
|
||||
0, (int)render->title_bar_height_,
|
||||
(int)render->stream_window_width_,
|
||||
(int)(render->stream_window_height_ - render->title_bar_height_)};
|
||||
break;
|
||||
case ConnectionStatus::Disconnected:
|
||||
case ConnectionStatus::Failed:
|
||||
case ConnectionStatus::Closed:
|
||||
render->password_validating_time_ = 0;
|
||||
render->start_screen_capturer_ = false;
|
||||
render->start_mouse_controller_ = false;
|
||||
render->start_keyboard_capturer_ = false;
|
||||
render->control_mouse_ = false;
|
||||
props->connection_established_ = false;
|
||||
props->mouse_control_button_pressed_ = false;
|
||||
if (props->dst_buffer_) {
|
||||
memset(props->dst_buffer_, 0, props->dst_buffer_capacity_);
|
||||
SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_,
|
||||
props->texture_width_);
|
||||
}
|
||||
render->CleanSubStreamWindowProperties(props);
|
||||
break;
|
||||
case ConnectionStatus::IncorrectPassword:
|
||||
render->password_validating_ = false;
|
||||
render->password_validating_time_++;
|
||||
if (render->connect_button_pressed_) {
|
||||
render->connect_button_pressed_ = false;
|
||||
props->connection_established_ = false;
|
||||
render->connect_button_label_ =
|
||||
localization::connect[render->localization_language_index_];
|
||||
}
|
||||
break;
|
||||
case ConnectionStatus::NoSuchTransmissionId:
|
||||
if (render->connect_button_pressed_) {
|
||||
props->connection_established_ = false;
|
||||
render->connect_button_label_ =
|
||||
localization::connect[render->localization_language_index_];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
render->is_client_mode_ = false;
|
||||
render->show_connection_status_window_ = true;
|
||||
|
||||
switch (status) {
|
||||
case ConnectionStatus::Connected:
|
||||
render->need_to_send_host_info_ = true;
|
||||
render->start_screen_capturer_ = true;
|
||||
render->start_mouse_controller_ = true;
|
||||
break;
|
||||
case ConnectionStatus::Closed:
|
||||
render->start_screen_capturer_ = false;
|
||||
render->start_mouse_controller_ = false;
|
||||
render->start_keyboard_capturer_ = false;
|
||||
render->need_to_send_host_info_ = false;
|
||||
if (props) props->connection_established_ = false;
|
||||
if (render->audio_capture_) {
|
||||
render->StopSpeakerCapturer();
|
||||
render->audio_capture_ = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Render::NetStatusReport(const char* client_id, size_t client_id_size,
|
||||
TraversalMode mode,
|
||||
const XNetTrafficStats* net_traffic_stats,
|
||||
const char* user_id, const size_t user_id_size,
|
||||
void* user_data) {
|
||||
Render* render = (Render*)user_data;
|
||||
if (!render) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) {
|
||||
std::string id, password;
|
||||
const char* at_pos = strchr(client_id, '@');
|
||||
if (at_pos == nullptr) {
|
||||
id = client_id;
|
||||
password.clear();
|
||||
} else {
|
||||
id.assign(client_id, at_pos - client_id);
|
||||
password = at_pos + 1;
|
||||
}
|
||||
|
||||
memset(&render->client_id_, 0, sizeof(render->client_id_));
|
||||
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
|
||||
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
|
||||
|
||||
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
|
||||
strncpy(render->password_saved_, password.c_str(),
|
||||
sizeof(render->password_saved_) - 1);
|
||||
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
|
||||
|
||||
memset(&render->client_id_with_password_, 0,
|
||||
sizeof(render->client_id_with_password_));
|
||||
strncpy(render->client_id_with_password_, client_id,
|
||||
sizeof(render->client_id_with_password_) - 1);
|
||||
render->client_id_with_password_[sizeof(render->client_id_with_password_) -
|
||||
1] = '\0';
|
||||
|
||||
LOG_INFO("Use client id [{}] and save id into cache file", id);
|
||||
render->SaveSettingsIntoCacheFile();
|
||||
}
|
||||
|
||||
std::string remote_id(user_id, user_id_size);
|
||||
if (render->client_properties_.find(remote_id) ==
|
||||
render->client_properties_.end()) {
|
||||
return;
|
||||
}
|
||||
auto props = render->client_properties_.find(remote_id)->second;
|
||||
if (props->traversal_mode_ != mode) {
|
||||
props->traversal_mode_ = mode;
|
||||
LOG_INFO("Net mode: [{}]", int(props->traversal_mode_));
|
||||
}
|
||||
|
||||
if (!net_traffic_stats) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only display client side net status if connected to itself
|
||||
if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) {
|
||||
props->net_traffic_stats_ = *net_traffic_stats;
|
||||
}
|
||||
}
|
||||
326
src/gui/toolbars/control_bar.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int CountDigits(int number) {
|
||||
if (number == 0) return 1;
|
||||
return (int)std::floor(std::log10(std::abs(number))) + 1;
|
||||
}
|
||||
|
||||
int BitrateDisplay(int bitrate) {
|
||||
int num_of_digits = CountDigits(bitrate);
|
||||
if (num_of_digits <= 3) {
|
||||
ImGui::Text("%d bps", bitrate);
|
||||
} else if (num_of_digits > 3 && num_of_digits <= 6) {
|
||||
ImGui::Text("%d kbps", bitrate / 1000);
|
||||
} else {
|
||||
ImGui::Text("%.1f mbps", bitrate / 1000000.0f);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LossRateDisplay(float loss_rate) {
|
||||
if (loss_rate < 0.01f) {
|
||||
ImGui::Text("0%%");
|
||||
} else {
|
||||
ImGui::Text("%.0f%%", loss_rate * 100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
if (props->control_bar_expand_) {
|
||||
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
||||
? (props->control_window_width_ + 5.0f)
|
||||
: 38.0f);
|
||||
// mouse control button
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
if (props->is_control_bar_in_left_) {
|
||||
draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f),
|
||||
ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f +
|
||||
props->control_window_height_),
|
||||
IM_COL32(178, 178, 178, 255), 1.0f);
|
||||
}
|
||||
|
||||
std::string display = ICON_FA_DISPLAY;
|
||||
if (ImGui::Button(display.c_str(), ImVec2(25, 25))) {
|
||||
ImGui::OpenPopup("display");
|
||||
}
|
||||
|
||||
ImVec2 btn_min = ImGui::GetItemRectMin();
|
||||
ImVec2 btn_size_actual = ImGui::GetItemRectSize();
|
||||
|
||||
if (ImGui::BeginPopup("display")) {
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
for (int i = 0; i < props->display_info_list_.size(); i++) {
|
||||
if (ImGui::Selectable(props->display_info_list_[i].name.c_str())) {
|
||||
props->selected_display_ = i;
|
||||
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::display_id;
|
||||
remote_action.d = i;
|
||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
props->display_selectable_hovered_ = ImGui::IsWindowHovered();
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(0.6f);
|
||||
ImVec2 text_size = ImGui::CalcTextSize(
|
||||
std::to_string(props->selected_display_ + 1).c_str());
|
||||
ImVec2 text_pos =
|
||||
ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.5f,
|
||||
btn_min.y + (btn_size_actual.y - text_size.y) * 0.5f - 2.0f);
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
text_pos, IM_COL32(0, 0, 0, 255),
|
||||
std::to_string(props->selected_display_ + 1).c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::SameLine();
|
||||
float disable_mouse_x = ImGui::GetCursorScreenPos().x + 4.0f;
|
||||
float disable_mouse_y = ImGui::GetCursorScreenPos().y + 4.0f;
|
||||
std::string mouse = props->mouse_control_button_pressed_
|
||||
? ICON_FA_COMPUTER_MOUSE
|
||||
: ICON_FA_COMPUTER_MOUSE;
|
||||
if (ImGui::Button(mouse.c_str(), ImVec2(25, 25))) {
|
||||
if (props->connection_established_) {
|
||||
start_keyboard_capturer_ = !start_keyboard_capturer_;
|
||||
props->control_mouse_ = !props->control_mouse_;
|
||||
props->mouse_control_button_pressed_ =
|
||||
!props->mouse_control_button_pressed_;
|
||||
props->mouse_control_button_label_ =
|
||||
props->mouse_control_button_pressed_
|
||||
? localization::release_mouse[localization_language_index_]
|
||||
: localization::control_mouse[localization_language_index_];
|
||||
}
|
||||
}
|
||||
if (!props->mouse_control_button_pressed_) {
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_mouse_x, disable_mouse_y),
|
||||
ImVec2(disable_mouse_x + 16.0f, disable_mouse_y + 14.2f),
|
||||
IM_COL32(0, 0, 0, 255), 2.0f);
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_mouse_x - 1.2f, disable_mouse_y + 1.2f),
|
||||
ImVec2(disable_mouse_x + 15.3f, disable_mouse_y + 15.4f),
|
||||
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255)
|
||||
: IM_COL32(179, 213, 253, 255),
|
||||
2.0f);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// audio capture button
|
||||
float disable_audio_x = ImGui::GetCursorScreenPos().x + 4;
|
||||
float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f;
|
||||
// std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH
|
||||
// :
|
||||
// ICON_FA_VOLUME_XMARK;
|
||||
std::string audio = props->audio_capture_button_pressed_
|
||||
? ICON_FA_VOLUME_HIGH
|
||||
: ICON_FA_VOLUME_HIGH;
|
||||
if (ImGui::Button(audio.c_str(), ImVec2(25, 25))) {
|
||||
if (props->connection_established_) {
|
||||
props->audio_capture_button_pressed_ =
|
||||
!props->audio_capture_button_pressed_;
|
||||
props->audio_capture_button_label_ =
|
||||
props->audio_capture_button_pressed_
|
||||
? localization::audio_capture[localization_language_index_]
|
||||
: localization::mute[localization_language_index_];
|
||||
|
||||
RemoteAction remote_action;
|
||||
remote_action.type = ControlType::audio_capture;
|
||||
remote_action.a = props->audio_capture_button_pressed_;
|
||||
SendDataFrame(props->peer_, (const char*)&remote_action,
|
||||
sizeof(remote_action), props->data_label_.c_str());
|
||||
}
|
||||
}
|
||||
if (!props->audio_capture_button_pressed_) {
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_audio_x, disable_audio_y),
|
||||
ImVec2(disable_audio_x + 16.0f, disable_audio_y + 14.2f),
|
||||
IM_COL32(0, 0, 0, 255), 2.0f);
|
||||
draw_list->AddLine(
|
||||
ImVec2(disable_audio_x - 1.2f, disable_audio_y + 1.2f),
|
||||
ImVec2(disable_audio_x + 15.3f, disable_audio_y + 15.4f),
|
||||
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255)
|
||||
: IM_COL32(179, 213, 253, 255),
|
||||
2.0f);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// net traffic stats button
|
||||
bool button_color_style_pushed = false;
|
||||
if (props->net_traffic_stats_button_pressed_) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(66 / 255.0f, 150 / 255.0f,
|
||||
250 / 255.0f, 1.0f));
|
||||
button_color_style_pushed = true;
|
||||
}
|
||||
std::string net_traffic_stats = ICON_FA_SIGNAL;
|
||||
if (ImGui::Button(net_traffic_stats.c_str(), ImVec2(25, 25))) {
|
||||
props->net_traffic_stats_button_pressed_ =
|
||||
!props->net_traffic_stats_button_pressed_;
|
||||
props->control_window_height_is_changing_ = true;
|
||||
props->net_traffic_stats_button_pressed_time_ = ImGui::GetTime();
|
||||
props->net_traffic_stats_button_label_ =
|
||||
props->net_traffic_stats_button_pressed_
|
||||
? localization::hide_net_traffic_stats
|
||||
[localization_language_index_]
|
||||
: localization::show_net_traffic_stats
|
||||
[localization_language_index_];
|
||||
}
|
||||
if (button_color_style_pushed) {
|
||||
ImGui::PopStyleColor();
|
||||
button_color_style_pushed = false;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// fullscreen button
|
||||
std::string fullscreen =
|
||||
fullscreen_button_pressed_ ? ICON_FA_COMPRESS : ICON_FA_EXPAND;
|
||||
if (ImGui::Button(fullscreen.c_str(), ImVec2(25, 25))) {
|
||||
fullscreen_button_pressed_ = !fullscreen_button_pressed_;
|
||||
props->fullscreen_button_label_ =
|
||||
fullscreen_button_pressed_
|
||||
? localization::exit_fullscreen[localization_language_index_]
|
||||
: localization::fullscreen[localization_language_index_];
|
||||
|
||||
if (fullscreen_button_pressed_) {
|
||||
SDL_SetWindowFullscreen(stream_window_, true);
|
||||
} else {
|
||||
SDL_SetWindowFullscreen(stream_window_, false);
|
||||
}
|
||||
props->reset_control_bar_pos_ = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// close button
|
||||
std::string close_button = ICON_FA_XMARK;
|
||||
if (ImGui::Button(close_button.c_str(), ImVec2(25, 25))) {
|
||||
CleanupPeer(props);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (!props->is_control_bar_in_left_) {
|
||||
draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f),
|
||||
ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
|
||||
ImGui::GetCursorScreenPos().y - 7.0f +
|
||||
props->control_window_height_),
|
||||
IM_COL32(178, 178, 178, 255), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
||||
? (props->control_window_width_ * 2 - 20.0f)
|
||||
: 5.0f);
|
||||
|
||||
std::string control_bar =
|
||||
props->control_bar_expand_
|
||||
? (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_LEFT
|
||||
: ICON_FA_ANGLE_RIGHT)
|
||||
: (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT
|
||||
: ICON_FA_ANGLE_LEFT);
|
||||
if (ImGui::Button(control_bar.c_str(), ImVec2(15, 25))) {
|
||||
props->control_bar_expand_ = !props->control_bar_expand_;
|
||||
props->control_bar_button_pressed_time_ = ImGui::GetTime();
|
||||
props->control_window_width_is_changing_ = true;
|
||||
|
||||
if (!props->control_bar_expand_) {
|
||||
props->control_window_height_ = 40;
|
||||
props->net_traffic_stats_button_pressed_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) {
|
||||
NetTrafficStats(props);
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||
ImGui::SetCursorPos(ImVec2(props->is_control_bar_in_left_
|
||||
? (props->control_window_width_ + 5.0f)
|
||||
: 5.0f,
|
||||
40.0f));
|
||||
|
||||
if (ImGui::BeginTable("NetTrafficStats", 4, ImGuiTableFlags_BordersH,
|
||||
ImVec2(props->control_window_max_width_ - 10.0f,
|
||||
props->control_window_max_height_ - 60.0f))) {
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(" ");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", localization::in[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", localization::out[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::loss_rate[localization_language_index_].c_str());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::video[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.video_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.video_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.video_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::audio[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.audio_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.audio_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.audio_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", localization::data[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.data_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.data_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.data_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",
|
||||
localization::total[localization_language_index_].c_str());
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.total_inbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
BitrateDisplay((int)props->net_traffic_stats_.total_outbound_stats.bitrate);
|
||||
ImGui::TableNextColumn();
|
||||
LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("FPS");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", props->fps_);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
38
src/gui/toolbars/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;
|
||||
}
|
||||
174
src/gui/toolbars/title_bar.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#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();
|
||||
SelfHostedServerWindow();
|
||||
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))) {
|
||||
#if _WIN32
|
||||
if (enable_minimize_to_tray_) {
|
||||
tray_->MinimizeToTray();
|
||||
} else {
|
||||
#endif
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
#if _WIN32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
|
||||
xmark_pos_y - xmark_size / 2 + 0.75f),
|
||||
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;
|
||||
}
|
||||
112
src/gui/tray/win_tray.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "win_tray.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "localization.h"
|
||||
|
||||
// callback for the message-only window that handles tray icon messages
|
||||
static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
WinTray* tray =
|
||||
reinterpret_cast<WinTray*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
if (!tray) {
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
if (msg == WM_TRAY_CALLBACK) {
|
||||
MSG tmpMsg = {};
|
||||
tmpMsg.message = msg;
|
||||
tmpMsg.wParam = wParam;
|
||||
tmpMsg.lParam = lParam;
|
||||
tray->HandleTrayMessage(&tmpMsg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
WinTray::WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
|
||||
int language_index)
|
||||
: app_hwnd_(app_hwnd),
|
||||
icon_(icon),
|
||||
tip_(tooltip),
|
||||
hwnd_message_only_(nullptr),
|
||||
language_index_(language_index) {
|
||||
WNDCLASS wc = {};
|
||||
wc.lpfnWndProc = MsgWndProc;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = L"TrayMessageWindow";
|
||||
RegisterClass(&wc);
|
||||
|
||||
// create a message-only window to receive tray messages
|
||||
hwnd_message_only_ =
|
||||
CreateWindowEx(0, wc.lpszClassName, L"TrayMsg", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, nullptr, wc.hInstance, nullptr);
|
||||
|
||||
// store pointer to this WinTray instance in window data
|
||||
SetWindowLongPtr(hwnd_message_only_, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(this));
|
||||
|
||||
// initialize NOTIFYICONDATA structure
|
||||
ZeroMemory(&nid_, sizeof(nid_));
|
||||
nid_.cbSize = sizeof(nid_);
|
||||
nid_.hWnd = hwnd_message_only_;
|
||||
nid_.uID = 1;
|
||||
nid_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
nid_.uCallbackMessage = WM_TRAY_CALLBACK;
|
||||
nid_.hIcon = icon_;
|
||||
wcsncpy_s(nid_.szTip, tip_.c_str(), _TRUNCATE);
|
||||
}
|
||||
|
||||
WinTray::~WinTray() {
|
||||
RemoveTrayIcon();
|
||||
if (hwnd_message_only_) DestroyWindow(hwnd_message_only_);
|
||||
}
|
||||
|
||||
void WinTray::MinimizeToTray() {
|
||||
Shell_NotifyIcon(NIM_ADD, &nid_);
|
||||
// hide application window
|
||||
ShowWindow(app_hwnd_, SW_HIDE);
|
||||
}
|
||||
|
||||
void WinTray::RemoveTrayIcon() { Shell_NotifyIcon(NIM_DELETE, &nid_); }
|
||||
|
||||
bool WinTray::HandleTrayMessage(MSG* msg) {
|
||||
if (!msg || msg->message != WM_TRAY_CALLBACK) return false;
|
||||
|
||||
switch (LOWORD(msg->lParam)) {
|
||||
case WM_LBUTTONDBLCLK:
|
||||
case WM_LBUTTONUP: {
|
||||
ShowWindow(app_hwnd_, SW_SHOW);
|
||||
SetForegroundWindow(app_hwnd_);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_RBUTTONUP: {
|
||||
POINT pt;
|
||||
GetCursorPos(&pt);
|
||||
HMENU menu = CreatePopupMenu();
|
||||
AppendMenuW(menu, MF_STRING, 1001,
|
||||
localization::exit_program[language_index_]);
|
||||
|
||||
SetForegroundWindow(hwnd_message_only_);
|
||||
int cmd =
|
||||
TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_LEFTALIGN,
|
||||
pt.x, pt.y, 0, hwnd_message_only_, nullptr);
|
||||
DestroyMenu(menu);
|
||||
|
||||
// handle menu command
|
||||
if (cmd == 1001) {
|
||||
// exit application
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
} else if (cmd == 1002) {
|
||||
ShowWindow(app_hwnd_, SW_SHOW); // show main window
|
||||
SetForegroundWindow(app_hwnd_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
36
src/gui/tray/win_tray.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-10-22
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _WIN_TRAY_H_
|
||||
#define _WIN_TRAY_H_
|
||||
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define WM_TRAY_CALLBACK (WM_USER + 1)
|
||||
|
||||
class WinTray {
|
||||
public:
|
||||
WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
|
||||
int language_index);
|
||||
~WinTray();
|
||||
|
||||
void MinimizeToTray();
|
||||
void RemoveTrayIcon();
|
||||
bool HandleTrayMessage(MSG* msg);
|
||||
|
||||
private:
|
||||
HWND app_hwnd_;
|
||||
HWND hwnd_message_only_;
|
||||
HICON icon_;
|
||||
std::wstring tip_;
|
||||
int language_index_;
|
||||
NOTIFYICONDATA nid_;
|
||||
};
|
||||
|
||||
#endif
|
||||
61
src/gui/windows/about_window.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "layout.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 CROSSDESK_VERSION
|
||||
version = CROSSDESK_VERSION;
|
||||
#else
|
||||
version = "Unknown";
|
||||
#endif
|
||||
|
||||
std::string text = localization::version[localization_language_index_] +
|
||||
": CrossDesk v" + version;
|
||||
ImGui::Text("%s", text.c_str());
|
||||
ImGui::Text("");
|
||||
|
||||
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
|
||||
std::string license_text = "Licensed under GNU LGPL v3.";
|
||||
ImGui::Text("%s", copyright_text.c_str());
|
||||
ImGui::Text("%s", license_text.c_str());
|
||||
|
||||
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
|
||||
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
|
||||
// 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;
|
||||
}
|
||||
171
src/gui/windows/connection_status_window.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
bool Render::ConnectionStatusWindow(
|
||||
std::shared_ptr<SubStreamWindowProperties> &props) {
|
||||
const ImGuiViewport *viewport = ImGui::GetMainViewport();
|
||||
bool ret_flag = false;
|
||||
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
connection_status_window_width_) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
connection_status_window_height_) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_,
|
||||
connection_status_window_height_));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
|
||||
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
std::string text;
|
||||
|
||||
if (ConnectionStatus::Connecting == props->connection_status_) {
|
||||
text = localization::p2p_connecting[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
} else if (ConnectionStatus::Connected == props->connection_status_) {
|
||||
text = localization::p2p_connected[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::Disconnected == props->connection_status_) {
|
||||
text = localization::p2p_disconnected[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::Failed == props->connection_status_) {
|
||||
text = localization::p2p_failed[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::Closed == props->connection_status_) {
|
||||
text = localization::p2p_closed[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
show_connection_status_window_ = false;
|
||||
}
|
||||
} else if (ConnectionStatus::IncorrectPassword == props->connection_status_) {
|
||||
if (!password_validating_) {
|
||||
if (password_validating_time_ == 1) {
|
||||
text = localization::input_password[localization_language_index_];
|
||||
} else {
|
||||
text = localization::reinput_password[localization_language_index_];
|
||||
}
|
||||
|
||||
auto window_width = ImGui::GetWindowSize().x;
|
||||
auto window_height = ImGui::GetWindowSize().y;
|
||||
ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f);
|
||||
ImGui::SetCursorPosY(window_height * 0.4f);
|
||||
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
|
||||
if (focus_on_input_widget_) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
focus_on_input_widget_ = false;
|
||||
}
|
||||
|
||||
ImGui::InputText("##password", props->remote_password_,
|
||||
IM_ARRAYSIZE(props->remote_password_),
|
||||
ImGuiInputTextFlags_CharsNoBlank);
|
||||
|
||||
ImGui::SetWindowFontScale(0.4f);
|
||||
|
||||
ImVec2 text_size = ImGui::CalcTextSize(
|
||||
localization::remember_password[localization_language_index_]
|
||||
.c_str());
|
||||
ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f);
|
||||
ImGui::Checkbox(
|
||||
localization::remember_password[localization_language_index_].c_str(),
|
||||
&(props->remember_password_));
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SetCursorPosX(window_width * 0.315f);
|
||||
ImGui::SetCursorPosY(window_height * 0.75f);
|
||||
// ok
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
|
||||
show_connection_status_window_ = true;
|
||||
password_validating_ = true;
|
||||
props->rejoin_ = true;
|
||||
need_to_rejoin_ = true;
|
||||
focus_on_input_widget_ = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
memset(props->remote_password_, 0, sizeof(props->remote_password_));
|
||||
show_connection_status_window_ = false;
|
||||
focus_on_input_widget_ = true;
|
||||
}
|
||||
} else if (password_validating_) {
|
||||
text = localization::validate_password[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
}
|
||||
} else if (ConnectionStatus::NoSuchTransmissionId ==
|
||||
props->connection_status_) {
|
||||
text = localization::no_such_id[localization_language_index_];
|
||||
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
|
||||
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
|
||||
// ok
|
||||
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
|
||||
show_connection_status_window_ = false;
|
||||
re_enter_remote_id_ = true;
|
||||
DestroyPeer(&props->peer_);
|
||||
ret_flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto window_width = ImGui::GetWindowSize().x;
|
||||
auto window_height = ImGui::GetWindowSize().y;
|
||||
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
|
||||
ImGui::SetCursorPosY(window_height * 0.2f);
|
||||
ImGui::Text("%s", text.c_str());
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return ret_flag;
|
||||
}
|
||||
223
src/gui/windows/control_window.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) {
|
||||
double time_duration =
|
||||
ImGui::GetTime() - props->control_bar_button_pressed_time_;
|
||||
if (props->control_window_width_is_changing_) {
|
||||
if (props->control_bar_expand_) {
|
||||
props->control_window_width_ =
|
||||
(float)(props->control_window_min_width_ +
|
||||
(props->control_window_max_width_ -
|
||||
props->control_window_min_width_) *
|
||||
4 * time_duration);
|
||||
} else {
|
||||
props->control_window_width_ =
|
||||
(float)(props->control_window_max_width_ -
|
||||
(props->control_window_max_width_ -
|
||||
props->control_window_min_width_) *
|
||||
4 * time_duration);
|
||||
}
|
||||
}
|
||||
|
||||
time_duration =
|
||||
ImGui::GetTime() - props->net_traffic_stats_button_pressed_time_;
|
||||
if (props->control_window_height_is_changing_) {
|
||||
if (props->control_bar_expand_ &&
|
||||
props->net_traffic_stats_button_pressed_) {
|
||||
props->control_window_height_ =
|
||||
(float)(props->control_window_min_height_ +
|
||||
(props->control_window_max_height_ -
|
||||
props->control_window_min_height_) *
|
||||
4 * time_duration);
|
||||
} else if (props->control_bar_expand_ &&
|
||||
!props->net_traffic_stats_button_pressed_) {
|
||||
props->control_window_height_ =
|
||||
(float)(props->control_window_max_height_ -
|
||||
(props->control_window_max_height_ -
|
||||
props->control_window_min_height_) *
|
||||
4 * time_duration);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 1));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(props->control_window_width_, props->control_window_height_),
|
||||
ImGuiCond_Always);
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_ + 1), ImGuiCond_Once);
|
||||
|
||||
float pos_x = 0;
|
||||
float pos_y = 0;
|
||||
float y_boundary = fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1);
|
||||
|
||||
if (props->reset_control_bar_pos_) {
|
||||
float new_cursor_pos_x = 0;
|
||||
float new_cursor_pos_y = 0;
|
||||
|
||||
// set control window pos
|
||||
if (props->control_window_pos_.y + props->control_window_height_ >
|
||||
stream_window_height_) {
|
||||
pos_y = stream_window_height_ - props->control_window_height_;
|
||||
} else if (props->control_window_pos_.y < y_boundary) {
|
||||
pos_y = y_boundary;
|
||||
} else {
|
||||
pos_y = props->control_window_pos_.y;
|
||||
}
|
||||
|
||||
if (props->is_control_bar_in_left_) {
|
||||
pos_x = 0;
|
||||
} else {
|
||||
pos_x = stream_window_width_ - props->control_window_width_;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
|
||||
|
||||
if (0 != props->mouse_diff_control_bar_pos_x_ &&
|
||||
0 != props->mouse_diff_control_bar_pos_y_) {
|
||||
// set cursor pos
|
||||
new_cursor_pos_x = pos_x + props->mouse_diff_control_bar_pos_x_;
|
||||
new_cursor_pos_y = pos_y + props->mouse_diff_control_bar_pos_y_;
|
||||
|
||||
SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x,
|
||||
(int)new_cursor_pos_y);
|
||||
}
|
||||
props->reset_control_bar_pos_ = false;
|
||||
} else if (!props->reset_control_bar_pos_ &&
|
||||
ImGui::IsMouseReleased(ImGuiMouseButton_Left) ||
|
||||
props->control_window_width_is_changing_) {
|
||||
if (props->control_window_pos_.x <= stream_window_width_ / 2) {
|
||||
if (props->control_window_pos_.y + props->control_window_height_ >
|
||||
stream_window_height_) {
|
||||
pos_y = stream_window_height_ - props->control_window_height_;
|
||||
} else {
|
||||
pos_y = props->control_window_pos_.y;
|
||||
}
|
||||
|
||||
if (props->control_bar_expand_) {
|
||||
if (props->control_window_width_ >= props->control_window_max_width_) {
|
||||
props->control_window_width_ = props->control_window_max_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
}
|
||||
} else {
|
||||
if (props->control_window_width_ <= props->control_window_min_width_) {
|
||||
props->control_window_width_ = props->control_window_min_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
}
|
||||
}
|
||||
props->is_control_bar_in_left_ = true;
|
||||
} else if (props->control_window_pos_.x > stream_window_width_ / 2) {
|
||||
pos_x = 0;
|
||||
pos_y =
|
||||
(props->control_window_pos_.y >= y_boundary &&
|
||||
props->control_window_pos_.y <=
|
||||
stream_window_height_ - props->control_window_height_)
|
||||
? props->control_window_pos_.y
|
||||
: (props->control_window_pos_.y < (fullscreen_button_pressed_
|
||||
? 0
|
||||
: (title_bar_height_ + 1))
|
||||
? (fullscreen_button_pressed_ ? 0
|
||||
: (title_bar_height_ + 1))
|
||||
: (stream_window_height_ - props->control_window_height_));
|
||||
|
||||
if (props->control_bar_expand_) {
|
||||
if (props->control_window_width_ >= props->control_window_max_width_) {
|
||||
props->control_window_width_ = props->control_window_max_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
pos_x = stream_window_width_ - props->control_window_max_width_;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
pos_x = stream_window_width_ - props->control_window_width_;
|
||||
}
|
||||
} else {
|
||||
if (props->control_window_width_ <= props->control_window_min_width_) {
|
||||
props->control_window_width_ = props->control_window_min_width_;
|
||||
props->control_window_width_is_changing_ = false;
|
||||
pos_x = stream_window_width_ - props->control_window_min_width_;
|
||||
} else {
|
||||
props->control_window_width_is_changing_ = true;
|
||||
pos_x = stream_window_width_ - props->control_window_width_;
|
||||
}
|
||||
}
|
||||
props->is_control_bar_in_left_ = false;
|
||||
}
|
||||
|
||||
if (props->control_window_pos_.y + props->control_window_height_ >
|
||||
stream_window_height_) {
|
||||
pos_y = stream_window_height_ - props->control_window_height_;
|
||||
} else if (props->control_window_pos_.y < y_boundary) {
|
||||
pos_y = y_boundary;
|
||||
}
|
||||
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
|
||||
}
|
||||
|
||||
if (props->control_bar_expand_ && props->control_window_height_is_changing_) {
|
||||
if (props->net_traffic_stats_button_pressed_) {
|
||||
if (props->control_window_height_ >= props->control_window_max_height_) {
|
||||
props->control_window_height_ = props->control_window_max_height_;
|
||||
props->control_window_height_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_height_is_changing_ = true;
|
||||
}
|
||||
} else {
|
||||
if (props->control_window_height_ <= props->control_window_min_height_) {
|
||||
props->control_window_height_ = props->control_window_min_height_;
|
||||
props->control_window_height_is_changing_ = false;
|
||||
} else {
|
||||
props->control_window_height_is_changing_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string control_window_title = props->remote_id_ + "ControlWindow";
|
||||
ImGui::Begin(control_window_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
props->control_window_pos_ = ImGui::GetWindowPos();
|
||||
SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_);
|
||||
props->mouse_diff_control_bar_pos_x_ =
|
||||
props->mouse_pos_x_ - props->control_window_pos_.x;
|
||||
props->mouse_diff_control_bar_pos_y_ =
|
||||
props->mouse_pos_y_ - props->control_window_pos_.y;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
static bool a, b, c, d, e;
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(props->is_control_bar_in_left_
|
||||
? props->control_window_pos_.x - props->control_window_width_
|
||||
: props->control_window_pos_.x,
|
||||
props->control_window_pos_.y),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
|
||||
std::string control_child_window_title =
|
||||
props->remote_id_ + "ControlChildWindow";
|
||||
ImGui::BeginChild(
|
||||
control_child_window_title.c_str(),
|
||||
ImVec2(props->control_window_width_ * 2, props->control_window_height_),
|
||||
ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ControlBar(props);
|
||||
props->control_bar_hovered_ = ImGui::IsWindowHovered();
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(4);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
return 0;
|
||||
}
|
||||
393
src/gui/windows/main_settings_window.cpp
Normal file
@@ -0,0 +1,393 @@
|
||||
#include "layout.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
|
||||
{
|
||||
static int settings_items_padding = 30;
|
||||
int settings_items_offset = 0;
|
||||
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::Begin(localization::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()};
|
||||
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s", localization::language[localization_language_index_].c_str());
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
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()};
|
||||
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
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(settings_items_offset);
|
||||
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_frame_rate_items[] = {"30 fps", "60 fps"};
|
||||
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text("%s",
|
||||
localization::video_frame_rate[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::Combo("##video_frame_rate", &video_frame_rate_button_value_,
|
||||
video_frame_rate_items,
|
||||
IM_ARRAYSIZE(video_frame_rate_items));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
const char* video_encode_format_items[] = {
|
||||
localization::h264[localization_language_index_].c_str(),
|
||||
localization::av1[localization_language_index_].c_str()};
|
||||
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::video_encode_format[localization_language_index_]
|
||||
.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(settings_items_offset);
|
||||
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();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text("%s", localization::enable_hardware_video_codec
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
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(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_hardware_video_codec",
|
||||
&enable_hardware_video_codec_);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
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(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_turn", &enable_turn_);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::enable_srtp[localization_language_index_].c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_srtp", &enable_srtp_);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 1);
|
||||
|
||||
if (ImGui::Button(localization::self_hosted_server_config
|
||||
[localization_language_index_]
|
||||
.c_str())) {
|
||||
show_self_hosted_server_config_window_ = true;
|
||||
}
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_self_hosted_server",
|
||||
&enable_self_hosted_server_);
|
||||
}
|
||||
#if _WIN32
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 4);
|
||||
|
||||
ImGui::Text("%s",
|
||||
localization::minimize_to_tray[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::Checkbox("##enable_minimize_to_tray_",
|
||||
&enable_minimize_to_tray_);
|
||||
}
|
||||
#endif
|
||||
if (stream_window_inited_) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN);
|
||||
}
|
||||
|
||||
settings_items_offset += settings_items_padding + 10;
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// OK
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = false;
|
||||
show_self_hosted_server_config_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::H264);
|
||||
} else if (video_encode_format_button_value_ == 1) {
|
||||
config_center_->SetVideoEncodeFormat(
|
||||
ConfigCenter::VIDEO_ENCODE_FORMAT::AV1);
|
||||
}
|
||||
video_encode_format_button_value_last_ =
|
||||
video_encode_format_button_value_;
|
||||
|
||||
// Hardware video codec
|
||||
if (enable_hardware_video_codec_) {
|
||||
config_center_->SetHardwareVideoCodec(true);
|
||||
} 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_;
|
||||
|
||||
// SRTP
|
||||
if (enable_srtp_) {
|
||||
config_center_->SetSrtp(true);
|
||||
} else {
|
||||
config_center_->SetSrtp(false);
|
||||
}
|
||||
enable_srtp_last_ = enable_srtp_;
|
||||
|
||||
if (enable_self_hosted_server_) {
|
||||
config_center_->SetSelfHosted(true);
|
||||
} else {
|
||||
config_center_->SetSelfHosted(false);
|
||||
}
|
||||
|
||||
settings_window_pos_reset_ = true;
|
||||
|
||||
// Recreate peer instance
|
||||
LoadSettingsFromCacheFile();
|
||||
|
||||
// Recreate peer instance
|
||||
if (!stream_window_inited_) {
|
||||
LOG_INFO("Recreate peer instance");
|
||||
CleanupPeers();
|
||||
CreateConnectionPeer();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// Cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str())) {
|
||||
show_settings_window_ = false;
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
|
||||
if (language_button_value_ != language_button_value_last_) {
|
||||
language_button_value_ = language_button_value_last_;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
49
src/gui/windows/main_window.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#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();
|
||||
|
||||
if (show_connection_status_window_) {
|
||||
for (auto it = client_properties_.begin();
|
||||
it != client_properties_.end();) {
|
||||
auto& props = it->second;
|
||||
if (focused_remote_id_ == props->remote_id_) {
|
||||
if (ConnectionStatusWindow(props)) {
|
||||
it = client_properties_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
321
src/gui/windows/server_settings_window.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "layout.h"
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
std::vector<std::string> GetRootEntries() {
|
||||
std::vector<std::string> roots;
|
||||
#ifdef _WIN32
|
||||
DWORD mask = GetLogicalDrives();
|
||||
for (char letter = 'A'; letter <= 'Z'; ++letter) {
|
||||
if (mask & 1) {
|
||||
roots.push_back(std::string(1, letter) + ":\\");
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
#else
|
||||
roots.push_back("/");
|
||||
#endif
|
||||
return roots;
|
||||
}
|
||||
|
||||
int Render::ShowSimpleFileBrowser() {
|
||||
std::string display_text;
|
||||
|
||||
if (selected_current_file_path_.empty()) {
|
||||
selected_current_file_path_ = std::filesystem::current_path().string();
|
||||
}
|
||||
|
||||
if (!tls_cert_path_self_.empty()) {
|
||||
display_text =
|
||||
std::filesystem::path(tls_cert_path_self_).filename().string();
|
||||
} else if (selected_current_file_path_ != "Root") {
|
||||
display_text =
|
||||
std::filesystem::path(selected_current_file_path_).filename().string();
|
||||
if (display_text.empty()) {
|
||||
display_text = selected_current_file_path_;
|
||||
}
|
||||
}
|
||||
|
||||
if (display_text.empty()) {
|
||||
display_text =
|
||||
localization::select_a_file[localization_language_index_].c_str();
|
||||
}
|
||||
|
||||
if (show_file_browser_) {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_AutoClosePopups, false);
|
||||
|
||||
float fixed_width = 130.0f;
|
||||
ImGui::SetNextItemWidth(fixed_width);
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(fixed_width, 0),
|
||||
ImVec2(fixed_width, 100.0f));
|
||||
|
||||
if (ImGui::BeginCombo("##select_a_file", display_text.c_str(), 0)) {
|
||||
bool file_selected = false;
|
||||
|
||||
auto roots = GetRootEntries();
|
||||
if (selected_current_file_path_ == "Root" ||
|
||||
!std::filesystem::exists(selected_current_file_path_) ||
|
||||
!std::filesystem::is_directory(selected_current_file_path_)) {
|
||||
for (const auto& root : roots) {
|
||||
if (ImGui::Selectable(root.c_str())) {
|
||||
selected_current_file_path_ = root;
|
||||
tls_cert_path_self_.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::filesystem::path p(selected_current_file_path_);
|
||||
|
||||
if (ImGui::Selectable("..")) {
|
||||
if (std::find(roots.begin(), roots.end(),
|
||||
selected_current_file_path_) != roots.end()) {
|
||||
selected_current_file_path_ = "Root";
|
||||
} else if (p.has_parent_path()) {
|
||||
selected_current_file_path_ = p.parent_path().string();
|
||||
} else {
|
||||
selected_current_file_path_ = "Root";
|
||||
}
|
||||
tls_cert_path_self_.clear();
|
||||
}
|
||||
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(
|
||||
selected_current_file_path_)) {
|
||||
std::string name = entry.path().filename().string();
|
||||
if (entry.is_directory()) {
|
||||
if (ImGui::Selectable(name.c_str())) {
|
||||
selected_current_file_path_ = entry.path().string();
|
||||
tls_cert_path_self_.clear();
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Selectable(name.c_str())) {
|
||||
tls_cert_path_self_ = entry.path().string();
|
||||
file_selected = true;
|
||||
show_file_browser_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::PopItemFlag();
|
||||
} else {
|
||||
show_file_browser_ = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Render::SelfHostedServerWindow() {
|
||||
if (show_self_hosted_server_config_window_) {
|
||||
if (self_hosted_server_config_window_pos_reset_) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN,
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN));
|
||||
} else {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN) /
|
||||
2,
|
||||
(viewport->WorkSize.y - viewport->WorkPos.y -
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN) /
|
||||
2));
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN,
|
||||
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN));
|
||||
}
|
||||
|
||||
self_hosted_server_config_window_pos_reset_ = false;
|
||||
}
|
||||
|
||||
// Settings
|
||||
{
|
||||
static int settings_items_padding = 30;
|
||||
int settings_items_offset = 0;
|
||||
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
||||
|
||||
ImGui::Begin(localization::self_hosted_server_settings
|
||||
[localization_language_index_]
|
||||
.c_str(),
|
||||
nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text("%s", localization::self_hosted_server_address
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::InputText("##signal_server_ip_self_", signal_server_ip_self_,
|
||||
IM_ARRAYSIZE(signal_server_ip_self_),
|
||||
ImGuiInputTextFlags_AlwaysOverwrite);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text(
|
||||
"%s",
|
||||
localization::self_hosted_server_port[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::InputText("##signal_server_port_self_", signal_server_port_self_,
|
||||
IM_ARRAYSIZE(signal_server_port_self_));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text("%s", localization::self_hosted_server_coturn_server_port
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ImGui::InputText("##coturn_server_port_self_", coturn_server_port_self_,
|
||||
IM_ARRAYSIZE(coturn_server_port_self_));
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
{
|
||||
settings_items_offset += settings_items_padding;
|
||||
ImGui::SetCursorPosY(settings_items_offset + 2);
|
||||
ImGui::Text("%s", localization::self_hosted_server_certificate_path
|
||||
[localization_language_index_]
|
||||
.c_str());
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
|
||||
}
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
|
||||
|
||||
ShowSimpleFileBrowser();
|
||||
}
|
||||
|
||||
if (stream_window_inited_) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN);
|
||||
} else {
|
||||
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN);
|
||||
}
|
||||
|
||||
settings_items_offset += settings_items_padding + 10;
|
||||
ImGui::SetCursorPosY(settings_items_offset);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// OK
|
||||
if (ImGui::Button(
|
||||
localization::ok[localization_language_index_].c_str())) {
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
|
||||
config_center_->SetServerHost(signal_server_ip_self_);
|
||||
config_center_->SetServerPort(atoi(signal_server_port_self_));
|
||||
config_center_->SetCoturnServerPort(atoi(coturn_server_port_self_));
|
||||
config_center_->SetCertFilePath(tls_cert_path_self_);
|
||||
strncpy(signal_server_ip_, signal_server_ip_self_,
|
||||
sizeof(signal_server_ip_) - 1);
|
||||
signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0';
|
||||
strncpy(signal_server_port_, signal_server_port_self_,
|
||||
sizeof(signal_server_port_) - 1);
|
||||
signal_server_port_[sizeof(signal_server_port_) - 1] = '\0';
|
||||
strncpy(coturn_server_port_, coturn_server_port_self_,
|
||||
sizeof(coturn_server_port_) - 1);
|
||||
coturn_server_port_[sizeof(coturn_server_port_) - 1] = '\0';
|
||||
strncpy(cert_file_path_, tls_cert_path_self_.c_str(),
|
||||
sizeof(cert_file_path_) - 1);
|
||||
cert_file_path_[sizeof(cert_file_path_) - 1] = '\0';
|
||||
|
||||
self_hosted_server_config_window_pos_reset_ = true;
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
// Cancel
|
||||
if (ImGui::Button(
|
||||
localization::cancel[localization_language_index_].c_str())) {
|
||||
show_self_hosted_server_config_window_ = false;
|
||||
self_hosted_server_config_window_pos_reset_ = true;
|
||||
|
||||
strncpy(signal_server_ip_self_, signal_server_ip_,
|
||||
sizeof(signal_server_ip_self_) - 1);
|
||||
signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
|
||||
strncpy(signal_server_port_self_, signal_server_port_,
|
||||
sizeof(signal_server_port_self_) - 1);
|
||||
signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
|
||||
config_center_->SetServerHost(signal_server_ip_self_);
|
||||
config_center_->SetServerPort(atoi(signal_server_port_self_));
|
||||
tls_cert_path_self_.clear();
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::SetWindowFontScale(0.5f);
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
202
src/gui/windows/stream_window.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "localization.h"
|
||||
#include "rd_log.h"
|
||||
#include "render.h"
|
||||
|
||||
void Render::DrawConnectionStatusText(
|
||||
std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||
std::string text;
|
||||
switch (props->connection_status_) {
|
||||
case ConnectionStatus::Disconnected:
|
||||
text = localization::p2p_disconnected[localization_language_index_];
|
||||
break;
|
||||
case ConnectionStatus::Failed:
|
||||
text = localization::p2p_failed[localization_language_index_];
|
||||
break;
|
||||
case ConnectionStatus::Closed:
|
||||
text = localization::p2p_closed[localization_language_index_];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!text.empty()) {
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2((size.x - text_size.x) * 0.5f,
|
||||
(size.y - text_size.y - title_bar_height_) * 0.5f));
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Render::CloseTab(decltype(client_properties_)::iterator& it) {
|
||||
CleanupPeer(it->second);
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
}
|
||||
|
||||
int Render::StreamWindow() {
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
|
||||
ImGui::Begin("VideoBg", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoDocking);
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGuiWindowFlags stream_window_flag =
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove;
|
||||
|
||||
if (!fullscreen_button_pressed_) {
|
||||
ImGui::SetNextWindowPos(ImVec2(20, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(0, 20), ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 8.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
|
||||
ImGui::Begin("TabBar", nullptr,
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoDocking);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
if (ImGui::BeginTabBar("StreamTabBar",
|
||||
ImGuiTabBarFlags_Reorderable |
|
||||
ImGuiTabBarFlags_AutoSelectNewTabs)) {
|
||||
is_tab_bar_hovered_ = ImGui::IsWindowHovered();
|
||||
|
||||
for (auto it = client_properties_.begin();
|
||||
it != client_properties_.end();) {
|
||||
auto& props = it->second;
|
||||
if (!props->tab_opened_) {
|
||||
CloseTab(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::SetWindowFontScale(0.6f);
|
||||
std::string tab_label =
|
||||
enable_srtp_
|
||||
? std::string(ICON_FA_SHIELD_HALVED) + " " + props->remote_id_
|
||||
: props->remote_id_;
|
||||
if (ImGui::BeginTabItem(tab_label.c_str(), &props->tab_opened_)) {
|
||||
props->tab_selected_ = true;
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(stream_window_width_, stream_window_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
|
||||
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImVec2 pos = ImGui::GetWindowPos();
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
props->render_window_x_ = pos.x;
|
||||
props->render_window_y_ = pos.y;
|
||||
props->render_window_width_ = size.x;
|
||||
props->render_window_height_ = size.y;
|
||||
UpdateRenderRect();
|
||||
|
||||
ControlWindow(props);
|
||||
|
||||
focused_remote_id_ = props->remote_id_;
|
||||
|
||||
if (!props->peer_) {
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
} else {
|
||||
DrawConnectionStatusText(props);
|
||||
++it;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::EndTabItem();
|
||||
} else {
|
||||
props->tab_selected_ = false;
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::End(); // End TabBar
|
||||
} else {
|
||||
for (auto it = client_properties_.begin();
|
||||
it != client_properties_.end();) {
|
||||
auto& props = it->second;
|
||||
if (!props->tab_opened_) {
|
||||
CloseTab(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (props->tab_selected_) {
|
||||
ImGui::SetNextWindowSize(
|
||||
ImVec2(stream_window_width_, stream_window_height_),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
|
||||
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImVec2 pos = ImGui::GetWindowPos();
|
||||
ImVec2 size = ImGui::GetWindowSize();
|
||||
props->render_window_x_ = pos.x;
|
||||
props->render_window_y_ = pos.y;
|
||||
props->render_window_width_ = size.x;
|
||||
props->render_window_height_ = size.y;
|
||||
UpdateRenderRect();
|
||||
|
||||
ControlWindow(props);
|
||||
ImGui::End();
|
||||
|
||||
if (!props->peer_) {
|
||||
fullscreen_button_pressed_ = false;
|
||||
SDL_SetWindowFullscreen(stream_window_, false);
|
||||
it = client_properties_.erase(it);
|
||||
if (client_properties_.empty()) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
} else {
|
||||
DrawConnectionStatusText(props);
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRenderRect();
|
||||
ImGui::End(); // End VideoBg
|
||||
|
||||
return 0;
|
||||
}
|
||||
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
@@ -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
@@ -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
@@ -0,0 +1,91 @@
|
||||
#include "path_manager.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {}
|
||||
|
||||
std::filesystem::path PathManager::GetConfigPath() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_;
|
||||
#elif __APPLE__
|
||||
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_;
|
||||
#else
|
||||
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetCachePath() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache";
|
||||
#elif __APPLE__
|
||||
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
|
||||
#else
|
||||
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetLogPath() {
|
||||
#ifdef _WIN32
|
||||
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs";
|
||||
#elif __APPLE__
|
||||
return GetHome() + "/Library/Logs/" + app_name_;
|
||||
#else
|
||||
return GetCachePath() / app_name_ / "logs";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetCertPath() {
|
||||
#ifdef _WIN32
|
||||
// %APPDATA%\AppName\Certs
|
||||
return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_ / "certs";
|
||||
#elif __APPLE__
|
||||
// $HOME/Library/Application Support/AppName/certs
|
||||
return GetHome() + "/Library/Application Support/" + app_name_ + "/certs";
|
||||
#else
|
||||
// $XDG_CONFIG_HOME/AppName/certs
|
||||
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") /
|
||||
app_name_ / "certs";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PathManager::CreateDirectories(const std::filesystem::path& p) {
|
||||
std::error_code ec;
|
||||
bool created = std::filesystem::create_directories(p, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
return created || std::filesystem::exists(p);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) {
|
||||
PWSTR path = NULL;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(id, 0, NULL, &path))) {
|
||||
std::wstring wpath(path);
|
||||
CoTaskMemFree(path);
|
||||
return std::filesystem::path(wpath);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string PathManager::GetHome() {
|
||||
if (const char* home = getenv("HOME")) {
|
||||
return std::string(home);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path)))
|
||||
return std::string(path);
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var,
|
||||
const std::string& def) {
|
||||
if (const char* val = getenv(env_var)) {
|
||||
return std::filesystem::path(val);
|
||||
}
|
||||
|
||||
return std::filesystem::path(def);
|
||||
}
|
||||
44
src/path_manager/path_manager.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2025-07-16
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _PATH_MANAGER_H_
|
||||
#define _PATH_MANAGER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class PathManager {
|
||||
public:
|
||||
explicit PathManager(const std::string& app_name);
|
||||
|
||||
std::filesystem::path GetConfigPath();
|
||||
|
||||
std::filesystem::path GetCachePath();
|
||||
|
||||
std::filesystem::path GetLogPath();
|
||||
|
||||
std::filesystem::path GetCertPath();
|
||||
|
||||
bool CreateDirectories(const std::filesystem::path& p);
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id);
|
||||
#endif
|
||||
|
||||
std::string GetHome();
|
||||
std::filesystem::path GetEnvOrDefault(const char* env_var,
|
||||
const std::string& def);
|
||||
|
||||
private:
|
||||
std::string app_name_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "screen_capture_x11.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
|
||||
|
||||
ScreenCaptureX11::ScreenCaptureX11() {}
|
||||
|
||||
ScreenCaptureX11::~ScreenCaptureX11() {
|
||||
if (capture_thread_->joinable()) {
|
||||
capture_thread_->join();
|
||||
}
|
||||
}
|
||||
|
||||
int ScreenCaptureX11::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", "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);
|
||||
ifmt_ = (AVInputFormat *)av_find_input_format("x11grab");
|
||||
if (!ifmt_) {
|
||||
printf("Couldn't find_input_format\n");
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCaptureX11::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 ScreenCaptureX11::Pause() { return 0; }
|
||||
|
||||
int ScreenCaptureX11::Resume() { return 0; }
|
||||
|
||||
int ScreenCaptureX11::Stop() { return 0; }
|
||||
|
||||
void ScreenCaptureX11::OnFrame() {}
|
||||
|
||||
void ScreenCaptureX11::CleanUp() {}
|
||||
@@ -1,85 +0,0 @@
|
||||
#ifndef _SCREEN_CAPTURE_X11_H_
|
||||
#define _SCREEN_CAPTURE_X11_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#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
|
||||
|
||||
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(int)> cb_desktop_error;
|
||||
|
||||
class ScreenCaptureX11 {
|
||||
public:
|
||||
ScreenCaptureX11();
|
||||
~ScreenCaptureX11();
|
||||
|
||||
public:
|
||||
int Init(const RECORD_DESKTOP_RECT &rect, const int fps, cb_desktop_data cb);
|
||||
|
||||
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;
|
||||
cb_desktop_error _on_error;
|
||||
|
||||
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
|
||||
@@ -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,140 +0,0 @@
|
||||
#include "screen_capture_avf.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
|
||||
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
|
||||
|
||||
ScreenCaptureAvf::ScreenCaptureAvf() {}
|
||||
|
||||
ScreenCaptureAvf::~ScreenCaptureAvf() {
|
||||
if (capture_thread_->joinable()) {
|
||||
capture_thread_->join();
|
||||
}
|
||||
}
|
||||
|
||||
int ScreenCaptureAvf::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 ScreenCaptureAvf::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 ScreenCaptureAvf::Pause() { return 0; }
|
||||
|
||||
int ScreenCaptureAvf::Resume() { return 0; }
|
||||
|
||||
int ScreenCaptureAvf::Stop() { return 0; }
|
||||
|
||||
void ScreenCaptureAvf::OnFrame() {}
|
||||
|
||||
void ScreenCaptureAvf::CleanUp() {}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-01
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURE_AVF_H_
|
||||
#define _SCREEN_CAPTURE_AVF_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#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
|
||||
|
||||
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(int)> cb_desktop_error;
|
||||
|
||||
class ScreenCaptureAvf {
|
||||
public:
|
||||
ScreenCaptureAvf();
|
||||
~ScreenCaptureAvf();
|
||||
|
||||
public:
|
||||
int Init(const RECORD_DESKTOP_RECT &rect, const int fps, cb_desktop_data cb);
|
||||
|
||||
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;
|
||||
cb_desktop_error _on_error;
|
||||
|
||||
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
|
||||
@@ -1,138 +0,0 @@
|
||||
#include "screen_capture_wgc.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <winrt/Windows.Foundation.Metadata.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc,
|
||||
LPARAM data) {
|
||||
MONITORINFOEX info_ex;
|
||||
info_ex.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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HMONITOR GetPrimaryMonitor() {
|
||||
HMONITOR hmonitor = nullptr;
|
||||
|
||||
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor);
|
||||
|
||||
return hmonitor;
|
||||
}
|
||||
|
||||
ScreenCaptureWgc::ScreenCaptureWgc() {}
|
||||
|
||||
ScreenCaptureWgc::~ScreenCaptureWgc() {
|
||||
Stop();
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
bool ScreenCaptureWgc::IsWgcSupported() {
|
||||
try {
|
||||
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
|
||||
return winrt::Windows::Foundation::Metadata::ApiInformation::
|
||||
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
|
||||
} catch (const winrt::hresult_error &) {
|
||||
return false;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int ScreenCaptureWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
|
||||
cb_desktop_data cb) {
|
||||
int error = 0;
|
||||
if (_inited == true) return error;
|
||||
|
||||
_fps = fps;
|
||||
|
||||
_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) {
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int ScreenCaptureWgc::Start() {
|
||||
if (_running == true) {
|
||||
std::cout << "record desktop duplication is already running" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_inited == false) {
|
||||
std::cout << "AE_NEED_INIT" << std::endl;
|
||||
return 4;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
session_->Start();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCaptureWgc::Pause() {
|
||||
_paused = true;
|
||||
if (session_) session_->Pause();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCaptureWgc::Resume() {
|
||||
_paused = false;
|
||||
if (session_) session_->Resume();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCaptureWgc::Stop() {
|
||||
_running = false;
|
||||
|
||||
if (session_) session_->Stop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCaptureWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
|
||||
if (_on_data)
|
||||
_on_data((unsigned char *)frame.data, frame.width * frame.height * 4,
|
||||
frame.width, frame.height);
|
||||
}
|
||||
|
||||
void ScreenCaptureWgc::CleanUp() {
|
||||
_inited = false;
|
||||
|
||||
if (session_) session_->Release();
|
||||
|
||||
session_ = nullptr;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef _SCREEN_CAPTURE_WGC_H_
|
||||
#define _SCREEN_CAPTURE_WGC_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "wgc_session.h"
|
||||
#include "wgc_session_impl.h"
|
||||
|
||||
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(int)> cb_desktop_error;
|
||||
|
||||
class ScreenCaptureWgc : public WgcSession::wgc_session_observer {
|
||||
public:
|
||||
ScreenCaptureWgc();
|
||||
~ScreenCaptureWgc();
|
||||
|
||||
public:
|
||||
bool IsWgcSupported();
|
||||
|
||||
int Init(const RECORD_DESKTOP_RECT &rect, const int fps, cb_desktop_data cb);
|
||||
|
||||
int Start();
|
||||
int Pause();
|
||||
int Resume();
|
||||
int Stop();
|
||||
|
||||
void OnFrame(const WgcSession::wgc_session_frame &frame);
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
WgcSession *session_ = nullptr;
|
||||
|
||||
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;
|
||||
cb_desktop_error _on_error;
|
||||
};
|
||||
|
||||
#endif
|
||||
174
src/screen_capturer/linux/screen_capturer_x11.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "screen_capturer_x11.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
ScreenCapturerX11::ScreenCapturerX11() {}
|
||||
|
||||
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
|
||||
|
||||
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;
|
||||
|
||||
y_plane_.resize(width_ * height_);
|
||||
uv_plane_.resize((width_ / 2) * (height_ / 2) * 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Destroy() {
|
||||
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() {
|
||||
if (running_) return 0;
|
||||
running_ = true;
|
||||
paused_ = false;
|
||||
thread_ = std::thread([this]() {
|
||||
while (running_) {
|
||||
if (!paused_) OnFrame();
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Stop() {
|
||||
if (!running_) return 0;
|
||||
running_ = false;
|
||||
if (thread_.joinable()) thread_.join();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Pause(int monitor_index) {
|
||||
paused_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::Resume(int monitor_index) {
|
||||
paused_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerX11::SwitchTo(int monitor_index) {
|
||||
monitor_index_ = monitor_index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
64
src/screen_capturer/linux/screen_capturer_x11.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* @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 <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
class ScreenCapturerX11 : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerX11();
|
||||
~ScreenCapturerX11();
|
||||
|
||||
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();
|
||||
|
||||
private:
|
||||
Display* display_ = nullptr;
|
||||
Window root_ = 0;
|
||||
XRRScreenResources* screen_res_ = nullptr;
|
||||
int left_ = 0;
|
||||
int top_ = 0;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
std::thread thread_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> paused_{false};
|
||||
std::atomic<int> monitor_index_{0};
|
||||
int fps_ = 60;
|
||||
cb_desktop_data callback_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
|
||||
// 缓冲区
|
||||
std::vector<uint8_t> y_plane_;
|
||||
std::vector<uint8_t> uv_plane_;
|
||||
};
|
||||
|
||||
#endif
|
||||
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
@@ -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
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/graphics/IOGraphicsLib.h>
|
||||
#include <IOSurface/IOSurface.h>
|
||||
#include <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "display_info.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
static const int kFullDesktopScreenId = -1;
|
||||
class ScreenCapturerSckImpl;
|
||||
|
||||
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
|
||||
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
|
||||
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
||||
// introduced in macOS 14.
|
||||
API_AVAILABLE(macos(14.0))
|
||||
@interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput>
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer;
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content;
|
||||
|
||||
// Called just before the capturer is destroyed. This avoids a dangling pointer,
|
||||
// and prevents any new calls into a deleted capturer. If any method-call on the
|
||||
// capturer is currently running on a different thread, this blocks until it
|
||||
// completes.
|
||||
- (void)releaseCapturer;
|
||||
@end
|
||||
|
||||
class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
||||
public:
|
||||
explicit ScreenCapturerSckImpl();
|
||||
|
||||
ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete;
|
||||
ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete;
|
||||
~ScreenCapturerSckImpl();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb) override;
|
||||
|
||||
int Start() override;
|
||||
|
||||
int SwitchTo(int monitor_index) override;
|
||||
|
||||
int Destroy() override;
|
||||
|
||||
int Stop() override;
|
||||
|
||||
int Pause(int monitor_index) override { return 0; }
|
||||
|
||||
int Resume(int monitor_index) override { return 0; }
|
||||
|
||||
std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; }
|
||||
|
||||
private:
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
std::map<int, CGDirectDisplayID> display_id_map_;
|
||||
std::map<CGDirectDisplayID, int> display_id_map_reverse_;
|
||||
std::map<CGDirectDisplayID, std::string> display_id_name_map_;
|
||||
unsigned char *nv12_frame_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int fps_ = 60;
|
||||
|
||||
public:
|
||||
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
|
||||
// nil if an error occurred. May run on an arbitrary thread.
|
||||
void OnShareableContentCreated(SCShareableContent *content);
|
||||
// Called by SckHelper to notify of a newly captured frame. May run on an arbitrary thread.
|
||||
// void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment);
|
||||
void OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, CFDictionaryRef attachment);
|
||||
|
||||
private:
|
||||
// Called when starting the capturer or the configuration has changed (either from a
|
||||
// SwitchTo() call, or the screen-resolution has changed). This tells SCK to fetch new
|
||||
// shareable content, and the completion-handler will either start a new stream, or reconfigure
|
||||
// the existing stream. Runs on the caller's thread.
|
||||
void StartOrReconfigureCapturer();
|
||||
// Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++
|
||||
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
|
||||
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
|
||||
SckHelper *__strong helper_;
|
||||
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
|
||||
// thread.
|
||||
cb_desktop_data _on_data = nullptr;
|
||||
// Signals that a permanent error occurred. This may be set on any thread, and is read by
|
||||
// CaptureFrame() which runs on the caller's thread.
|
||||
std::atomic<bool> permanent_error_ = false;
|
||||
// Guards some variables that may be accessed on different threads.
|
||||
std::mutex lock_;
|
||||
// Provides captured desktop frames.
|
||||
SCStream *__strong stream_;
|
||||
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
|
||||
// support full-desktop capture, and will fall back to the first display.
|
||||
CGDirectDisplayID current_display_ = 0;
|
||||
};
|
||||
|
||||
std::string GetDisplayName(CGDirectDisplayID display_id) {
|
||||
io_iterator_t iter;
|
||||
io_service_t serv = 0, matched_serv = 0;
|
||||
|
||||
CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");
|
||||
if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) {
|
||||
return "";
|
||||
}
|
||||
|
||||
while ((serv = IOIteratorNext(iter)) != 0) {
|
||||
CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
|
||||
if (info) {
|
||||
CFNumberRef vendorID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
|
||||
CFNumberRef productID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
|
||||
uint32_t vID = 0, pID = 0;
|
||||
if (vendorID && productID && CFNumberGetValue(vendorID, kCFNumberIntType, &vID) &&
|
||||
CFNumberGetValue(productID, kCFNumberIntType, &pID) &&
|
||||
vID == CGDisplayVendorNumber(display_id) && pID == CGDisplayModelNumber(display_id)) {
|
||||
matched_serv = serv;
|
||||
CFRelease(info);
|
||||
break;
|
||||
}
|
||||
CFRelease(info);
|
||||
}
|
||||
IOObjectRelease(serv);
|
||||
}
|
||||
IOObjectRelease(iter);
|
||||
|
||||
if (!matched_serv) return "";
|
||||
|
||||
CFDictionaryRef display_info =
|
||||
IODisplayCreateInfoDictionary(matched_serv, kIODisplayOnlyPreferredName);
|
||||
IOObjectRelease(matched_serv);
|
||||
if (!display_info) return "";
|
||||
|
||||
CFDictionaryRef product_name_dict =
|
||||
(CFDictionaryRef)CFDictionaryGetValue(display_info, CFSTR(kDisplayProductName));
|
||||
std::string result;
|
||||
if (product_name_dict) {
|
||||
CFIndex count = CFDictionaryGetCount(product_name_dict);
|
||||
if (count > 0) {
|
||||
std::vector<const void *> keys(count);
|
||||
std::vector<const void *> values(count);
|
||||
CFDictionaryGetKeysAndValues(product_name_dict, keys.data(), values.data());
|
||||
CFStringRef name_ref = (CFStringRef)values[0];
|
||||
if (name_ref) {
|
||||
CFIndex maxSize =
|
||||
CFStringGetMaximumSizeForEncoding(CFStringGetLength(name_ref), kCFStringEncodingUTF8) +
|
||||
1;
|
||||
std::vector<char> buffer(maxSize);
|
||||
if (CFStringGetCString(name_ref, buffer.data(), buffer.size(), kCFStringEncodingUTF8)) {
|
||||
result = buffer.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CFRelease(display_info);
|
||||
return result;
|
||||
}
|
||||
|
||||
ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
|
||||
helper_ = [[SckHelper alloc] initWithCapturer:this];
|
||||
}
|
||||
|
||||
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
||||
display_info_list_.clear();
|
||||
display_id_map_.clear();
|
||||
display_id_map_reverse_.clear();
|
||||
display_id_name_map_.clear();
|
||||
|
||||
if (nv12_frame_) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = nullptr;
|
||||
}
|
||||
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
[helper_ releaseCapturer];
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||
_on_data = cb;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block SCShareableContent *content = nil;
|
||||
|
||||
[SCShareableContent
|
||||
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Failed to get shareable content: %@", error);
|
||||
} else {
|
||||
content = result;
|
||||
}
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (!content || content.displays.count == 0) {
|
||||
LOG_ERROR("Failed to get display info");
|
||||
return 0;
|
||||
}
|
||||
|
||||
CGDirectDisplayID displays[10];
|
||||
uint32_t count;
|
||||
CGGetActiveDisplayList(10, displays, &count);
|
||||
|
||||
int unnamed_count = 1;
|
||||
for (SCDisplay *display in content.displays) {
|
||||
CGDirectDisplayID display_id = display.displayID;
|
||||
CGRect bounds = CGDisplayBounds(display_id);
|
||||
bool is_primary = CGDisplayIsMain(display_id);
|
||||
|
||||
std::string name;
|
||||
name = GetDisplayName(display_id);
|
||||
|
||||
if (name.empty()) {
|
||||
name = "Display " + std::to_string(unnamed_count++);
|
||||
}
|
||||
|
||||
DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary,
|
||||
static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y),
|
||||
static_cast<int>(bounds.origin.x + bounds.size.width),
|
||||
static_cast<int>(bounds.origin.y + bounds.size.height));
|
||||
|
||||
display_info_list_.push_back(info);
|
||||
display_id_map_[display_info_list_.size() - 1] = display_id;
|
||||
display_id_map_reverse_[display_id] = display_info_list_.size() - 1;
|
||||
display_id_name_map_[display_id] = name;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Start() {
|
||||
StartOrReconfigureCapturer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
|
||||
if (stream_) {
|
||||
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) {
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
stream_ = nil;
|
||||
current_display_ = display_id_map_[monitor_index];
|
||||
StartOrReconfigureCapturer();
|
||||
}];
|
||||
} else {
|
||||
current_display_ = display_id_map_[monitor_index];
|
||||
StartOrReconfigureCapturer();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Destroy() {
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
if (stream_) {
|
||||
LOG_INFO("Destroying stream");
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
stream_ = nil;
|
||||
}
|
||||
current_display_ = 0;
|
||||
permanent_error_ = false;
|
||||
_on_data = nullptr;
|
||||
[helper_ releaseCapturer];
|
||||
helper_ = nil;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Stop() {
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
if (stream_) {
|
||||
LOG_INFO("Stopping stream");
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
stream_ = nil;
|
||||
}
|
||||
current_display_ = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *content) {
|
||||
if (!content) {
|
||||
LOG_ERROR("getShareableContent failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.displays.count) {
|
||||
LOG_ERROR("getShareableContent returned no displays");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SCDisplay *captured_display;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
for (SCDisplay *display in content.displays) {
|
||||
if (current_display_ == display.displayID) {
|
||||
LOG_WARN("current display: {}, name: {}", current_display_,
|
||||
display_id_name_map_[current_display_]);
|
||||
captured_display = display;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!captured_display) {
|
||||
captured_display = content.displays.firstObject;
|
||||
current_display_ = captured_display.displayID;
|
||||
}
|
||||
}
|
||||
|
||||
SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display
|
||||
excludingWindows:@[]];
|
||||
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
|
||||
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
config.showsCursor = false;
|
||||
config.width = filter.contentRect.size.width * filter.pointPixelScale;
|
||||
config.height = filter.contentRect.size.height * filter.pointPixelScale;
|
||||
config.captureResolution = SCCaptureResolutionAutomatic;
|
||||
config.minimumFrameInterval = CMTimeMake(1, fps_);
|
||||
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
|
||||
if (stream_) {
|
||||
LOG_INFO("Updating stream configuration");
|
||||
[stream_ updateContentFilter:filter completionHandler:nil];
|
||||
[stream_ updateConfiguration:config completionHandler:nil];
|
||||
} else {
|
||||
stream_ = [[SCStream alloc] initWithFilter:filter configuration:config delegate:helper_];
|
||||
|
||||
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
|
||||
// best performance.
|
||||
NSError *add_stream_output_error;
|
||||
dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL);
|
||||
bool add_stream_output_result = [stream_ addStreamOutput:helper_
|
||||
type:SCStreamOutputTypeScreen
|
||||
sampleHandlerQueue:queue
|
||||
error:&add_stream_output_error];
|
||||
|
||||
if (!add_stream_output_result) {
|
||||
stream_ = nil;
|
||||
LOG_ERROR("addStreamOutput failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = ^(NSError *error) {
|
||||
if (error) {
|
||||
// It should be safe to access `this` here, because the C++ destructor
|
||||
// calls stopCaptureWithCompletionHandler on the stream, which cancels
|
||||
// this handler.
|
||||
permanent_error_ = true;
|
||||
LOG_ERROR("startCaptureWithCompletionHandler failed");
|
||||
} else {
|
||||
LOG_INFO("Capture started");
|
||||
}
|
||||
};
|
||||
|
||||
[stream_ startCaptureWithCompletionHandler:handler];
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
||||
CFDictionaryRef attachment) {
|
||||
size_t width = CVPixelBufferGetWidth(pixelBuffer);
|
||||
size_t height = CVPixelBufferGetHeight(pixelBuffer);
|
||||
|
||||
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
if (status != kCVReturnSuccess) {
|
||||
LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t required_size = width * height * 3 / 2;
|
||||
if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) {
|
||||
delete[] nv12_frame_;
|
||||
nv12_frame_ = new unsigned char[required_size];
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
||||
|
||||
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
|
||||
|
||||
unsigned char *dst_y = nv12_frame_;
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width);
|
||||
}
|
||||
|
||||
unsigned char *dst_uv = nv12_frame_ + width * height;
|
||||
for (size_t row = 0; row < height / 2; ++row) {
|
||||
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
|
||||
}
|
||||
|
||||
_on_data(nv12_frame_, width * height * 3 / 2, width, height,
|
||||
display_id_name_map_[current_display_].c_str());
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
|
||||
// The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_`
|
||||
// inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is
|
||||
// deleted before the block is executed.
|
||||
SckHelper *local_helper = helper_;
|
||||
auto handler = ^(SCShareableContent *content, NSError *error) {
|
||||
[local_helper onShareableContentCreated:content];
|
||||
};
|
||||
[SCShareableContent getShareableContentWithCompletionHandler:handler];
|
||||
}
|
||||
|
||||
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
|
||||
return std::make_unique<ScreenCapturerSckImpl>();
|
||||
}
|
||||
|
||||
@implementation SckHelper {
|
||||
// This lock is to prevent the capturer being destroyed while an instance
|
||||
// method is still running on another thread.
|
||||
std::mutex _capturer_lock;
|
||||
ScreenCapturerSckImpl *_capturer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_capturer = capturer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content {
|
||||
std::lock_guard<std::mutex> lock(_capturer_lock);
|
||||
if (_capturer) {
|
||||
_capturer->OnShareableContentCreated(content);
|
||||
} else {
|
||||
LOG_ERROR("Invalid capturer");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stream:(SCStream *)stream
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
ofType:(SCStreamOutputType)type {
|
||||
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (!pixelBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFRetain(pixelBuffer);
|
||||
|
||||
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
|
||||
if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) {
|
||||
LOG_ERROR("Discarding frame with no attachments");
|
||||
CFRelease(pixelBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
CFDictionaryRef attachment =
|
||||
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0));
|
||||
|
||||
std::lock_guard<std::mutex> lock(_capturer_lock);
|
||||
if (_capturer) {
|
||||
_capturer->OnNewCVPixelBuffer(pixelBuffer, attachment);
|
||||
}
|
||||
|
||||
CFRelease(pixelBuffer);
|
||||
}
|
||||
|
||||
- (void)releaseCapturer {
|
||||
std::lock_guard<std::mutex> lock(_capturer_lock);
|
||||
_capturer = nullptr;
|
||||
}
|
||||
|
||||
@end
|
||||
34
src/screen_capturer/screen_capturer.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-15
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_H_
|
||||
#define _SCREEN_CAPTURER_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "display_info.h"
|
||||
|
||||
class ScreenCapturer {
|
||||
public:
|
||||
typedef std::function<void(unsigned char*, int, int, int, const char*)>
|
||||
cb_desktop_data;
|
||||
|
||||
public:
|
||||
virtual ~ScreenCapturer() {}
|
||||
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb) = 0;
|
||||
virtual int Destroy() = 0;
|
||||
virtual int Start() = 0;
|
||||
virtual int Stop() = 0;
|
||||
virtual int Pause(int monitor_index) = 0;
|
||||
virtual int Resume(int monitor_index) = 0;
|
||||
|
||||
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
|
||||
virtual int SwitchTo(int monitor_index) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
38
src/screen_capturer/screen_capturer_factory.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-15
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_FACTORY_H_
|
||||
#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_sck.h"
|
||||
#endif
|
||||
|
||||
class ScreenCapturerFactory {
|
||||
public:
|
||||
virtual ~ScreenCapturerFactory() {}
|
||||
|
||||
public:
|
||||
ScreenCapturer* Create() {
|
||||
#ifdef _WIN32
|
||||
return new ScreenCapturerWgc();
|
||||
#elif __linux__
|
||||
return new ScreenCapturerX11();
|
||||
#elif __APPLE__
|
||||
// return new ScreenCapturerAvf();
|
||||
return new ScreenCapturerSck();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||