Compare commits
	
		
			490 Commits
		
	
	
		
			audio_capt
			...
			v1.0.2-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2c622bc76e | ||
|  | b790c7d08e | ||
|  | 0ca90d2516 | ||
|  | 401bfe4483 | ||
|  | 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 | ||
|  | 2d6cfb5c76 | ||
|  | ed8b536ac0 | ||
|  | 5e2d27e9d2 | ||
|  | 47a2dc85f9 | ||
|  | 5febe99bc2 | 
							
								
								
									
										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-13 | ||||||
|  |             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"] | [submodule "submodules/minirtc"] | ||||||
| 	path = thirdparty/projectx | 	path = submodules/minirtc | ||||||
| 	url = git@github.com:dijunkun/projectx.git | 	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[]) { | ||||||
|  |   crossdesk::Render render; | ||||||
|  |   render.Run(); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/common/display_info.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | |||||||
|  | /* | ||||||
|  |  * @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> | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										128
									
								
								src/common/platform.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,128 @@ | |||||||
|  | #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 | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										18
									
								
								src/common/platform.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2023-12-18 | ||||||
|  |  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _PLATFORM_H_ | ||||||
|  | #define _PLATFORM_H_ | ||||||
|  |  | ||||||
|  | #include <iostream> | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | std::string GetMac(); | ||||||
|  | std::string GetHostName(); | ||||||
|  |  | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										3644
									
								
								src/config_center/SimpleIni.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										269
									
								
								src/config_center/config_center.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,269 @@ | |||||||
|  | #include "config_center.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										91
									
								
								src/config_center/config_center.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,91 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										85
									
								
								src/device_controller/device_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,85 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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(); | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										36
									
								
								src/device_controller/device_controller_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										72
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,72 @@ | |||||||
|  | #include "keyboard_capturer.h" | ||||||
|  |  | ||||||
|  | #include "keyboard_converter.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										34
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										170
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,170 @@ | |||||||
|  | #include "keyboard_capturer.h" | ||||||
|  |  | ||||||
|  | #include "keyboard_converter.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										39
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										59
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | |||||||
|  | #include "keyboard_capturer.h" | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										31
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										742
									
								
								src/device_controller/keyboard_converter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,742 @@ | |||||||
|  | /* | ||||||
|  |  * @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> | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | // 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 | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #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__ */ | ||||||
							
								
								
									
										127
									
								
								src/device_controller/mouse/linux/mouse_controller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,127 @@ | |||||||
|  | #include "mouse_controller.h" | ||||||
|  |  | ||||||
|  | #include <X11/extensions/XTest.h> | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_); | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										43
									
								
								src/device_controller/mouse/linux/mouse_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,43 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										104
									
								
								src/device_controller/mouse/mac/mouse_controller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,104 @@ | |||||||
|  | #include "mouse_controller.h" | ||||||
|  |  | ||||||
|  | #include <ApplicationServices/ApplicationServices.h> | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										32
									
								
								src/device_controller/mouse/mac/mouse_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										75
									
								
								src/device_controller/mouse/windows/mouse_controller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,75 @@ | |||||||
|  | #include "mouse_controller.h" | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										30
									
								
								src/device_controller/mouse/windows/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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #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 | ||||||
							
								
								
									
										176
									
								
								src/gui/assets/localization/localization.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,176 @@ | |||||||
|  | /* | ||||||
|  |  * @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 crossdesk { | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										1150
									
								
								src/gui/main.cpp
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										293
									
								
								src/gui/panels/local_peer_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,293 @@ | |||||||
|  | #include <random> | ||||||
|  |  | ||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										289
									
								
								src/gui/panels/recent_connections_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,289 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										184
									
								
								src/gui/panels/remote_peer_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,184 @@ | |||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										1437
									
								
								src/gui/render.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										474
									
								
								src/gui/render.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,474 @@ | |||||||
|  | /* | ||||||
|  |  * @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 | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  | 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 ------ */ | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										548
									
								
								src/gui/render_callback.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,548 @@ | |||||||
|  | #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 | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										329
									
								
								src/gui/toolbars/control_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,329 @@ | |||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										41
									
								
								src/gui/toolbars/status_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										177
									
								
								src/gui/toolbars/title_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,177 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | #define BUTTON_PADDING 36.0f | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										115
									
								
								src/gui/tray/win_tray.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,115 @@ | |||||||
|  | #include "win_tray.h" | ||||||
|  |  | ||||||
|  | #include <SDL3/SDL.h> | ||||||
|  |  | ||||||
|  | #include "localization.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | // 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										38
									
								
								src/gui/tray/win_tray.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | |||||||
|  | /* | ||||||
|  |  * @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) | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										64
									
								
								src/gui/windows/about_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,64 @@ | |||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										174
									
								
								src/gui/windows/connection_status_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,174 @@ | |||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										226
									
								
								src/gui/windows/control_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,226 @@ | |||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										396
									
								
								src/gui/windows/main_settings_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,396 @@ | |||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										52
									
								
								src/gui/windows/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										324
									
								
								src/gui/windows/server_settings_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,324 @@ | |||||||
|  | #include <filesystem> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <windows.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #include "layout.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										205
									
								
								src/gui/windows/stream_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,205 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										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 |  | ||||||
							
								
								
									
										65
									
								
								src/log/rd_log.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,65 @@ | |||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <filesystem> | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										41
									
								
								src/log/rd_log.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | |||||||
|  | /* | ||||||
|  |  * @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 | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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__) | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										94
									
								
								src/path_manager/path_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,94 @@ | |||||||
|  | #include "path_manager.h" | ||||||
|  |  | ||||||
|  | #include <cstdlib> | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										46
									
								
								src/path_manager/path_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | |||||||
|  | /* | ||||||
|  |  * @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 | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
| @@ -1,134 +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; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   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,26 +0,0 @@ | |||||||
| #include "screen_capture_wgc.h" |  | ||||||
|  |  | ||||||
| #include <iostream> |  | ||||||
|  |  | ||||||
| ScreenCaptureWgc::ScreenCaptureWgc() {} |  | ||||||
|  |  | ||||||
| ScreenCaptureWgc::~ScreenCaptureWgc() {} |  | ||||||
|  |  | ||||||
| bool ScreenCaptureWgc::IsWgcSupported() { return false; } |  | ||||||
|  |  | ||||||
| int ScreenCaptureWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps, |  | ||||||
|                            cb_desktop_data cb) { |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int ScreenCaptureWgc::Start() { return 0; } |  | ||||||
|  |  | ||||||
| int ScreenCaptureWgc::Pause() { return 0; } |  | ||||||
|  |  | ||||||
| int ScreenCaptureWgc::Resume() { return 0; } |  | ||||||
|  |  | ||||||
| int ScreenCaptureWgc::Stop() { return 0; } |  | ||||||
|  |  | ||||||
| void ScreenCaptureWgc::OnFrame() {} |  | ||||||
|  |  | ||||||
| void ScreenCaptureWgc::CleanUp() {} |  | ||||||
| @@ -1,56 +0,0 @@ | |||||||
| #ifndef _SCREEN_CAPTURE_WGC_H_ |  | ||||||
| #define _SCREEN_CAPTURE_WGC_H_ |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <functional> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
|  |  | ||||||
| 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: |  | ||||||
|   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(); |  | ||||||
|  |  | ||||||
|  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; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #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 |  | ||||||
							
								
								
									
										177
									
								
								src/screen_capturer/linux/screen_capturer_x11.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,177 @@ | |||||||
|  | #include "screen_capturer_x11.h" | ||||||
|  |  | ||||||
|  | #include <chrono> | ||||||
|  | #include <thread> | ||||||
|  |  | ||||||
|  | #include "libyuv.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | } | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										66
									
								
								src/screen_capturer/linux/screen_capturer_x11.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										76
									
								
								src/screen_capturer/macosx/screen_capturer_sck.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,76 @@ | |||||||
|  | #include "screen_capturer_sck.h" | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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() {} | ||||||
|  | }  // namespace crossdesk | ||||||
							
								
								
									
										61
									
								
								src/screen_capturer/macosx/screen_capturer_sck.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,61 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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_; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										491
									
								
								src/screen_capturer/macosx/screen_capturer_sck_impl.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,491 @@ | |||||||
|  | /* | ||||||
|  |  *  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" | ||||||
|  |  | ||||||
|  | using namespace crossdesk; | ||||||
|  |  | ||||||
|  | class ScreenCapturerSckImpl; | ||||||
|  |  | ||||||
|  | static const int kFullDesktopScreenId = -1; | ||||||
|  |  | ||||||
|  | // 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]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @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 | ||||||
|  |  | ||||||
|  | std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() { | ||||||
|  |   return std::make_unique<ScreenCapturerSckImpl>(); | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/screen_capturer/screen_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | |||||||
|  | /* | ||||||
|  |  * @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" | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||
							
								
								
									
										40
									
								
								src/screen_capturer/screen_capturer_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | |||||||
|  | /* | ||||||
|  |  * @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 | ||||||
|  |  | ||||||
|  | namespace crossdesk { | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | }  // namespace crossdesk | ||||||
|  | #endif | ||||||