Compare commits
	
		
			476 Commits
		
	
	
		
			audio_capt
			...
			v1.0.1-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | cba644f055 | ||
|  | 27263fe1db | ||
|  | 0bd27d0b17 | ||
|  | ee5612da8b | ||
|  | c7a2023c88 | ||
|  | c031a8c145 | ||
|  | 0bf83f07ad | ||
|  | 3638b712bd | ||
|  | 17f9536476 | ||
|  | 0ef51e3faf | ||
|  | cccf5dadb2 | ||
|  | 2f0b0ffc22 | ||
|  | c7411b59f1 | ||
|  | 8222782522 | ||
|  | 5fed09c1aa | ||
|  | 8e499772f9 | ||
|  | 0da812e7e9 | ||
|  | 3d67b6e9d6 | ||
|  | 506b2893c6 | ||
|  | 56abe9e690 | ||
|  | ab13fa582d | ||
|  | b9e8192eee | ||
|  | 5590aaeb1e | ||
|  | adfe14809f | ||
|  | a21dbc8d69 | ||
|  | 9d45e497f4 | ||
|  | e5891eb397 | ||
|  | b9c70f54d3 | ||
|  | 9cd617d078 | ||
|  | 92fd7f2e89 | ||
|  | b8535fff6f | ||
|  | 09f34a81ad | ||
|  | 22cc552e85 | ||
|  | 69a4dfcbb9 | ||
|  | e1390ca2d3 | ||
|  | dccdcd1b6f | ||
|  | 276c3a336f | ||
|  | 08518a9409 | ||
|  | f6b0767bb1 | ||
|  | 641fc84430 | ||
|  | 4ce79b87a3 | ||
|  | 477b204913 | ||
|  | 98f349ea2f | ||
|  | 47b1e15eef | ||
|  | 590bf50924 | ||
|  | 097633e47d | ||
|  | d9ba88107d | ||
|  | f6a6ef0b08 | ||
|  | a93ee0c19e | ||
|  | 7e73553aca | ||
|  | b858bd78c0 | ||
|  | f10947edf9 | ||
|  | c723cca8f9 | ||
|  | b8f8b07ebe | ||
|  | f9c6e5a6ef | ||
|  | 15bf8ef8c0 | ||
|  | 6565816c0e | ||
|  | 2aa67ccd57 | ||
|  | 88c75f94e4 | ||
|  | aea9505c4c | ||
|  | 646740aab6 | ||
|  | a458836e6e | ||
|  | 5fe7df8ea8 | ||
|  | fda3743f86 | ||
|  | 9e4170b3a8 | ||
|  | 43338aaf02 | ||
|  | 274b7fcedc | ||
|  | 01a5660984 | ||
|  | a45118f785 | ||
|  | b6631c3db0 | ||
|  | 84d164c3af | ||
|  | 32815ce25a | ||
|  | 7c2b3f8c8d | ||
|  | 309d9d6ed6 | ||
|  | 91ab21c5f2 | ||
|  | b8369c4f0a | ||
|  | a068a32303 | ||
|  | b6fe0da581 | ||
|  | 6e7e2697b5 | ||
|  | 22084072b0 | ||
|  | d5457373f4 | ||
|  | 01ae299cde | ||
|  | fbcd4c21cf | ||
|  | cf9dc0a9a5 | ||
|  | 1b4f41af46 | ||
|  | 7f3425519b | ||
|  | 434c92deb6 | ||
|  | e5eae1feb4 | ||
|  | 37e37d7d20 | ||
|  | 062568dc96 | ||
|  | d60fdf9050 | ||
|  | 9912a88a13 | ||
|  | 8a8f2cd5d7 | ||
|  | e77e16d9c2 | ||
|  | 1616d0ec33 | ||
|  | 374453775f | ||
|  | 6f8fd6a030 | ||
|  | c7166975b3 | ||
|  | 2ae5e5a969 | ||
|  | 85cdc995c5 | ||
|  | b051c8a059 | ||
|  | 508b7dc7a1 | ||
|  | f203566b81 | ||
|  | 8360c1725f | ||
|  | 3b00fdef71 | ||
|  | f2664daaec | ||
|  | 1a1bbe3e4d | ||
|  | 4c898c5f07 | ||
|  | 5d704edbc8 | ||
|  | b23038bac6 | ||
|  | 147b9b2ddc | ||
|  | 1327b995f2 | ||
|  | e8d7ec8daf | ||
|  | 3bf68a396f | ||
|  | 29077fad5e | ||
|  | 69367bdadf | ||
|  | f650b5a6ef | ||
|  | 75021b74ef | ||
|  | 205421621b | ||
|  | 4bb5607702 | ||
|  | 78c54136e2 | ||
|  | 8fe8f4fd7e | ||
|  | c13e2613b6 | ||
|  | e52ec6cde2 | ||
|  | ad6a24b230 | ||
|  | ad60815d83 | ||
|  | c47a90bf9c | ||
|  | 3901e26c37 | ||
|  | 41c27139a0 | ||
|  | a31ca21ef4 | ||
|  | 57939ccd00 | ||
|  | fc14f7b9c6 | ||
|  | cfcf6dd9cb | ||
|  | 854c3a72c7 | ||
|  | 8004d25ca3 | ||
|  | b04dfd188a | ||
|  | 383ace2493 | ||
|  | 094204361c | ||
|  | d72c6d9df7 | ||
|  | 05d73ebe9a | ||
|  | a8b5e934b8 | ||
|  | 920677a433 | ||
|  | b86d3d42ee | ||
|  | 818dab764f | ||
|  | 7ea26854af | ||
|  | f34b55b88a | ||
|  | 67168f7735 | ||
|  | 6e69c37056 | ||
|  | 8f5dd21e75 | ||
|  | e2dfeb1186 | ||
|  | 96c7d3174b | ||
|  | 97f6c18296 | ||
|  | 9138ca771f | ||
|  | abc6b17a3b | ||
|  | 8e0524bf60 | ||
|  | 6ebf583a13 | ||
|  | eca4f614c8 | ||
|  | 11358e0b60 | ||
|  | 0a1014dded | ||
|  | a6beb48b1f | ||
|  | 5eff530ab9 | ||
|  | e6e237279c | ||
|  | 6fe46f6181 | ||
|  | 9b5023645c | ||
|  | c2da4ebcb7 | ||
|  | dc05f2405f | ||
|  | e2a469ed65 | ||
|  | e89d385f99 | ||
|  | 0a61dcc2be | ||
|  | 250fd49406 | ||
|  | 93bd5b2660 | ||
|  | d05ff9f905 | ||
|  | f3451a5fa1 | ||
|  | a9084ba98d | ||
|  | 893051f9b3 | ||
|  | dfbd4317b7 | ||
|  | 532ad0eb51 | ||
|  | 184983d857 | ||
|  | c33e4bbe0e | ||
|  | 4b9e86c424 | ||
|  | f7f8ddd925 | ||
|  | 188b1758f2 | ||
|  | 9705374b9a | ||
|  | 22aed6ea53 | ||
|  | 44c5fde086 | ||
|  | 2cd5a9dd76 | ||
|  | fa73447f88 | ||
|  | 536fe17055 | ||
|  | 662cbbc3cc | ||
|  | 69a8503ee1 | ||
|  | b906bfcafd | ||
|  | f891a047d6 | ||
|  | dce32672a6 | ||
|  | 2774991107 | ||
|  | b7ce8b6299 | ||
|  | 700fb2ec14 | ||
|  | 62d14587cd | ||
|  | 824d9243cc | ||
|  | 5351b4da0e | ||
|  | fcd0488624 | ||
|  | 193e905b28 | ||
|  | f48085c69a | ||
|  | cf078be53f | ||
|  | badcd6a05c | ||
|  | d828bd736d | ||
|  | 1e014bdae3 | ||
|  | 334ab182db | ||
|  | f52de76bfc | ||
|  | 6c7d0e8cab | ||
|  | 7229ae856e | ||
|  | 597d760d24 | ||
|  | bcf61e1ada | ||
|  | 08e596714b | ||
|  | bff577ba34 | ||
|  | 4df935b9d2 | ||
|  | 9bb560314b | ||
|  | 1a0c5e8b42 | ||
|  | 011919d0e7 | ||
|  | 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-15-intel | ||||
|             cache-key: intel | ||||
|             out-dir: ./build/macosx/x86_64/release/crossdesk | ||||
|             package_script: ./scripts/macosx/pkg_x64.sh | ||||
|           - arch: arm64 | ||||
|             runner: macos-14 | ||||
|             cache-key: arm | ||||
|             out-dir: ./build/macosx/arm64/release/crossdesk | ||||
|             package_script: ./scripts/macosx/pkg_arm64.sh | ||||
|  | ||||
|     steps: | ||||
|       - name: Extract version number | ||||
|         id: version | ||||
|         run: | | ||||
|           VERSION="${GITHUB_REF##*/}" | ||||
|           SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | ||||
|           VERSION_NUM="${VERSION#v}-${SHORT_SHA}" | ||||
|           echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV | ||||
|           echo "VERSION_NUM=${VERSION_NUM}" | ||||
|  | ||||
|       - name: Cache xmake dependencies | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: ~/.xmake/packages | ||||
|           key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}- | ||||
|  | ||||
|       - name: Install xmake | ||||
|         run: brew install xmake | ||||
|  | ||||
|       - name: Checkout code | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: Initialize submodules | ||||
|         run: git submodule update --init --recursive | ||||
|  | ||||
|       - name: Build CrossDesk | ||||
|         run: | | ||||
|           xmake f --CROSSDESK_VERSION=${VERSION_NUM} -y | ||||
|           xmake b -vy crossdesk | ||||
|  | ||||
|       - name: Decode and save certificate | ||||
|         shell: bash | ||||
|         run: | | ||||
|           mkdir -p certs | ||||
|           echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt | ||||
|  | ||||
|       - name: Package CrossDesk app | ||||
|         run: | | ||||
|           chmod +x ${{ matrix.package_script }} | ||||
|           ${{ matrix.package_script }} ${VERSION_NUM} | ||||
|  | ||||
|       - name: Upload build artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }} | ||||
|           path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg | ||||
|  | ||||
|       - name: Move files to release dir | ||||
|         run: | | ||||
|           mkdir -p release | ||||
|           cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/ | ||||
|  | ||||
|   # Windows | ||||
|   build-windows-x64: | ||||
|     name: Build on Windows x64 | ||||
|     runs-on: windows-2022 | ||||
|     env: | ||||
|       XMAKE_GLOBALDIR: D:\xmake_global | ||||
|     steps: | ||||
|       - name: Extract version number | ||||
|         shell: pwsh | ||||
|         run: | | ||||
|           $ref = $env:GITHUB_REF | ||||
|           $version = $ref -replace '^refs/(tags|heads)/', '' | ||||
|           $version = $version -replace '^v', '' | ||||
|           $version = $version -replace '/', '-' | ||||
|           $SHORT_SHA = $env:GITHUB_SHA.Substring(0,7) | ||||
|           echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV | ||||
|  | ||||
|       - name: Cache xmake dependencies | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: D:\xmake_global\.xmake\packages | ||||
|           key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }} | ||||
|           restore-keys: | | ||||
|             ${{ runner.os }}-xmake-deps-intel- | ||||
|  | ||||
|       - name: Install xmake | ||||
|         run: | | ||||
|           Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content | ||||
|           echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | ||||
|           xmake create cuda | ||||
|           Set-Location cuda | ||||
|           xmake g --theme=plain | ||||
|           $cudaPath = "" | ||||
|           $packagesPath = "D:\xmake_global\.xmake\packages" | ||||
|  | ||||
|           if (Test-Path $packagesPath) { | ||||
|               Write-Host "Packages directory exists: $packagesPath" | ||||
|               try { | ||||
|                   $info = xmake require --info "cuda 12.6.3" 2>$null | ||||
|                   if ($null -ne $info -and $info -ne "") { | ||||
|                       $cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim() | ||||
|                   } | ||||
|               } catch {} | ||||
|           } else { | ||||
|               Write-Host "Packages directory not found: $packagesPath" | ||||
|               Write-Host "Installing CUDA package..." | ||||
|               xmake require -vy "cuda 12.6.3" | ||||
|               $info = xmake require --info "cuda 12.6.3" | ||||
|               $cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim() | ||||
|           } | ||||
|  | ||||
|           echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV | ||||
|           Write-Host "Resolved CUDA_PATH = $cudaPath" | ||||
|           Pop-Location | ||||
|  | ||||
|       - name: Download INetC plugin | ||||
|         shell: powershell | ||||
|         run: | | ||||
|           $url = "https://github.com/DigitalMediaServer/NSIS-INetC-plugin/releases/download/v1.0.5.7/InetC.zip" | ||||
|           $out = "$env:RUNNER_TEMP\InetC.zip" | ||||
|           Invoke-WebRequest -Uri $url -OutFile $out | ||||
|           Expand-Archive $out -DestinationPath "$env:RUNNER_TEMP\InetC" -Force | ||||
|  | ||||
|           $source = "$env:RUNNER_TEMP\InetC\x86-unicode\INetC.dll" | ||||
|           $target = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\INetC.dll" | ||||
|  | ||||
|           Write-Host "Copying $source to $target" | ||||
|           Copy-Item $source $target -Force | ||||
|  | ||||
|       - name: Checkout code | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: Initialize submodules | ||||
|         run: git submodule update --init --recursive | ||||
|  | ||||
|       - name: Copy nsProcess plugin to NSIS folder | ||||
|         run: | | ||||
|           $nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode" | ||||
|           copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir | ||||
|  | ||||
|       - name: Build CrossDesk | ||||
|         run: | | ||||
|           xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y | ||||
|           xmake b -vy crossdesk | ||||
|   | ||||
|       - name: Decode and save certificate | ||||
|         shell: powershell | ||||
|         run: | | ||||
|           New-Item -ItemType Directory -Force -Path certs | ||||
|           [System.IO.File]::WriteAllBytes('certs\crossdesk.cn_root.crt', [Convert]::FromBase64String('${{ secrets.CROSSDESK_CERT_BASE64 }}')) | ||||
|  | ||||
|       - name: Package | ||||
|         shell: pwsh | ||||
|         run: | | ||||
|           cd "${{ github.workspace }}\scripts\windows" | ||||
|           makensis /DVERSION=$env:VERSION_NUM nsis_script.nsi | ||||
|  | ||||
|       - name: Upload artifact | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: crossdesk-win-x64-${{ env.VERSION_NUM }} | ||||
|           path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe | ||||
|  | ||||
|   release: | ||||
|     name: Publish Release | ||||
|     if: startsWith(github.ref, 'refs/tags/v') | ||||
|     needs: | ||||
|       [build-linux-amd64, build-linux-arm64, build-macos, build-windows-x64] | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: Download all artifacts | ||||
|         uses: actions/download-artifact@v4 | ||||
|         with: | ||||
|           path: artifacts | ||||
|  | ||||
|       - name: Extract version number | ||||
|         id: version | ||||
|         run: | | ||||
|           VERSION="${GITHUB_REF##*/}" | ||||
|           SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | ||||
|           VERSION_NUM="${VERSION#v}-${SHORT_SHA}" | ||||
|           echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Rename artifacts | ||||
|         run: | | ||||
|           mkdir -p release | ||||
|           cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}.pkg | ||||
|           cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}.pkg | ||||
|           cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}.deb | ||||
|           cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}.deb | ||||
|           cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}.exe | ||||
|  | ||||
|       - name: List release files | ||||
|         run: ls -lh release/ | ||||
|  | ||||
|       - name: Upload to Versioned GitHub Release | ||||
|         uses: softprops/action-gh-release@v2 | ||||
|         with: | ||||
|           tag_name: v${{ steps.version.outputs.VERSION_NUM }} | ||||
|           name: Release v${{ steps.version.outputs.VERSION_NUM }} | ||||
|           draft: false | ||||
|           prerelease: false | ||||
|           files: release/* | ||||
|           generate_release_notes: false | ||||
|           body: | | ||||
|             Binary release only. Source code is not included. | ||||
|  | ||||
|       - name: Create or update 'latest' tag | ||||
|         run: | | ||||
|           git config user.name "github-actions[bot]" | ||||
|           git config user.email "github-actions[bot]@users.noreply.github.com" | ||||
|           git tag -f latest | ||||
|           git push origin latest --force | ||||
|  | ||||
|       - name: Upload to GitHub Release (latest) | ||||
|         uses: softprops/action-gh-release@v2 | ||||
|         with: | ||||
|           tag_name: latest | ||||
|           name: Latest Release | ||||
|           draft: false | ||||
|           prerelease: false | ||||
|           files: release/* | ||||
|           generate_release_notes: false | ||||
|  | ||||
|       - name: Upload artifacts to server | ||||
|         uses: burnett01/rsync-deployments@5.2 | ||||
|         with: | ||||
|           switches: -avzr --progress --delete | ||||
|           path: release/* | ||||
|           remote_path: /var/www/html/downloads/ | ||||
|           remote_host: ${{ secrets.SERVER_HOST }} | ||||
|           remote_user: ${{ secrets.SERVER_USER }} | ||||
|           remote_key: ${{ secrets.SERVER_KEY }} | ||||
							
								
								
									
										67
									
								
								.github/workflows/close-issue.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,67 @@ | ||||
| name: Close Inactive Issues | ||||
|  | ||||
| on: | ||||
|   schedule: | ||||
|     # run every day at midnight | ||||
|     - cron: "0 0 * * *" | ||||
|  | ||||
| jobs: | ||||
|   close_inactive_issues: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check inactive issues and close them | ||||
|         uses: actions/github-script@v6 | ||||
|         with: | ||||
|           script: | | ||||
|             const { data: issues } = await github.rest.issues.listForRepo({ | ||||
|               owner: context.repo.owner, | ||||
|               repo: context.repo.repo, | ||||
|               state: 'open', | ||||
|               per_page: 100, | ||||
|             }); | ||||
|  | ||||
|             const now = new Date().getTime(); | ||||
|             const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days | ||||
|  | ||||
|             for (const issue of issues) { | ||||
|               // skip pull requests (they are also returned by listForRepo) | ||||
|               if (issue.pull_request) continue; | ||||
|  | ||||
|               // skip labeled issues | ||||
|               if (issue.labels.length > 0) { | ||||
|                 console.log(`Skipping issue #${issue.number} (Has labels).`); | ||||
|                 continue; | ||||
|               } | ||||
|  | ||||
|               // fetch comments for this issue | ||||
|               const { data: comments } = await github.rest.issues.listComments({ | ||||
|                 owner: context.repo.owner, | ||||
|                 repo: context.repo.repo, | ||||
|                 issue_number: issue.number, | ||||
|                 per_page: 100, | ||||
|               }); | ||||
|  | ||||
|               // determine the "last activity" time | ||||
|               let lastActivityTime; | ||||
|               if (comments.length > 0) { | ||||
|                 const lastComment = comments[comments.length - 1]; | ||||
|                 lastActivityTime = new Date(lastComment.updated_at).getTime(); | ||||
|               } else { | ||||
|                 lastActivityTime = new Date(issue.created_at).getTime(); | ||||
|               } | ||||
|  | ||||
|               // check inactivity | ||||
|               if (now - lastActivityTime > inactivePeriod) { | ||||
|                 console.log(`Closing inactive issue: #${issue.number} (No recent replies for 7 days)`); | ||||
|                 await github.rest.issues.update({ | ||||
|                   owner: context.repo.owner, | ||||
|                   repo: context.repo.repo, | ||||
|                   issue_number: issue.number, | ||||
|                   state: 'closed', | ||||
|                 }); | ||||
|               } else { | ||||
|                 console.log(`Skipping issue #${issue.number} (Active within 7 days).`); | ||||
|               } | ||||
|             } | ||||
|         env: | ||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
							
								
								
									
										49
									
								
								.github/workflows/update-pages.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | ||||
| name: Update GitHub Pages Downloads | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - "v*" | ||||
|  | ||||
| jobs: | ||||
|   update-pages: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout CrossDesk repo | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: Set version number | ||||
|         id: version | ||||
|         run: | | ||||
|           SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | ||||
|           VERSION_NUM="${GITHUB_REF##*/}" | ||||
|           VERSION_NUM="${VERSION_NUM#v}-${SHORT_SHA}" | ||||
|           echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV | ||||
|           echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Checkout Pages repo | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           repository: kunkundi/kunkundi.github.io | ||||
|           token: ${{ secrets.GH_PAGES_PAT }} | ||||
|           path: pages | ||||
|  | ||||
|       - name: Update download links | ||||
|         run: | | ||||
|           cd pages | ||||
|           sed -E -i "s/crossdesk-win-x64-[0-9]+\.[0-9]+\.[0-9]+\.exe/crossdesk-win-x64-${VERSION_NUM}.exe/g" index.html | ||||
|           sed -E -i "s/crossdesk-macos-x64-[0-9]+\.[0-9]+\.[0-9]+\.pkg/crossdesk-macos-x64-${VERSION_NUM}.pkg/g" index.html | ||||
|           sed -E -i "s/crossdesk-macos-arm64-[0-9]+\.[0-9]+\.[0-9]+\.pkg/crossdesk-macos-arm64-${VERSION_NUM}.pkg/g" index.html | ||||
|           sed -E -i "s/crossdesk-linux-amd64-[0-9]+\.[0-9]+\.[0-9]+\.deb/crossdesk-linux-amd64-${VERSION_NUM}.deb/g" index.html | ||||
|           sed -E -i "s/crossdesk-linux-arm64-[0-9]+\.[0-9]+\.[0-9]+\.deb/crossdesk-linux-arm64-${VERSION_NUM}.deb/g" index.html | ||||
|  | ||||
|       - name: Commit & Push changes | ||||
|         run: | | ||||
|           cd pages | ||||
|           git config user.name "github-actions[bot]" | ||||
|           git config user.email "github-actions[bot]@users.noreply.github.com" | ||||
|           git add index.html | ||||
|           git commit -m "Update download links to v${VERSION_NUM}" || echo "No changes to commit" | ||||
|           git push origin main | ||||
|         env: | ||||
|           VERSION_NUM: ${{ env.VERSION_NUM }} | ||||
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,3 +1,3 @@ | ||||
| [submodule "thirdparty/projectx"] | ||||
| 	path = thirdparty/projectx | ||||
| 	url = git@github.com:dijunkun/projectx.git | ||||
| [submodule "thirdparty/minirtc"] | ||||
| 	path = thirdparty/minirtc | ||||
| 	url = https://github.com/kunkundi/minirtc.git | ||||
|   | ||||
							
								
								
									
										165
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,165 @@ | ||||
|                    GNU LESSER GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
|  | ||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
|  | ||||
|   This version of the GNU Lesser General Public License incorporates | ||||
| the terms and conditions of version 3 of the GNU General Public | ||||
| License, supplemented by the additional permissions listed below. | ||||
|  | ||||
|   0. Additional Definitions. | ||||
|  | ||||
|   As used herein, "this License" refers to version 3 of the GNU Lesser | ||||
| General Public License, and the "GNU GPL" refers to version 3 of the GNU | ||||
| General Public License. | ||||
|  | ||||
|   "The Library" refers to a covered work governed by this License, | ||||
| other than an Application or a Combined Work as defined below. | ||||
|  | ||||
|   An "Application" is any work that makes use of an interface provided | ||||
| by the Library, but which is not otherwise based on the Library. | ||||
| Defining a subclass of a class defined by the Library is deemed a mode | ||||
| of using an interface provided by the Library. | ||||
|  | ||||
|   A "Combined Work" is a work produced by combining or linking an | ||||
| Application with the Library.  The particular version of the Library | ||||
| with which the Combined Work was made is also called the "Linked | ||||
| Version". | ||||
|  | ||||
|   The "Minimal Corresponding Source" for a Combined Work means the | ||||
| Corresponding Source for the Combined Work, excluding any source code | ||||
| for portions of the Combined Work that, considered in isolation, are | ||||
| based on the Application, and not on the Linked Version. | ||||
|  | ||||
|   The "Corresponding Application Code" for a Combined Work means the | ||||
| object code and/or source code for the Application, including any data | ||||
| and utility programs needed for reproducing the Combined Work from the | ||||
| Application, but excluding the System Libraries of the Combined Work. | ||||
|  | ||||
|   1. Exception to Section 3 of the GNU GPL. | ||||
|  | ||||
|   You may convey a covered work under sections 3 and 4 of this License | ||||
| without being bound by section 3 of the GNU GPL. | ||||
|  | ||||
|   2. Conveying Modified Versions. | ||||
|  | ||||
|   If you modify a copy of the Library, and, in your modifications, a | ||||
| facility refers to a function or data to be supplied by an Application | ||||
| that uses the facility (other than as an argument passed when the | ||||
| facility is invoked), then you may convey a copy of the modified | ||||
| version: | ||||
|  | ||||
|    a) under this License, provided that you make a good faith effort to | ||||
|    ensure that, in the event an Application does not supply the | ||||
|    function or data, the facility still operates, and performs | ||||
|    whatever part of its purpose remains meaningful, or | ||||
|  | ||||
|    b) under the GNU GPL, with none of the additional permissions of | ||||
|    this License applicable to that copy. | ||||
|  | ||||
|   3. Object Code Incorporating Material from Library Header Files. | ||||
|  | ||||
|   The object code form of an Application may incorporate material from | ||||
| a header file that is part of the Library.  You may convey such object | ||||
| code under terms of your choice, provided that, if the incorporated | ||||
| material is not limited to numerical parameters, data structure | ||||
| layouts and accessors, or small macros, inline functions and templates | ||||
| (ten or fewer lines in length), you do both of the following: | ||||
|  | ||||
|    a) Give prominent notice with each copy of the object code that the | ||||
|    Library is used in it and that the Library and its use are | ||||
|    covered by this License. | ||||
|  | ||||
|    b) Accompany the object code with a copy of the GNU GPL and this license | ||||
|    document. | ||||
|  | ||||
|   4. Combined Works. | ||||
|  | ||||
|   You may convey a Combined Work under terms of your choice that, | ||||
| taken together, effectively do not restrict modification of the | ||||
| portions of the Library contained in the Combined Work and reverse | ||||
| engineering for debugging such modifications, if you also do each of | ||||
| the following: | ||||
|  | ||||
|    a) Give prominent notice with each copy of the Combined Work that | ||||
|    the Library is used in it and that the Library and its use are | ||||
|    covered by this License. | ||||
|  | ||||
|    b) Accompany the Combined Work with a copy of the GNU GPL and this license | ||||
|    document. | ||||
|  | ||||
|    c) For a Combined Work that displays copyright notices during | ||||
|    execution, include the copyright notice for the Library among | ||||
|    these notices, as well as a reference directing the user to the | ||||
|    copies of the GNU GPL and this license document. | ||||
|  | ||||
|    d) Do one of the following: | ||||
|  | ||||
|        0) Convey the Minimal Corresponding Source under the terms of this | ||||
|        License, and the Corresponding Application Code in a form | ||||
|        suitable for, and under terms that permit, the user to | ||||
|        recombine or relink the Application with a modified version of | ||||
|        the Linked Version to produce a modified Combined Work, in the | ||||
|        manner specified by section 6 of the GNU GPL for conveying | ||||
|        Corresponding Source. | ||||
|  | ||||
|        1) Use a suitable shared library mechanism for linking with the | ||||
|        Library.  A suitable mechanism is one that (a) uses at run time | ||||
|        a copy of the Library already present on the user's computer | ||||
|        system, and (b) will operate properly with a modified version | ||||
|        of the Library that is interface-compatible with the Linked | ||||
|        Version. | ||||
|  | ||||
|    e) Provide Installation Information, but only if you would otherwise | ||||
|    be required to provide such information under section 6 of the | ||||
|    GNU GPL, and only to the extent that such information is | ||||
|    necessary to install and execute a modified version of the | ||||
|    Combined Work produced by recombining or relinking the | ||||
|    Application with a modified version of the Linked Version. (If | ||||
|    you use option 4d0, the Installation Information must accompany | ||||
|    the Minimal Corresponding Source and Corresponding Application | ||||
|    Code. If you use option 4d1, you must provide the Installation | ||||
|    Information in the manner specified by section 6 of the GNU GPL | ||||
|    for conveying Corresponding Source.) | ||||
|  | ||||
|   5. Combined Libraries. | ||||
|  | ||||
|   You may place library facilities that are a work based on the | ||||
| Library side by side in a single library together with other library | ||||
| facilities that are not Applications and are not covered by this | ||||
| License, and convey such a combined library under terms of your | ||||
| choice, if you do both of the following: | ||||
|  | ||||
|    a) Accompany the combined library with a copy of the same work based | ||||
|    on the Library, uncombined with any other library facilities, | ||||
|    conveyed under the terms of this License. | ||||
|  | ||||
|    b) Give prominent notice with the combined library that part of it | ||||
|    is a work based on the Library, and explaining where to find the | ||||
|    accompanying uncombined form of the same work. | ||||
|  | ||||
|   6. Revised Versions of the GNU Lesser General Public License. | ||||
|  | ||||
|   The Free Software Foundation may publish revised and/or new versions | ||||
| of the GNU Lesser General Public License from time to time. Such new | ||||
| versions will be similar in spirit to the present version, but may | ||||
| differ in detail to address new problems or concerns. | ||||
|  | ||||
|   Each version is given a distinguishing version number. If the | ||||
| Library as you received it specifies that a certain numbered version | ||||
| of the GNU Lesser General Public License "or any later version" | ||||
| applies to it, you have the option of following the terms and | ||||
| conditions either of that published version or of any later version | ||||
| published by the Free Software Foundation. If the Library as you | ||||
| received it does not specify a version number of the GNU Lesser | ||||
| General Public License, you may choose any version of the GNU Lesser | ||||
| General Public License ever published by the Free Software Foundation. | ||||
|  | ||||
|   If the Library as you received it specifies that a proxy can decide | ||||
| whether future versions of the GNU Lesser General Public License shall | ||||
| apply, that proxy's public statement of acceptance of any version is | ||||
| permanent authorization for you to choose that version for the | ||||
| Library. | ||||
							
								
								
									
										300
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,300 @@ | ||||
| # 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=9099 \ | ||||
|   -v /path/to/your/certs:/crossdesk-server/certs \ | ||||
|   -v /path/to/your/db:/crossdesk-server/db \ | ||||
|   -v /path/to/your/logs:/crossdesk-server/logs \ | ||||
|   crossdesk/crossdesk-server:latest | ||||
| ``` | ||||
|  | ||||
| 上述命令中,用户需注意的参数如下: | ||||
|  | ||||
| - EXTERNAL_IP:服务器公网 IP , 对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器地址** | ||||
|  | ||||
| - INTERNAL_IP:服务器内网 IP | ||||
|  | ||||
| - CROSSDESK_SERVER_PORT:自托管服务使用的端口,对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口** | ||||
|  | ||||
| - /path/to/your/certs:证书文件目录 | ||||
|  | ||||
| - /path/to/your/db:CrossDesk Server 设备管理数据库 | ||||
|  | ||||
| - /path/to/your/logs:日志目录 | ||||
|  | ||||
| **注意**: | ||||
| - **/path/to/your/ 是示例路径,请替换为你自己的实际路径。挂载的目录必须事先创建好,否则容器会报错。** | ||||
| - **服务器需开放端口:3478/udp,3478/tcp,30000-60000/udp,CROSSDESK_SERVER_PORT/tcp,443/tcp。** | ||||
|  | ||||
| ## 证书文件 | ||||
| 客户端需加载根证书文件,服务端需加载服务器私钥和服务器证书文件。 | ||||
|  | ||||
| 如果已有SSL证书的用户,可以忽略下面的证书生成步骤。 | ||||
|  | ||||
| 对于无证书的用户,可使用下面的脚本自行生成证书文件: | ||||
| ``` | ||||
| # 创建证书生成脚本 | ||||
| vim generate_certs.sh | ||||
| ``` | ||||
| 拷贝到脚本中 | ||||
| ``` | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| # 检查参数 | ||||
| if [ "$#" -ne 1 ]; then | ||||
|     echo "Usage: $0 <SERVER_IP>" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| SERVER_IP="$1" | ||||
|  | ||||
| # 文件名 | ||||
| ROOT_KEY="crossdesk.cn_root.key" | ||||
| ROOT_CERT="crossdesk.cn_root.crt" | ||||
| SERVER_KEY="crossdesk.cn.key" | ||||
| SERVER_CSR="crossdesk.cn.csr" | ||||
| SERVER_CERT="crossdesk.cn_bundle.crt" | ||||
| FULLCHAIN_CERT="crossdesk.cn_fullchain.crt" | ||||
|  | ||||
| # 证书主题 | ||||
| SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP" | ||||
|  | ||||
| # 1. 生成根证书 | ||||
| echo "Generating root private key..." | ||||
| openssl genrsa -out "$ROOT_KEY" 4096 | ||||
|  | ||||
| echo "Generating self-signed root certificate..." | ||||
| openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ" | ||||
|  | ||||
| # 2. 生成服务器私钥 | ||||
| echo "Generating server private key..." | ||||
| openssl genrsa -out "$SERVER_KEY" 2048 | ||||
|  | ||||
| # 3. 生成服务器 CSR | ||||
| echo "Generating server CSR..." | ||||
| openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ" | ||||
|  | ||||
| # 4. 生成临时 OpenSSL 配置文件,加入 SAN | ||||
| SAN_CONF="san.cnf" | ||||
| cat > $SAN_CONF <<EOL | ||||
| [ req ] | ||||
| default_bits = 2048 | ||||
| distinguished_name = req_distinguished_name | ||||
| req_extensions = req_ext | ||||
| prompt = no | ||||
|  | ||||
| [ req_distinguished_name ] | ||||
| C = CN | ||||
| ST = Zhejiang | ||||
| L = Hangzhou | ||||
| O = CrossDesk | ||||
| OU = CrossDesk | ||||
| CN = $SERVER_IP | ||||
|  | ||||
| [ req_ext ] | ||||
| subjectAltName = IP:$SERVER_IP | ||||
| EOL | ||||
|  | ||||
| # 5. 用根证书签发服务器证书(包含 SAN) | ||||
| echo "Signing server certificate with root certificate..." | ||||
| openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \ | ||||
|   -out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext | ||||
|  | ||||
| # 6. 生成完整链证书 | ||||
| cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT" | ||||
|  | ||||
| # 7. 清理中间文件 | ||||
| rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT" | ||||
|  | ||||
| echo "Generation complete. Deployment files:" | ||||
| echo "  Client root certificate: $ROOT_CERT" | ||||
| echo "  Server private key: $SERVER_KEY" | ||||
| echo "  Server certificate: $SERVER_CERT" | ||||
| ``` | ||||
| 执行 | ||||
| ``` | ||||
| chmod +x generate_certs.sh | ||||
| ./generate_certs.sh 服务器公网IP | ||||
|  | ||||
| # 例如 ./generate_certs.sh 111.111.111.111 | ||||
| ``` | ||||
| 输出如下: | ||||
| ``` | ||||
| Generating root private key... | ||||
| Generating self-signed root certificate... | ||||
| Generating server private key... | ||||
| Generating server CSR... | ||||
| Signing server certificate with root certificate... | ||||
| Certificate request self-signature ok | ||||
| subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx | ||||
| cleaning up intermediate files... | ||||
| Generation complete. Deployment files:: | ||||
|   Client root certificate:: crossdesk.cn_root.crt | ||||
|   Server private key: crossdesk.cn.key | ||||
|   Server certificate: crossdesk.cn_bundle.crt | ||||
| ``` | ||||
|  | ||||
| #### 服务端 | ||||
| 将 **crossdesk.cn.key** 和 **crossdesk.cn_bundle.crt** 放置到 **/path/to/your/certs** 目录下。 | ||||
|  | ||||
| #### 客户端 | ||||
| 1. 点击右上角设置进入设置页面。<br> | ||||
| <img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br> | ||||
|  | ||||
| 3. 点击点击**自托管服务器配置**。<br><br> | ||||
| <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br> | ||||
|  | ||||
| 5. 在**证书文件路径**选择框中找到 **crossdesk.cn_root.crt** 的存放路径,选中 **crossdesk.cn_root.crt**,点击确认。<br><br> | ||||
| <img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br> | ||||
|  | ||||
| 7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br> | ||||
| <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br> | ||||
|  | ||||
| # 常见问题 | ||||
| 见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。 | ||||
							
								
								
									
										305
									
								
								README_EN.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,305 @@ | ||||
| # CrossDesk | ||||
|  | ||||
| []() | ||||
| [](https://www.gnu.org/licenses/lgpl-3.0) | ||||
| [](https://github.com/kunkundi/crossdesk/commits/self-hosted-server) | ||||
| [](https://github.com/kunkundi/crossdesk/actions)   | ||||
| [](https://hub.docker.com/r/crossdesk/crossdesk-server/tags) | ||||
| []() | ||||
| []() | ||||
| []() | ||||
|  | ||||
| [ [中文](README.md) / English ] | ||||
|  | ||||
|  | ||||
|  | ||||
| # Intro | ||||
|  | ||||
| CrossDesk is a lightweight cross-platform remote desktop software. | ||||
|  | ||||
| CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)). | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| Enter the remote desktop ID in the menu bar’s “Remote ID” field and click “→” to initiate a remote connection. | ||||
|  | ||||
|  | ||||
|  | ||||
| If the remote desktop requires a connection password, you must enter the correct password on your side to successfully establish the connection. | ||||
|  | ||||
|  | ||||
|  | ||||
| Before connecting, you can customize configuration options in the settings, such as language and video encoding format. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## How to build | ||||
|  | ||||
| Requirements: | ||||
| - [xmake](https://xmake.io/#/guide/installation) | ||||
| - [cmake](https://cmake.org/download/) | ||||
|  | ||||
| Following packages need to be installed on Linux: | ||||
|  | ||||
| ``` | ||||
| sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev | ||||
| ``` | ||||
|  | ||||
| Build: | ||||
| ``` | ||||
| git clone https://github.com/kunkundi/crossdesk.git | ||||
|  | ||||
| cd crossdesk | ||||
|  | ||||
| git submodule init  | ||||
|  | ||||
| git submodule update | ||||
|  | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| Run: | ||||
| ``` | ||||
| xmake r crossdesk | ||||
| ``` | ||||
|  | ||||
| #### Development Without CUDA Environment | ||||
|  | ||||
| For **Linux developers who do not have a CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.   | ||||
| This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup. | ||||
|  | ||||
| After entering the container, download the project and run: | ||||
| ``` | ||||
| export CUDA_PATH=/usr/local/cuda | ||||
| export XMAKE_GLOBALDIR=/data | ||||
|  | ||||
| xmake b --root -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment: | ||||
| ``` | ||||
| xmake require -vy "cuda 12.6.3" | ||||
| ``` | ||||
| After the installation is complete, execute: | ||||
| ``` | ||||
| xmake require --info "cuda 12.6.3" | ||||
| ``` | ||||
| The output will look like this: | ||||
|  | ||||
| <img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" /> | ||||
|  | ||||
| From the output above, locate the CUDA installation directory — this is the path pointed to by installdir. | ||||
| Add this path to your system environment variable CUDA_PATH, or set it in the terminal using: | ||||
| ``` | ||||
| set CUDA_PATH=path_to_cuda_installdir: | ||||
| ``` | ||||
| Then re-run: | ||||
| ``` | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| #### Notice | ||||
| If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment. | ||||
|  | ||||
| <img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" /> | ||||
|  | ||||
| ## About Xmake | ||||
| #### Installing Xmake | ||||
|  | ||||
| You can install Xmake using one of the following methods: | ||||
|  | ||||
| Using curl: | ||||
| ``` | ||||
| curl -fsSL https://xmake.io/shget.text | bash | ||||
| ``` | ||||
| Using wget: | ||||
| ``` | ||||
| wget https://xmake.io/shget.text -O - | bash | ||||
| ``` | ||||
| Using powershell: | ||||
| ``` | ||||
| irm https://xmake.io/psget.text | iex | ||||
| ``` | ||||
|  | ||||
| #### Build Options | ||||
| ``` | ||||
| # Switch build mode | ||||
| xmake f -m debug/release | ||||
|  | ||||
| # Optional build parameters | ||||
| -r : Rebuild the target | ||||
| -v : Show detailed build logs | ||||
| -y : Automatically confirm prompts | ||||
|  | ||||
| # Example | ||||
| xmake b -vy crossdesk | ||||
| ``` | ||||
|  | ||||
| #### Run Options | ||||
| ``` | ||||
| # Run in debug mode | ||||
| xmake r -d crossdesk | ||||
| ``` | ||||
|  | ||||
| For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) . | ||||
|  | ||||
| ## Self-Hosted Server | ||||
| It is recommended to deploy CrossDesk Server using Docker. | ||||
| ``` | ||||
| sudo docker run -d \ | ||||
|   --name crossdesk_server \ | ||||
|   --network host \ | ||||
|   -e EXTERNAL_IP=150.158.81.30 \ | ||||
|   -e INTERNAL_IP=10.0.4.3 \ | ||||
|   -e CROSSDESK_SERVER_PORT=9099 \ | ||||
|   -v /path/to/your/certs:/crossdesk-server/certs \ | ||||
|   -v /path/to/your/db:/crossdesk-server/db \ | ||||
|   -v /path/to/your/logs:/crossdesk-server/logs \ | ||||
|   crossdesk/crossdesk-server:latest | ||||
| ``` | ||||
|  | ||||
| The parameters you need to pay attention to are as follows: | ||||
|  | ||||
| - **EXTERNAL_IP**: The server's public IP, corresponding to the **Server Address** in the CrossDesk client **Self-Hosted Server Configuration**. | ||||
|  | ||||
| - **INTERNAL_IP**: The server's internal IP. | ||||
|  | ||||
| - **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**. | ||||
|  | ||||
| - **/path/to/your/certs**: Directory for certificate files. | ||||
|  | ||||
| - **/path/to/your/db**: CrossDesk Server device management database. | ||||
|  | ||||
| - **/path/to/your/logs**: Log directory. | ||||
|  | ||||
| **Note**:   | ||||
| - **/path/to/your/ is an example path; please replace it with your actual path. The mounted directories must be created in advance, otherwise the container will fail.** | ||||
| - **The server must open the following ports: 3478/udp, 3478/tcp, 30000-60000/udp, CROSSDESK_SERVER_PORT/tcp, 443/tcp.** | ||||
|  | ||||
| ## Certificate Files | ||||
| The client needs to load the root certificate, and the server needs to load the server private key and server certificate. | ||||
|  | ||||
| If you already have an SSL certificate, you can skip the following certificate generation steps. | ||||
|  | ||||
| For users without a certificate, you can use the script below to generate the certificate files: | ||||
| ``` | ||||
| # Create certificate generation script | ||||
| vim generate_certs.sh | ||||
| ``` | ||||
| Copy the following into the script: | ||||
| ``` | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| # Check arguments | ||||
| if [ "$#" -ne 1 ]; then | ||||
|     echo "Usage: $0 <SERVER_IP>" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| SERVER_IP="$1" | ||||
|  | ||||
| # Filenames | ||||
| ROOT_KEY="crossdesk.cn_root.key" | ||||
| ROOT_CERT="crossdesk.cn_root.crt" | ||||
| SERVER_KEY="crossdesk.cn.key" | ||||
| SERVER_CSR="crossdesk.cn.csr" | ||||
| SERVER_CERT="crossdesk.cn_bundle.crt" | ||||
| FULLCHAIN_CERT="crossdesk.cn_fullchain.crt" | ||||
|  | ||||
| # Certificate subject | ||||
| SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP" | ||||
|  | ||||
| # 1. Generate root certificate | ||||
| echo "Generating root private key..." | ||||
| openssl genrsa -out "$ROOT_KEY" 4096 | ||||
|  | ||||
| echo "Generating self-signed root certificate..." | ||||
| openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ" | ||||
|  | ||||
| # 2. Generate server private key | ||||
| echo "Generating server private key..." | ||||
| openssl genrsa -out "$SERVER_KEY" 2048 | ||||
|  | ||||
| # 3. Generate server CSR | ||||
| echo "Generating server CSR..." | ||||
| openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ" | ||||
|  | ||||
| # 4. Create temporary OpenSSL config file with SAN | ||||
| SAN_CONF="san.cnf" | ||||
| cat > $SAN_CONF <<EOL | ||||
| [ req ] | ||||
| default_bits = 2048 | ||||
| distinguished_name = req_distinguished_name | ||||
| req_extensions = req_ext | ||||
| prompt = no | ||||
|  | ||||
| [ req_distinguished_name ] | ||||
| C = CN | ||||
| ST = Zhejiang | ||||
| L = Hangzhou | ||||
| O = CrossDesk | ||||
| OU = CrossDesk | ||||
| CN = $SERVER_IP | ||||
|  | ||||
| [ req_ext ] | ||||
| subjectAltName = IP:$SERVER_IP | ||||
| EOL | ||||
|  | ||||
| # 5. Sign server certificate with root certificate (including SAN) | ||||
| echo "Signing server certificate with root certificate..." | ||||
| openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \ | ||||
|   -out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext | ||||
|  | ||||
| # 6. Generate full chain certificate | ||||
| cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT" | ||||
|  | ||||
| # 7. Clean up intermediate files | ||||
| rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT" | ||||
|  | ||||
| echo "Generation complete. Deployment files:" | ||||
| echo "  Client root certificate: $ROOT_CERT" | ||||
| echo "  Server private key: $SERVER_KEY" | ||||
| echo "  Server certificate: $SERVER_CERT" | ||||
| ``` | ||||
| Execute: | ||||
| ``` | ||||
| chmod +x generate_certs.sh | ||||
| ./generate_certs.sh EXTERNAL_IP | ||||
|  | ||||
| # example ./generate_certs.sh 111.111.111.111 | ||||
| ``` | ||||
| Expected output: | ||||
| ``` | ||||
| Generating root private key... | ||||
| Generating self-signed root certificate... | ||||
| Generating server private key... | ||||
| Generating server CSR... | ||||
| Signing server certificate with root certificate... | ||||
| Certificate request self-signature ok | ||||
| subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx | ||||
| cleaning up intermediate files... | ||||
| Generation complete. Deployment files:: | ||||
|   Client root certificate:: crossdesk.cn_root.crt | ||||
|   Server private key: crossdesk.cn.key | ||||
|   Server certificate: crossdesk.cn_bundle.crt | ||||
| ``` | ||||
|  | ||||
| #### Server Side | ||||
| Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory. | ||||
|  | ||||
| #### Client Side | ||||
| 1. Click the settings icon in the top-right corner to enter the settings page.<br> | ||||
| <img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br> | ||||
|  | ||||
| 2. Click **Self-Hosted Server Configuration**.<br><br> | ||||
| <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br> | ||||
|  | ||||
| 3. In the **Certificate File Path** selection, locate and select the **crossdesk.cn_root.crt** file.<br><br> | ||||
| <img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br> | ||||
|  | ||||
| 4. Check the option to use **Self-Hosted Server Configuration**.<br><br> | ||||
| <img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br> | ||||
|  | ||||
| # FAQ | ||||
| See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) . | ||||
| @@ -1,16 +0,0 @@ | ||||
| [signal server] | ||||
| ip = 120.77.216.215 | ||||
| port = 9099 | ||||
|  | ||||
| [stun server] | ||||
| ip = 120.77.216.215 | ||||
| port = 3478 | ||||
|  | ||||
| [turn server] | ||||
| ip = 120.77.216.215 | ||||
| port = 3478 | ||||
| username = dijunkun | ||||
| password = dijunkunpw | ||||
|  | ||||
| [hardware acceleration] | ||||
| turn_on = true | ||||
							
								
								
									
										33
									
								
								docs/FAQ.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | ||||
| # 常见问题(FAQ) | ||||
|  | ||||
| 欢迎来到 **CrossDesk 常见问题** 页面!   | ||||
| 这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。 | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Q1. 对等连接失败 | ||||
| **A:**   | ||||
| 打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。 | ||||
|  | ||||
| <img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" /> | ||||
|  | ||||
| 由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8) | ||||
|  | ||||
| ### Q2. Windows 无 CUDA 环境下编译 | ||||
| **A:**   | ||||
| 运行下面的命令安装 CUDA 编译环境。 | ||||
| ``` | ||||
| xmake require -vy "cuda 12.6.3" | ||||
| ``` | ||||
| 安装完成后执行 | ||||
| ``` | ||||
| xmake require --info "cuda 12.6.3" | ||||
| ``` | ||||
| 输出如下 | ||||
|  | ||||
| <img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" /> | ||||
|  | ||||
| 根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir,重新执行 xmake b -vy crossdesk 即可。 | ||||
| [Issue #6](https://github.com/kunkundi/crossdesk/issues/6) | ||||
|  | ||||
| --- | ||||
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 746 B | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_24x24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_256x256.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_48x48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_64x64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/linux/crossdesk_96x96.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icons/macos/crossdesk.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								icons/windows/crossdesk.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 84 KiB | 
							
								
								
									
										120
									
								
								scripts/linux/pkg_amd64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,120 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| PKG_NAME="crossdesk" | ||||
| APP_NAME="CrossDesk" | ||||
|  | ||||
| APP_VERSION="$1" | ||||
| ARCHITECTURE="amd64" | ||||
| MAINTAINER="Junkun Di <junkun.di@hotmail.com>" | ||||
| DESCRIPTION="A simple cross-platform remote desktop client." | ||||
|  | ||||
| DEB_DIR="${PKG_NAME}-${APP_VERSION}" | ||||
| DEBIAN_DIR="$DEB_DIR/DEBIAN" | ||||
| BIN_DIR="$DEB_DIR/usr/bin" | ||||
| CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs" | ||||
| ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor" | ||||
| DESKTOP_DIR="$DEB_DIR/usr/share/applications" | ||||
|  | ||||
| rm -rf "$DEB_DIR" | ||||
|  | ||||
| mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR" | ||||
|  | ||||
| cp build/linux/x86_64/release/crossdesk "$BIN_DIR/$PKG_NAME" | ||||
| chmod +x "$BIN_DIR/$PKG_NAME" | ||||
|  | ||||
| ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME" | ||||
|  | ||||
| cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt" | ||||
|  | ||||
| for size in 16 24 32 48 64 96 128 256; do | ||||
|     mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps" | ||||
|     cp "icons/linux/crossdesk_${size}x${size}.png" \ | ||||
|        "$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png" | ||||
| done | ||||
|  | ||||
| cat > "$DEBIAN_DIR/control" << EOF | ||||
| Package: $PKG_NAME | ||||
| Version: $APP_VERSION | ||||
| Architecture: $ARCHITECTURE | ||||
| Maintainer: $MAINTAINER | ||||
| Description: $DESCRIPTION | ||||
| Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1, | ||||
|  libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, | ||||
|  libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2, | ||||
|  libsndio7.0, libxcb-shm0, libpulse0 | ||||
| Recommends: nvidia-cuda-toolkit | ||||
| Priority: optional | ||||
| Section: utils | ||||
| EOF | ||||
|  | ||||
| cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF | ||||
| [Desktop Entry] | ||||
| Version=$APP_VERSION | ||||
| Name=$APP_NAME | ||||
| Comment=$DESCRIPTION | ||||
| Exec=/usr/bin/$PKG_NAME | ||||
| Icon=$PKG_NAME | ||||
| Terminal=false | ||||
| Type=Application | ||||
| Categories=Utility; | ||||
| EOF | ||||
|  | ||||
| cat > "$DEBIAN_DIR/postrm" << EOF | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then | ||||
|     rm -f /usr/bin/$PKG_NAME || true | ||||
|     rm -f /usr/bin/$APP_NAME || true | ||||
|     rm -f /usr/share/applications/$PKG_NAME.desktop || true | ||||
|     rm -rf /opt/$PKG_NAME/certs || true | ||||
|     for size in 16 24 32 48 64 96 128 256; do | ||||
|         rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true | ||||
|     done | ||||
| fi | ||||
|  | ||||
| exit 0 | ||||
| EOF | ||||
| chmod +x "$DEBIAN_DIR/postrm" | ||||
|  | ||||
| cat > "$DEBIAN_DIR/postinst" << 'EOF' | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| CERT_SRC="/opt/crossdesk/certs" | ||||
| CERT_FILE="crossdesk.cn_root.crt" | ||||
|  | ||||
| for user_home in /home/*; do | ||||
|     [ -d "$user_home" ] || continue | ||||
|     username=$(basename "$user_home") | ||||
|     config_dir="$user_home/.config/CrossDesk/certs" | ||||
|     target="$config_dir/$CERT_FILE" | ||||
|  | ||||
|     if [ ! -f "$target" ]; then | ||||
|         mkdir -p "$config_dir" || true | ||||
|         cp "$CERT_SRC/$CERT_FILE" "$target" || true | ||||
|         chown -R "$username:$username" "$user_home/.config/CrossDesk" || true | ||||
|         echo "✔ Installed cert for $username at $target" | ||||
|     fi | ||||
| done | ||||
|  | ||||
| if [ -d "/root" ]; then | ||||
|     config_dir="/root/.config/CrossDesk/certs" | ||||
|     mkdir -p "$config_dir" || true | ||||
|     cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true | ||||
|     chown -R root:root /root/.config/CrossDesk || true | ||||
| fi | ||||
|  | ||||
| exit 0 | ||||
| EOF | ||||
| chmod +x "$DEBIAN_DIR/postinst" | ||||
|  | ||||
| dpkg-deb --build "$DEB_DIR" | ||||
|  | ||||
| OUTPUT_FILE="${PKG_NAME}-linux-${ARCHITECTURE}-${APP_VERSION}.deb" | ||||
| mv "$DEB_DIR.deb" "$OUTPUT_FILE" | ||||
|  | ||||
| rm -rf "$DEB_DIR" | ||||
|  | ||||
| echo "✅ Deb package created: $OUTPUT_FILE" | ||||
							
								
								
									
										120
									
								
								scripts/linux/pkg_arm64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,120 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| PKG_NAME="crossdesk" | ||||
| APP_NAME="CrossDesk" | ||||
|  | ||||
| APP_VERSION="$1" | ||||
| ARCHITECTURE="arm64" | ||||
| MAINTAINER="Junkun Di <junkun.di@hotmail.com>" | ||||
| DESCRIPTION="A simple cross-platform remote desktop client." | ||||
|  | ||||
| DEB_DIR="${PKG_NAME}-${APP_VERSION}" | ||||
| DEBIAN_DIR="$DEB_DIR/DEBIAN" | ||||
| BIN_DIR="$DEB_DIR/usr/bin" | ||||
| CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs" | ||||
| ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor" | ||||
| DESKTOP_DIR="$DEB_DIR/usr/share/applications" | ||||
|  | ||||
| rm -rf "$DEB_DIR" | ||||
|  | ||||
| mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR" | ||||
|  | ||||
| cp build/linux/arm64/release/crossdesk "$BIN_DIR" | ||||
| chmod +x "$BIN_DIR/$PKG_NAME" | ||||
|  | ||||
| ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME" | ||||
|  | ||||
| cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt" | ||||
|  | ||||
| for size in 16 24 32 48 64 96 128 256; do | ||||
|     mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps" | ||||
|     cp "icons/linux/crossdesk_${size}x${size}.png" \ | ||||
|        "$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png" | ||||
| done | ||||
|  | ||||
| cat > "$DEBIAN_DIR/control" << EOF | ||||
| Package: $PKG_NAME | ||||
| Version: $APP_VERSION | ||||
| Architecture: $ARCHITECTURE | ||||
| Maintainer: $MAINTAINER | ||||
| Description: $DESCRIPTION | ||||
| Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1, | ||||
|  libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, | ||||
|  libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2, | ||||
|  libsndio7.0, libxcb-shm0, libpulse0 | ||||
| Priority: optional | ||||
| Section: utils | ||||
| EOF | ||||
|  | ||||
| cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF | ||||
| [Desktop Entry] | ||||
| Version=$APP_VERSION | ||||
| Name=$APP_NAME | ||||
| Comment=$DESCRIPTION | ||||
| Exec=/usr/bin/$PKG_NAME | ||||
| Icon=$PKG_NAME | ||||
| Terminal=false | ||||
| Type=Application | ||||
| Categories=Utility; | ||||
| EOF | ||||
|  | ||||
| cat > "$DEBIAN_DIR/postrm" << EOF | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then | ||||
|     rm -f /usr/bin/$PKG_NAME || true | ||||
|     rm -f /usr/bin/$APP_NAME || true | ||||
|     rm -f /usr/share/applications/$PKG_NAME.desktop || true | ||||
|     rm -rf /opt/$PKG_NAME/certs || true | ||||
|     for size in 16 24 32 48 64 96 128 256; do | ||||
|         rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true | ||||
|     done | ||||
| fi | ||||
|  | ||||
| exit 0 | ||||
| EOF | ||||
| chmod +x "$DEBIAN_DIR/postrm" | ||||
|  | ||||
| cat > "$DEBIAN_DIR/postinst" << 'EOF' | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| CERT_SRC="/opt/crossdesk/certs" | ||||
| CERT_FILE="crossdesk.cn_root.crt" | ||||
|  | ||||
| for user_home in /home/*; do | ||||
|     [ -d "$user_home" ] || continue | ||||
|     username=$(basename "$user_home") | ||||
|     config_dir="$user_home/.config/CrossDesk/certs" | ||||
|     target="$config_dir/$CERT_FILE" | ||||
|  | ||||
|     if [ ! -f "$target" ]; then | ||||
|         mkdir -p "$config_dir" || true | ||||
|         cp "$CERT_SRC/$CERT_FILE" "$target" || true | ||||
|         chown -R "$username:$username" "$user_home/.config/CrossDesk" || true | ||||
|         echo "✔ Installed cert for $username at $target" | ||||
|     fi | ||||
| done | ||||
|  | ||||
| if [ -d "/root" ]; then | ||||
|     config_dir="/root/.config/CrossDesk/certs" | ||||
|     mkdir -p "$config_dir" || true | ||||
|     cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true | ||||
|     chown -R root:root /root/.config/CrossDesk || true | ||||
| fi | ||||
|  | ||||
| exit 0 | ||||
| EOF | ||||
|  | ||||
| chmod +x "$DEBIAN_DIR/postinst" | ||||
|  | ||||
| dpkg-deb --build "$DEB_DIR" | ||||
|  | ||||
| OUTPUT_FILE="crossdesk-linux-arm64-$APP_VERSION.deb" | ||||
| mv "$DEB_DIR.deb" "$OUTPUT_FILE" | ||||
|  | ||||
| rm -rf "$DEB_DIR" | ||||
|  | ||||
| echo "✅ Deb package created: $OUTPUT_FILE" | ||||
							
								
								
									
										125
									
								
								scripts/macosx/pkg_arm64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,125 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| APP_NAME="crossdesk" | ||||
| APP_NAME_UPPER="CrossDesk" | ||||
| EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk" | ||||
| APP_VERSION="$1" | ||||
| PLATFORM="macos" | ||||
| ARCH="arm64" | ||||
| IDENTIFIER="cn.crossdesk.app" | ||||
| ICON_PATH="icons/macos/crossdesk.icns" | ||||
| MACOS_MIN_VERSION="10.12" | ||||
|  | ||||
| CERTS_SOURCE="certs" | ||||
| CERT_NAME="crossdesk.cn_root.crt" | ||||
|  | ||||
| APP_BUNDLE="${APP_NAME_UPPER}.app" | ||||
| CONTENTS_DIR="${APP_BUNDLE}/Contents" | ||||
| MACOS_DIR="${CONTENTS_DIR}/MacOS" | ||||
| RESOURCES_DIR="${CONTENTS_DIR}/Resources" | ||||
|  | ||||
| PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg" | ||||
| DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg" | ||||
| VOL_NAME="Install ${APP_NAME_UPPER}" | ||||
|  | ||||
| echo "delete old files" | ||||
| rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp | ||||
|  | ||||
| mkdir -p build_pkg_temp | ||||
| mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" | ||||
|  | ||||
| cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}" | ||||
| chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}" | ||||
|  | ||||
| if [ -f "${ICON_PATH}" ]; then | ||||
|     cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns" | ||||
|     ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>" | ||||
| else | ||||
|     ICON_KEY="" | ||||
| fi | ||||
|  | ||||
| echo "generate Info.plist" | ||||
| cat > "${CONTENTS_DIR}/Info.plist" <<EOF | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
|     <key>CFBundleName</key> | ||||
|     <string>${APP_NAME_UPPER}</string> | ||||
|     <key>CFBundleDisplayName</key> | ||||
|     <string>${APP_NAME_UPPER}</string> | ||||
|     <key>CFBundleIdentifier</key> | ||||
|     <string>${IDENTIFIER}</string> | ||||
|     <key>CFBundleVersion</key> | ||||
|     <string>${APP_VERSION}</string> | ||||
|     <key>CFBundleShortVersionString</key> | ||||
|     <string>${APP_VERSION}</string> | ||||
|     <key>CFBundleExecutable</key> | ||||
|     <string>${APP_NAME_UPPER}</string> | ||||
|     <key>CFBundlePackageType</key> | ||||
|     <string>APPL</string> | ||||
|     ${ICON_KEY} | ||||
|     <key>LSMinimumSystemVersion</key> | ||||
|     <string>${MACOS_MIN_VERSION}</string> | ||||
|     <key>NSHighResolutionCapable</key> | ||||
|     <true/> | ||||
|     <key>NSCameraUsageDescription</key> | ||||
|     <string>应用需要访问摄像头</string> | ||||
|     <key>NSMicrophoneUsageDescription</key> | ||||
|     <string>应用需要访问麦克风</string> | ||||
|     <key>NSAppleEventsUsageDescription</key> | ||||
|     <string>应用需要发送 Apple 事件</string> | ||||
|     <key>NSScreenCaptureUsageDescription</key> | ||||
|     <string>应用需要录屏权限以捕获屏幕内容</string> | ||||
| </dict> | ||||
| </plist> | ||||
| EOF | ||||
|  | ||||
| echo ".app created successfully." | ||||
|  | ||||
| echo "building pkg..." | ||||
| pkgbuild \ | ||||
|   --identifier "${IDENTIFIER}" \ | ||||
|   --version "${APP_VERSION}" \ | ||||
|   --install-location "/Applications" \ | ||||
|   --component "${APP_BUNDLE}" \ | ||||
|   build_pkg_temp/${APP_NAME}-component.pkg | ||||
|  | ||||
| mkdir -p scripts | ||||
|  | ||||
| cat > scripts/postinstall <<'EOF' | ||||
| #!/bin/bash | ||||
| USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) | ||||
| HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) | ||||
|  | ||||
| DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" | ||||
|  | ||||
| mkdir -p "$DEST" | ||||
| cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" | ||||
|  | ||||
| exit 0 | ||||
| EOF | ||||
|  | ||||
| chmod +x scripts/postinstall | ||||
|  | ||||
| pkgbuild \ | ||||
|   --root "${CERTS_SOURCE}" \ | ||||
|   --identifier "${IDENTIFIER}.certs" \ | ||||
|   --version "${APP_VERSION}" \ | ||||
|   --install-location "/Library/Application Support/CrossDesk/certs" \ | ||||
|   --scripts scripts \ | ||||
|   build_pkg_temp/${APP_NAME}-certs.pkg | ||||
|  | ||||
| productbuild \ | ||||
|   --package build_pkg_temp/${APP_NAME}-component.pkg \ | ||||
|   --package build_pkg_temp/${APP_NAME}-certs.pkg \ | ||||
|   "${PKG_NAME}" | ||||
|  | ||||
| echo "PKG package created: ${PKG_NAME}" | ||||
|  | ||||
| rm -rf build_pkg_temp scripts ${APP_BUNDLE} | ||||
|  | ||||
| echo "PKG package created successfully." | ||||
| echo "package ${APP_BUNDLE}" | ||||
| echo "installer ${PKG_NAME}" | ||||
							
								
								
									
										125
									
								
								scripts/macosx/pkg_x64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,125 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| APP_NAME="crossdesk" | ||||
| APP_NAME_UPPER="CrossDesk" | ||||
| EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk" | ||||
| APP_VERSION="$1" | ||||
| PLATFORM="macos" | ||||
| ARCH="x64" | ||||
| IDENTIFIER="cn.crossdesk.app" | ||||
| ICON_PATH="icons/macos/crossdesk.icns" | ||||
| MACOS_MIN_VERSION="10.12" | ||||
|  | ||||
| CERTS_SOURCE="certs" | ||||
| CERT_NAME="crossdesk.cn_root.crt" | ||||
|  | ||||
| APP_BUNDLE="${APP_NAME_UPPER}.app" | ||||
| CONTENTS_DIR="${APP_BUNDLE}/Contents" | ||||
| MACOS_DIR="${CONTENTS_DIR}/MacOS" | ||||
| RESOURCES_DIR="${CONTENTS_DIR}/Resources" | ||||
|  | ||||
| PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg" | ||||
| DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg" | ||||
| VOL_NAME="Install ${APP_NAME_UPPER}" | ||||
|  | ||||
| echo "delete old files" | ||||
| rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp | ||||
|  | ||||
| mkdir -p build_pkg_temp | ||||
| mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" | ||||
|  | ||||
| cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}" | ||||
| chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}" | ||||
|  | ||||
| if [ -f "${ICON_PATH}" ]; then | ||||
|     cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns" | ||||
|     ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>" | ||||
| else | ||||
|     ICON_KEY="" | ||||
| fi | ||||
|  | ||||
| echo "generate Info.plist" | ||||
| cat > "${CONTENTS_DIR}/Info.plist" <<EOF | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
|     <key>CFBundleName</key> | ||||
|     <string>${APP_NAME_UPPER}</string> | ||||
|     <key>CFBundleDisplayName</key> | ||||
|     <string>${APP_NAME_UPPER}</string> | ||||
|     <key>CFBundleIdentifier</key> | ||||
|     <string>${IDENTIFIER}</string> | ||||
|     <key>CFBundleVersion</key> | ||||
|     <string>${APP_VERSION}</string> | ||||
|     <key>CFBundleShortVersionString</key> | ||||
|     <string>${APP_VERSION}</string> | ||||
|     <key>CFBundleExecutable</key> | ||||
|     <string>${APP_NAME_UPPER}</string> | ||||
|     <key>CFBundlePackageType</key> | ||||
|     <string>APPL</string> | ||||
|     ${ICON_KEY} | ||||
|     <key>LSMinimumSystemVersion</key> | ||||
|     <string>${MACOS_MIN_VERSION}</string> | ||||
|     <key>NSHighResolutionCapable</key> | ||||
|     <true/> | ||||
|     <key>NSCameraUsageDescription</key> | ||||
|     <string>应用需要访问摄像头</string> | ||||
|     <key>NSMicrophoneUsageDescription</key> | ||||
|     <string>应用需要访问麦克风</string> | ||||
|     <key>NSAppleEventsUsageDescription</key> | ||||
|     <string>应用需要发送 Apple 事件</string> | ||||
|     <key>NSScreenCaptureUsageDescription</key> | ||||
|     <string>应用需要录屏权限以捕获屏幕内容</string> | ||||
| </dict> | ||||
| </plist> | ||||
| EOF | ||||
|  | ||||
| echo ".app created successfully." | ||||
|  | ||||
| echo "building pkg..." | ||||
| pkgbuild \ | ||||
|   --identifier "${IDENTIFIER}" \ | ||||
|   --version "${APP_VERSION}" \ | ||||
|   --install-location "/Applications" \ | ||||
|   --component "${APP_BUNDLE}" \ | ||||
|   build_pkg_temp/${APP_NAME}-component.pkg | ||||
|  | ||||
| mkdir -p scripts | ||||
|  | ||||
| cat > scripts/postinstall <<'EOF' | ||||
| #!/bin/bash | ||||
| USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) | ||||
| HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) | ||||
|  | ||||
| DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" | ||||
|  | ||||
| mkdir -p "$DEST" | ||||
| cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" | ||||
|  | ||||
| exit 0 | ||||
| EOF | ||||
|  | ||||
| chmod +x scripts/postinstall | ||||
|  | ||||
| pkgbuild \ | ||||
|   --root "${CERTS_SOURCE}" \ | ||||
|   --identifier "${IDENTIFIER}.certs" \ | ||||
|   --version "${APP_VERSION}" \ | ||||
|   --install-location "/Library/Application Support/CrossDesk/certs" \ | ||||
|   --scripts scripts \ | ||||
|   build_pkg_temp/${APP_NAME}-certs.pkg | ||||
|  | ||||
| productbuild \ | ||||
|   --package build_pkg_temp/${APP_NAME}-component.pkg \ | ||||
|   --package build_pkg_temp/${APP_NAME}-certs.pkg \ | ||||
|   "${PKG_NAME}" | ||||
|  | ||||
| echo "PKG package created: ${PKG_NAME}" | ||||
|  | ||||
| rm -rf build_pkg_temp scripts ${APP_BUNDLE} | ||||
|  | ||||
| echo "PKG package created successfully." | ||||
| echo "package ${APP_BUNDLE}" | ||||
| echo "installer ${PKG_NAME}" | ||||
							
								
								
									
										43
									
								
								scripts/windows/crossdesk.manifest
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,43 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
| <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||||
|  | ||||
|   <!-- 应用程序标识 --> | ||||
|   <assemblyIdentity | ||||
|       version="1.0.0.0" | ||||
|       processorArchitecture="*" | ||||
|       name="CrossDesk" | ||||
|       type="win32" /> | ||||
|  | ||||
|   <!-- 描述信息 --> | ||||
|   <description>CrossDesk Application</description> | ||||
|  | ||||
|   <!-- 权限:要求管理员运行 --> | ||||
|   <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> | ||||
|     <security> | ||||
|       <requestedPrivileges> | ||||
|         <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> | ||||
|       </requestedPrivileges> | ||||
|     </security> | ||||
|   </trustInfo> | ||||
|  | ||||
|   <!-- DPI 感知设置:支持高分屏 --> | ||||
|   <application xmlns="urn:schemas-microsoft-com:asm.v3"> | ||||
|     <windowsSettings> | ||||
|       <!-- Windows Vista/7 风格 DPI 感知 --> | ||||
|       <dpiAware>true/pm</dpiAware> | ||||
|       <!-- Windows 10/11 高级 DPI 感知 --> | ||||
|       <dpiAwareness>PerMonitorV2</dpiAwareness> | ||||
|     </windowsSettings> | ||||
|   </application> | ||||
|  | ||||
|   <!-- Windows 兼容性声明 --> | ||||
|   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> | ||||
|     <application> | ||||
|       <!-- 支持 Windows 10 --> | ||||
|       <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> | ||||
|       <!-- 支持 Windows 11(向下兼容 Win10 GUID) --> | ||||
|       <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> | ||||
|     </application> | ||||
|   </compatibility> | ||||
|  | ||||
| </assembly> | ||||
							
								
								
									
										
											BIN
										
									
								
								scripts/windows/nsProcess.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										164
									
								
								scripts/windows/nsis_script.nsi
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,164 @@ | ||||
| ; Set search path | ||||
| !addincludedir "${__FILEDIR__}" | ||||
|  | ||||
| ; Installer initial constants | ||||
| !define PRODUCT_NAME "CrossDesk" | ||||
| !define PRODUCT_VERSION "${VERSION}" | ||||
| !define PRODUCT_PUBLISHER "CrossDesk" | ||||
| !define PRODUCT_WEB_SITE "https://www.crossdesk.cn/" | ||||
| !define APP_NAME "CrossDesk" | ||||
| !define UNINSTALL_REG_KEY "CrossDesk" | ||||
|  | ||||
| ; Installer icon path | ||||
| !define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico" | ||||
|  | ||||
| ; Certificate path | ||||
| !define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt" | ||||
|  | ||||
| ; Compression settings | ||||
| SetCompressor /FINAL lzma | ||||
|  | ||||
| ; Request admin privileges (needed to write HKLM) | ||||
| RequestExecutionLevel admin | ||||
|  | ||||
| ; ------ MUI Modern UI Definition ------ | ||||
| !include "MUI.nsh" | ||||
| !define MUI_ABORTWARNING | ||||
| !insertmacro MUI_PAGE_WELCOME | ||||
| !insertmacro MUI_PAGE_DIRECTORY | ||||
| !insertmacro MUI_PAGE_INSTFILES | ||||
|  | ||||
| ; Add run-after-install option | ||||
| !define MUI_FINISHPAGE_RUN | ||||
| !define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}" | ||||
| !define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp | ||||
|  | ||||
| !insertmacro MUI_PAGE_FINISH | ||||
| !insertmacro MUI_LANGUAGE "SimpChinese" | ||||
| !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS | ||||
| ; ------ End of MUI Definition ------ | ||||
|  | ||||
| ; Include LogicLib for process handling | ||||
| !include "LogicLib.nsh" | ||||
|  | ||||
| Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" | ||||
| OutFile "crossdesk-win-x64-${PRODUCT_VERSION}.exe" | ||||
| InstallDir "$PROGRAMFILES\CrossDesk" | ||||
| InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" "InstallDir" | ||||
| ShowInstDetails show | ||||
|  | ||||
| Section "MainSection" | ||||
|     ; Check if CrossDesk is running | ||||
|     StrCpy $1 "crossdesk.exe" | ||||
|      | ||||
|     nsProcess::_FindProcess "$1" | ||||
|     Pop $R0 | ||||
|     ${If} $R0 = 0  ; | ||||
|         MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and continue the installation?" IDYES closeApp IDNO cancelInstall | ||||
|     ${Else} | ||||
|         Goto installApp | ||||
|     ${EndIf} | ||||
|  | ||||
| closeApp: | ||||
|     nsProcess::_KillProcess "$1" | ||||
|     Pop $R0 | ||||
|     Sleep 500 | ||||
|     Goto installApp | ||||
|  | ||||
| cancelInstall: | ||||
|     SetDetailsPrint both | ||||
|     MessageBox MB_ICONEXCLAMATION|MB_OK "Installation has been aborted." | ||||
|     Abort | ||||
|  | ||||
| installApp: | ||||
|     SetOutPath "$INSTDIR" | ||||
|     SetOverwrite ifnewer | ||||
|  | ||||
|     ; Main application executable path | ||||
|     File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe" | ||||
|      | ||||
|     ; Copy icon file to installation directory | ||||
|     File "${MUI_ICON}" | ||||
|  | ||||
|     ; Write uninstall information | ||||
|     WriteUninstaller "$INSTDIR\uninstall.exe" | ||||
|  | ||||
|     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}" | ||||
|     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe" | ||||
|     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" | ||||
|     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" | ||||
|     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}" | ||||
|     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\crossdesk.ico" | ||||
|     WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1 | ||||
|     WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1 | ||||
|     WriteRegStr HKCU "Software\${PRODUCT_NAME}" "InstallDir" "$INSTDIR" | ||||
| SectionEnd | ||||
|  | ||||
| ; After installation | ||||
| Section -Post | ||||
|     ExecWait '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\mt.exe" -manifest "$INSTDIR\crossdesk.manifest" -outputresource:"$INSTDIR\crossdesk.exe";1' | ||||
| SectionEnd | ||||
|  | ||||
| Section "Cert" | ||||
|     SetOutPath "$APPDATA\CrossDesk\certs" | ||||
|     File /r "${CERT_FILE}" | ||||
| SectionEnd | ||||
|  | ||||
| Section -AdditionalIcons | ||||
|     ; Desktop shortcut | ||||
|     CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico" | ||||
|  | ||||
|     ; Start menu shortcut | ||||
|     CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico" | ||||
| SectionEnd | ||||
|  | ||||
| Section "Uninstall" | ||||
|     ; Check if CrossDesk is running | ||||
|     StrCpy $1 "crossdesk.exe" | ||||
|      | ||||
|     nsProcess::_FindProcess "$1" | ||||
|     Pop $R0 | ||||
|     ${If} $R0 = 0 | ||||
|         MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and uninstall?" IDYES closeApp IDNO cancelUninstall | ||||
|     ${Else} | ||||
|         Goto uninstallApp | ||||
|     ${EndIf} | ||||
|  | ||||
| closeApp: | ||||
|     nsProcess::_KillProcess "$1" | ||||
|     Pop $R0 | ||||
|     Sleep 500 | ||||
|     Goto uninstallApp | ||||
|  | ||||
| cancelUninstall: | ||||
|     SetDetailsPrint both | ||||
|     MessageBox MB_ICONEXCLAMATION|MB_OK "Uninstallation has been aborted." | ||||
|     Abort | ||||
|  | ||||
| uninstallApp: | ||||
|     ; Delete main executable and uninstaller | ||||
|     Delete "$INSTDIR\crossdesk.exe" | ||||
|     Delete "$INSTDIR\uninstall.exe" | ||||
|  | ||||
|     ; Recursively delete installation directory | ||||
|     RMDir /r "$INSTDIR" | ||||
|  | ||||
|     ; Delete desktop and start menu shortcuts | ||||
|     Delete "$DESKTOP\${PRODUCT_NAME}.lnk" | ||||
|     Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk" | ||||
|  | ||||
|     ; Delete registry uninstall entry | ||||
|     DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" | ||||
|  | ||||
|     ; Delete remembered install dir | ||||
|     DeleteRegKey HKCU "Software\${PRODUCT_NAME}" | ||||
|  | ||||
|     ; Recursively delete CrossDesk folder in user AppData | ||||
|     RMDir /r "$APPDATA\CrossDesk" | ||||
|     RMDir /r "$LOCALAPPDATA\CrossDesk" | ||||
| SectionEnd | ||||
|  | ||||
| ; ------ Functions ------ | ||||
| Function LaunchApp | ||||
|     Exec "$INSTDIR\crossdesk.exe" | ||||
| FunctionEnd | ||||
							
								
								
									
										17
									
								
								src/app/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,17 @@ | ||||
| #ifdef _WIN32 | ||||
| #ifdef DESK_PORT_DEBUG | ||||
| #pragma comment(linker, "/subsystem:\"console\"") | ||||
| #else | ||||
| #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) { | ||||
|   Render render; | ||||
|   render.Run(); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/common/display_info.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2025-05-15 | ||||
|  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _DISPLAY_INFO_H_ | ||||
| #define _DISPLAY_INFO_H_ | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| class DisplayInfo { | ||||
|  public: | ||||
|   DisplayInfo(std::string name, int left, int top, int right, int bottom) | ||||
|       : name(name), left(left), top(top), right(right), bottom(bottom) { | ||||
|     width = right - left; | ||||
|     height = bottom - top; | ||||
|   } | ||||
|   DisplayInfo(void* handle, std::string name, bool is_primary, int left, | ||||
|               int top, int right, int bottom) | ||||
|       : handle(handle), | ||||
|         name(name), | ||||
|         is_primary(is_primary), | ||||
|         left(left), | ||||
|         top(top), | ||||
|         right(right), | ||||
|         bottom(bottom) { | ||||
|     width = right - left; | ||||
|     height = bottom - top; | ||||
|   } | ||||
|   ~DisplayInfo() {} | ||||
|  | ||||
|   void* handle = nullptr; | ||||
|   std::string name = ""; | ||||
|   bool is_primary = false; | ||||
|   int left = 0; | ||||
|   int top = 0; | ||||
|   int right = 0; | ||||
|   int bottom = 0; | ||||
|   int width = 0; | ||||
|   int height = 0; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										125
									
								
								src/common/platform.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,125 @@ | ||||
| #include "platform.h" | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <Winsock2.h> | ||||
| #include <iphlpapi.h> | ||||
| #elif __APPLE__ | ||||
| #include <ifaddrs.h> | ||||
| #include <net/if_dl.h> | ||||
| #include <net/if_types.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/types.h> | ||||
| #elif __linux__ | ||||
| #include <fcntl.h> | ||||
| #include <net/if.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <sys/socket.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
|  | ||||
| std::string GetMac() { | ||||
|   char mac_addr[16]; | ||||
|   int len = 0; | ||||
| #ifdef _WIN32 | ||||
|   IP_ADAPTER_INFO adapterInfo[16]; | ||||
|   DWORD bufferSize = sizeof(adapterInfo); | ||||
|   DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize); | ||||
|   if (result == ERROR_SUCCESS) { | ||||
|     PIP_ADAPTER_INFO adapter = adapterInfo; | ||||
|     while (adapter) { | ||||
|       for (UINT i = 0; i < adapter->AddressLength; i++) { | ||||
|         len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X", | ||||
|                          adapter->Address[i]); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| #elif __APPLE__ | ||||
|   std::string if_name = "en0"; | ||||
|  | ||||
|   struct ifaddrs *addrs; | ||||
|   struct ifaddrs *cursor; | ||||
|   const struct sockaddr_dl *dlAddr; | ||||
|  | ||||
|   if (!getifaddrs(&addrs)) { | ||||
|     cursor = addrs; | ||||
|     while (cursor != 0) { | ||||
|       const struct sockaddr_dl *socAddr = | ||||
|           (const struct sockaddr_dl *)cursor->ifa_addr; | ||||
|       if ((cursor->ifa_addr->sa_family == AF_LINK) && | ||||
|           (socAddr->sdl_type == IFT_ETHER) && | ||||
|           strcmp(if_name.c_str(), cursor->ifa_name) == 0) { | ||||
|         dlAddr = (const struct sockaddr_dl *)cursor->ifa_addr; | ||||
|         const unsigned char *base = | ||||
|             (const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen]; | ||||
|         for (int i = 0; i < dlAddr->sdl_alen; i++) { | ||||
|           len += | ||||
|               snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]); | ||||
|         } | ||||
|       } | ||||
|       cursor = cursor->ifa_next; | ||||
|     } | ||||
|     freeifaddrs(addrs); | ||||
|   } | ||||
| #elif __linux__ | ||||
|   int sock = socket(AF_INET, SOCK_DGRAM, 0); | ||||
|   if (sock < 0) { | ||||
|     return ""; | ||||
|   } | ||||
|   struct ifreq ifr; | ||||
|   struct ifconf ifc; | ||||
|   char buf[1024]; | ||||
|   ifc.ifc_len = sizeof(buf); | ||||
|   ifc.ifc_buf = buf; | ||||
|   if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { | ||||
|     close(sock); | ||||
|     return ""; | ||||
|   } | ||||
|   struct ifreq *it = ifc.ifc_req; | ||||
|   const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq)); | ||||
|   for (; it != end; ++it) { | ||||
|     std::strcpy(ifr.ifr_name, it->ifr_name); | ||||
|     if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { | ||||
|       continue; | ||||
|     } | ||||
|     if (ifr.ifr_flags & IFF_LOOPBACK) { | ||||
|       continue; | ||||
|     } | ||||
|     if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) { | ||||
|       continue; | ||||
|     } | ||||
|     std::string mac_address; | ||||
|     for (int i = 0; i < 6; ++i) { | ||||
|       len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff); | ||||
|     } | ||||
|     break; | ||||
|   } | ||||
|   close(sock); | ||||
| #endif | ||||
|   return mac_addr; | ||||
| } | ||||
|  | ||||
| std::string GetHostName() { | ||||
|   char hostname[256]; | ||||
| #ifdef _WIN32 | ||||
|   WSADATA wsaData; | ||||
|   if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { | ||||
|     std::cerr << "WSAStartup failed." << std::endl; | ||||
|     return ""; | ||||
|   } | ||||
|   if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) { | ||||
|     LOG_ERROR("gethostname failed: {}", WSAGetLastError()); | ||||
|     WSACleanup(); | ||||
|     return ""; | ||||
|   } | ||||
|   WSACleanup(); | ||||
| #else | ||||
|   if (gethostname(hostname, sizeof(hostname)) == -1) { | ||||
|     LOG_ERROR("gethostname failed"); | ||||
|     return ""; | ||||
|   } | ||||
| #endif | ||||
|   return hostname; | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/common/platform.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-18 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _PLATFORM_H_ | ||||
| #define _PLATFORM_H_ | ||||
|  | ||||
| #include <iostream> | ||||
|  | ||||
| std::string GetMac(); | ||||
| std::string GetHostName(); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										3644
									
								
								src/config_center/SimpleIni.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										241
									
								
								src/config_center/config_center.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,241 @@ | ||||
| #include "config_center.h" | ||||
|  | ||||
| ConfigCenter::ConfigCenter(const std::string& config_path, | ||||
|                            const std::string& cert_file_path) | ||||
|     : config_path_(config_path), | ||||
|       cert_file_path_(cert_file_path), | ||||
|       cert_file_path_default_(cert_file_path) { | ||||
|   ini_.SetUnicode(true); | ||||
|   Load(); | ||||
| } | ||||
|  | ||||
| ConfigCenter::~ConfigCenter() {} | ||||
|  | ||||
| int ConfigCenter::Load() { | ||||
|   SI_Error rc = ini_.LoadFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     Save(); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   language_ = static_cast<LANGUAGE>( | ||||
|       ini_.GetLongValue(section_, "language", static_cast<long>(language_))); | ||||
|  | ||||
|   video_quality_ = static_cast<VIDEO_QUALITY>(ini_.GetLongValue( | ||||
|       section_, "video_quality", static_cast<long>(video_quality_))); | ||||
|  | ||||
|   video_frame_rate_ = static_cast<VIDEO_FRAME_RATE>(ini_.GetLongValue( | ||||
|       section_, "video_frame_rate", static_cast<long>(video_frame_rate_))); | ||||
|  | ||||
|   video_encode_format_ = static_cast<VIDEO_ENCODE_FORMAT>( | ||||
|       ini_.GetLongValue(section_, "video_encode_format", | ||||
|                         static_cast<long>(video_encode_format_))); | ||||
|  | ||||
|   hardware_video_codec_ = ini_.GetBoolValue(section_, "hardware_video_codec", | ||||
|                                             hardware_video_codec_); | ||||
|  | ||||
|   enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_); | ||||
|   enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_); | ||||
|   server_host_ = ini_.GetValue(section_, "server_host", server_host_.c_str()); | ||||
|   server_port_ = static_cast<int>( | ||||
|       ini_.GetLongValue(section_, "server_port", server_port_)); | ||||
|   cert_file_path_ = | ||||
|       ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str()); | ||||
|   enable_self_hosted_ = | ||||
|       ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_); | ||||
|  | ||||
|   enable_minimize_to_tray_ = ini_.GetBoolValue( | ||||
|       section_, "enable_minimize_to_tray", enable_minimize_to_tray_); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::Save() { | ||||
|   ini_.SetLongValue(section_, "language", static_cast<long>(language_)); | ||||
|   ini_.SetLongValue(section_, "video_quality", | ||||
|                     static_cast<long>(video_quality_)); | ||||
|   ini_.SetLongValue(section_, "video_frame_rate", | ||||
|                     static_cast<long>(video_frame_rate_)); | ||||
|   ini_.SetLongValue(section_, "video_encode_format", | ||||
|                     static_cast<long>(video_encode_format_)); | ||||
|   ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_); | ||||
|   ini_.SetBoolValue(section_, "enable_turn", enable_turn_); | ||||
|   ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_); | ||||
|   ini_.SetValue(section_, "server_host", server_host_.c_str()); | ||||
|   ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_)); | ||||
|   ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); | ||||
|   ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_); | ||||
|   ini_.SetBoolValue(section_, "enable_minimize_to_tray", | ||||
|                     enable_minimize_to_tray_); | ||||
|  | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| // setters | ||||
|  | ||||
| int ConfigCenter::SetLanguage(LANGUAGE language) { | ||||
|   language_ = language; | ||||
|   ini_.SetLongValue(section_, "language", static_cast<long>(language_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) { | ||||
|   video_quality_ = video_quality; | ||||
|   ini_.SetLongValue(section_, "video_quality", | ||||
|                     static_cast<long>(video_quality_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate) { | ||||
|   video_frame_rate_ = video_frame_rate; | ||||
|   ini_.SetLongValue(section_, "video_frame_rate", | ||||
|                     static_cast<long>(video_frame_rate_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetVideoEncodeFormat( | ||||
|     VIDEO_ENCODE_FORMAT video_encode_format) { | ||||
|   video_encode_format_ = video_encode_format; | ||||
|   ini_.SetLongValue(section_, "video_encode_format", | ||||
|                     static_cast<long>(video_encode_format_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) { | ||||
|   hardware_video_codec_ = hardware_video_codec; | ||||
|   ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetTurn(bool enable_turn) { | ||||
|   enable_turn_ = enable_turn; | ||||
|   ini_.SetBoolValue(section_, "enable_turn", enable_turn_); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| 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& server_host) { | ||||
|   server_host_ = server_host; | ||||
|   ini_.SetValue(section_, "server_host", server_host_.c_str()); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetServerPort(int server_port) { | ||||
|   server_port_ = server_port; | ||||
|   ini_.SetLongValue(section_, "server_port", static_cast<long>(server_port_)); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) { | ||||
|   cert_file_path_ = cert_file_path; | ||||
|   ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str()); | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetSelfHosted(bool enable_self_hosted) { | ||||
|   enable_self_hosted_ = enable_self_hosted; | ||||
|   SI_Error rc = ini_.SaveFile(config_path_.c_str()); | ||||
|   if (rc < 0) { | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) { | ||||
|   enable_minimize_to_tray_ = enable_minimize_to_tray; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| // getters | ||||
|  | ||||
| ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; } | ||||
|  | ||||
| ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() const { | ||||
|   return video_quality_; | ||||
| } | ||||
|  | ||||
| ConfigCenter::VIDEO_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::GetServerHost() const { return server_host_; } | ||||
|  | ||||
| int ConfigCenter::GetServerPort() const { return server_port_; } | ||||
|  | ||||
| std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; } | ||||
|  | ||||
| std::string ConfigCenter::GetDefaultServerHost() const { | ||||
|   return server_host_default_; | ||||
| } | ||||
|  | ||||
| int ConfigCenter::GetDefaultServerPort() const { return server_port_default_; } | ||||
|  | ||||
| std::string ConfigCenter::GetDefaultCertFilePath() const { | ||||
|   return cert_file_path_default_; | ||||
| } | ||||
|  | ||||
| bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; } | ||||
|  | ||||
| bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; } | ||||
							
								
								
									
										84
									
								
								src/config_center/config_center.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,84 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-05-29 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _CONFIG_CENTER_H_ | ||||
| #define _CONFIG_CENTER_H_ | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #include "SimpleIni.h" | ||||
|  | ||||
| class ConfigCenter { | ||||
|  public: | ||||
|   enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 }; | ||||
|   enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 }; | ||||
|   enum class VIDEO_FRAME_RATE { FPS_30 = 0, FPS_60 = 1 }; | ||||
|   enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 }; | ||||
|  | ||||
|  public: | ||||
|   explicit ConfigCenter( | ||||
|       const std::string& config_path = "config.ini", | ||||
|       const std::string& cert_file_path = "crossdesk.cn_root.crt"); | ||||
|   ~ConfigCenter(); | ||||
|  | ||||
|   // write config | ||||
|   int SetLanguage(LANGUAGE language); | ||||
|   int SetVideoQuality(VIDEO_QUALITY video_quality); | ||||
|   int SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate); | ||||
|   int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format); | ||||
|   int SetHardwareVideoCodec(bool hardware_video_codec); | ||||
|   int SetTurn(bool enable_turn); | ||||
|   int SetSrtp(bool enable_srtp); | ||||
|   int SetServerHost(const std::string& server_host); | ||||
|   int SetServerPort(int server_port); | ||||
|   int SetCertFilePath(const std::string& cert_file_path); | ||||
|   int SetSelfHosted(bool enable_self_hosted); | ||||
|   int SetMinimizeToTray(bool enable_minimize_to_tray); | ||||
|  | ||||
|   // read config | ||||
|  | ||||
|   LANGUAGE GetLanguage() const; | ||||
|   VIDEO_QUALITY GetVideoQuality() const; | ||||
|   VIDEO_FRAME_RATE GetVideoFrameRate() const; | ||||
|   VIDEO_ENCODE_FORMAT GetVideoEncodeFormat() const; | ||||
|   bool IsHardwareVideoCodec() const; | ||||
|   bool IsEnableTurn() const; | ||||
|   bool IsEnableSrtp() const; | ||||
|   std::string GetServerHost() const; | ||||
|   int GetServerPort() const; | ||||
|   std::string GetCertFilePath() const; | ||||
|   std::string GetDefaultServerHost() const; | ||||
|   int GetDefaultServerPort() const; | ||||
|   std::string GetDefaultCertFilePath() const; | ||||
|   bool IsSelfHosted() const; | ||||
|   bool IsMinimizeToTray() const; | ||||
|  | ||||
|   int Load(); | ||||
|   int Save(); | ||||
|  | ||||
|  private: | ||||
|   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 server_host_ = "api.crossdesk.cn"; | ||||
|   int server_port_ = 9099; | ||||
|   std::string server_host_default_ = "api.crossdesk.cn"; | ||||
|   int server_port_default_ = 9099; | ||||
|   std::string cert_file_path_default_ = ""; | ||||
|   bool enable_self_hosted_ = false; | ||||
|   bool enable_minimize_to_tray_ = false; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										83
									
								
								src/device_controller/device_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,83 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-14 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _DEVICE_CONTROLLER_H_ | ||||
| #define _DEVICE_CONTROLLER_H_ | ||||
|  | ||||
| #include <stdio.h> | ||||
|  | ||||
| #include "display_info.h" | ||||
|  | ||||
| typedef enum { | ||||
|   mouse = 0, | ||||
|   keyboard, | ||||
|   audio_capture, | ||||
|   host_infomation, | ||||
|   display_id, | ||||
| } ControlType; | ||||
| typedef enum { | ||||
|   move = 0, | ||||
|   left_down, | ||||
|   left_up, | ||||
|   right_down, | ||||
|   right_up, | ||||
|   middle_down, | ||||
|   middle_up, | ||||
|   wheel_vertical, | ||||
|   wheel_horizontal | ||||
| } MouseFlag; | ||||
| typedef enum { key_down = 0, key_up } KeyFlag; | ||||
| typedef struct { | ||||
|   float x; | ||||
|   float y; | ||||
|   int s; | ||||
|   MouseFlag flag; | ||||
| } Mouse; | ||||
|  | ||||
| typedef struct { | ||||
|   size_t key_value; | ||||
|   KeyFlag flag; | ||||
| } Key; | ||||
|  | ||||
| typedef struct { | ||||
|   char host_name[64]; | ||||
|   size_t host_name_size; | ||||
|   char** display_list; | ||||
|   size_t display_num; | ||||
|   int* left; | ||||
|   int* top; | ||||
|   int* right; | ||||
|   int* bottom; | ||||
| } HostInfo; | ||||
|  | ||||
| typedef struct { | ||||
|   ControlType type; | ||||
|   union { | ||||
|     Mouse m; | ||||
|     Key k; | ||||
|     HostInfo i; | ||||
|     bool a; | ||||
|     int d; | ||||
|   }; | ||||
| } RemoteAction; | ||||
|  | ||||
| // int key_code, bool is_down | ||||
| typedef void (*OnKeyAction)(int, bool, void*); | ||||
|  | ||||
| class DeviceController { | ||||
|  public: | ||||
|   virtual ~DeviceController() {} | ||||
|  | ||||
|  public: | ||||
|   // virtual int Init(int screen_width, int screen_height); | ||||
|   // virtual int Destroy(); | ||||
|   // virtual int SendMouseCommand(RemoteAction remote_action); | ||||
|  | ||||
|   // virtual int Hook(); | ||||
|   // virtual int Unhook(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										34
									
								
								src/device_controller/device_controller_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-14 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _DEVICE_CONTROLLER_FACTORY_H_ | ||||
| #define _DEVICE_CONTROLLER_FACTORY_H_ | ||||
|  | ||||
| #include "device_controller.h" | ||||
| #include "keyboard_capturer.h" | ||||
| #include "mouse_controller.h" | ||||
|  | ||||
| class DeviceControllerFactory { | ||||
|  public: | ||||
|   enum Device { Mouse = 0, Keyboard }; | ||||
|  | ||||
|  public: | ||||
|   virtual ~DeviceControllerFactory() {} | ||||
|  | ||||
|  public: | ||||
|   DeviceController* Create(Device device) { | ||||
|     switch (device) { | ||||
|       case Mouse: | ||||
|         return new MouseController(); | ||||
|       case Keyboard: | ||||
|         return new KeyboardCapturer(); | ||||
|       default: | ||||
|         return nullptr; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										69
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,69 @@ | ||||
| #include "keyboard_capturer.h" | ||||
|  | ||||
| #include "keyboard_converter.h" | ||||
| #include "rd_log.h" | ||||
|  | ||||
| static OnKeyAction g_on_key_action = nullptr; | ||||
| static void* g_user_ptr = nullptr; | ||||
|  | ||||
| static int KeyboardEventHandler(Display* display, XEvent* event) { | ||||
|   if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) { | ||||
|     KeySym keySym = XKeycodeToKeysym(display, event->xkey.keycode, 0); | ||||
|     int key_code = XKeysymToKeycode(display, keySym); | ||||
|     bool is_key_down = (event->xkey.type == KeyPress); | ||||
|  | ||||
|     if (g_on_key_action) { | ||||
|       g_on_key_action(key_code, is_key_down, g_user_ptr); | ||||
|     } | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) { | ||||
|   display_ = XOpenDisplay(nullptr); | ||||
|   if (!display_) { | ||||
|     LOG_ERROR("Failed to open X display."); | ||||
|   } | ||||
| } | ||||
|  | ||||
| KeyboardCapturer::~KeyboardCapturer() { | ||||
|   if (display_) { | ||||
|     XCloseDisplay(display_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { | ||||
|   g_on_key_action = on_key_action; | ||||
|   g_user_ptr = user_ptr; | ||||
|  | ||||
|   XSelectInput(display_, DefaultRootWindow(display_), | ||||
|                KeyPressMask | KeyReleaseMask); | ||||
|  | ||||
|   while (running_) { | ||||
|     XEvent event; | ||||
|     XNextEvent(display_, &event); | ||||
|     KeyboardEventHandler(display_, &event); | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int KeyboardCapturer::Unhook() { | ||||
|   running_ = false; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { | ||||
|   if (!display_) { | ||||
|     LOG_ERROR("Display not initialized."); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) { | ||||
|     int x11_key_code = vkCodeToX11KeySym[key_code]; | ||||
|     KeyCode keycode = XKeysymToKeycode(display_, x11_key_code); | ||||
|     XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime); | ||||
|     XFlush(display_); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										32
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-11-22 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _KEYBOARD_CAPTURER_H_ | ||||
| #define _KEYBOARD_CAPTURER_H_ | ||||
|  | ||||
| #include <X11/Xlib.h> | ||||
| #include <X11/extensions/XTest.h> | ||||
| #include <X11/keysym.h> | ||||
|  | ||||
| #include "device_controller.h" | ||||
|  | ||||
| class KeyboardCapturer : public DeviceController { | ||||
|  public: | ||||
|   KeyboardCapturer(); | ||||
|   virtual ~KeyboardCapturer(); | ||||
|  | ||||
|  public: | ||||
|   virtual int Hook(OnKeyAction on_key_action, void *user_ptr); | ||||
|   virtual int Unhook(); | ||||
|   virtual int SendKeyboardCommand(int key_code, bool is_down); | ||||
|  | ||||
|  private: | ||||
|   Display *display_; | ||||
|   Window root_; | ||||
|   bool running_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										167
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,167 @@ | ||||
| #include "keyboard_capturer.h" | ||||
|  | ||||
| #include "keyboard_converter.h" | ||||
| #include "rd_log.h" | ||||
|  | ||||
| static OnKeyAction g_on_key_action = nullptr; | ||||
| static void *g_user_ptr = nullptr; | ||||
|  | ||||
| CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, | ||||
|                          CGEventRef event, void *userInfo) { | ||||
|   KeyboardCapturer *keyboard_capturer = (KeyboardCapturer *)userInfo; | ||||
|   if (!keyboard_capturer) { | ||||
|     LOG_ERROR("keyboard_capturer is nullptr"); | ||||
|     return event; | ||||
|   } | ||||
|  | ||||
|   int vk_code = 0; | ||||
|  | ||||
|   if (type == kCGEventKeyDown || type == kCGEventKeyUp) { | ||||
|     CGKeyCode key_code = static_cast<CGKeyCode>( | ||||
|         CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)); | ||||
|     if (CGKeyCodeToVkCode.find(key_code) != CGKeyCodeToVkCode.end()) { | ||||
|       g_on_key_action(CGKeyCodeToVkCode[key_code], type == kCGEventKeyDown, | ||||
|                       g_user_ptr); | ||||
|     } | ||||
|   } else if (type == kCGEventFlagsChanged) { | ||||
|     CGEventFlags current_flags = CGEventGetFlags(event); | ||||
|     CGKeyCode key_code = static_cast<CGKeyCode>( | ||||
|         CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)); | ||||
|  | ||||
|     // caps lock | ||||
|     bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0; | ||||
|     if (caps_lock_state != keyboard_capturer->caps_lock_flag_) { | ||||
|       keyboard_capturer->caps_lock_flag_ = caps_lock_state; | ||||
|       if (keyboard_capturer->caps_lock_flag_) { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||
|       } else { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // shift | ||||
|     bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0; | ||||
|     if (shift_state != keyboard_capturer->shift_flag_) { | ||||
|       keyboard_capturer->shift_flag_ = shift_state; | ||||
|       if (keyboard_capturer->shift_flag_) { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||
|       } else { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // control | ||||
|     bool control_state = (current_flags & kCGEventFlagMaskControl) != 0; | ||||
|     if (control_state != keyboard_capturer->control_flag_) { | ||||
|       keyboard_capturer->control_flag_ = control_state; | ||||
|       if (keyboard_capturer->control_flag_) { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||
|       } else { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // option | ||||
|     bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0; | ||||
|     if (option_state != keyboard_capturer->option_flag_) { | ||||
|       keyboard_capturer->option_flag_ = option_state; | ||||
|       if (keyboard_capturer->option_flag_) { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||
|       } else { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // command | ||||
|     bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0; | ||||
|     if (command_state != keyboard_capturer->command_flag_) { | ||||
|       keyboard_capturer->command_flag_ = command_state; | ||||
|       if (keyboard_capturer->command_flag_) { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||
|       } else { | ||||
|         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| KeyboardCapturer::KeyboardCapturer() {} | ||||
|  | ||||
| KeyboardCapturer::~KeyboardCapturer() {} | ||||
|  | ||||
| int KeyboardCapturer::Hook(OnKeyAction on_key_action, void *user_ptr) { | ||||
|   g_on_key_action = on_key_action; | ||||
|   g_user_ptr = user_ptr; | ||||
|  | ||||
|   CGEventMask eventMask = (1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) | | ||||
|                           (1 << kCGEventFlagsChanged); | ||||
|  | ||||
|   event_tap_ = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, | ||||
|                                 kCGEventTapOptionDefault, eventMask, | ||||
|                                 eventCallback, this); | ||||
|  | ||||
|   if (!event_tap_) { | ||||
|     LOG_ERROR("CGEventTapCreate failed"); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   run_loop_source_ = | ||||
|       CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event_tap_, 0); | ||||
|  | ||||
|   CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_, | ||||
|                      kCFRunLoopCommonModes); | ||||
|  | ||||
|   CGEventTapEnable(event_tap_, true); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int KeyboardCapturer::Unhook() { | ||||
|   CFRelease(run_loop_source_); | ||||
|   CFRelease(event_tap_); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| inline bool IsFunctionKey(int key_code) { | ||||
|   switch (key_code) { | ||||
|     case 0x7A: | ||||
|     case 0x78: | ||||
|     case 0x63: | ||||
|     case 0x76: | ||||
|     case 0x60: | ||||
|     case 0x61: | ||||
|     case 0x62: | ||||
|     case 0x64: | ||||
|     case 0x65: | ||||
|     case 0x6D: | ||||
|     case 0x67: | ||||
|     case 0x6F: | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { | ||||
|   if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) { | ||||
|     CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code]; | ||||
|     CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down); | ||||
|     CGEventRef clearFlags = | ||||
|         CGEventCreateKeyboardEvent(NULL, (CGKeyCode)0, true); | ||||
|     CGEventSetFlags(clearFlags, 0); | ||||
|     CGEventPost(kCGHIDEventTap, event); | ||||
|     CFRelease(event); | ||||
|  | ||||
|     // F1-F12 keys often require the FN key to be pressed on Mac keyboards, so | ||||
|     // we simulate the FN key release when an F1-F12 key is released. | ||||
|     if (IsFunctionKey(cg_key_code) && !is_down) { | ||||
|       CGEventRef fn_release_event = | ||||
|           CGEventCreateKeyboardEvent(NULL, fn_key_code_, false); | ||||
|       CGEventPost(kCGHIDEventTap, fn_release_event); | ||||
|       CFRelease(fn_release_event); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,37 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-11-22 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _KEYBOARD_CAPTURER_H_ | ||||
| #define _KEYBOARD_CAPTURER_H_ | ||||
|  | ||||
| #include <ApplicationServices/ApplicationServices.h> | ||||
|  | ||||
| #include "device_controller.h" | ||||
|  | ||||
| class KeyboardCapturer : public DeviceController { | ||||
|  public: | ||||
|   KeyboardCapturer(); | ||||
|   virtual ~KeyboardCapturer(); | ||||
|  | ||||
|  public: | ||||
|   virtual int Hook(OnKeyAction on_key_action, void *user_ptr); | ||||
|   virtual int Unhook(); | ||||
|   virtual int SendKeyboardCommand(int key_code, bool is_down); | ||||
|  | ||||
|  private: | ||||
|   CFMachPortRef event_tap_; | ||||
|   CFRunLoopSourceRef run_loop_source_; | ||||
|  | ||||
|  public: | ||||
|   bool caps_lock_flag_ = false; | ||||
|   bool shift_flag_ = false; | ||||
|   bool control_flag_ = false; | ||||
|   bool option_flag_ = false; | ||||
|   bool command_flag_ = false; | ||||
|   int fn_key_code_ = 0x3F; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										56
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,56 @@ | ||||
| #include "keyboard_capturer.h" | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| static OnKeyAction g_on_key_action = nullptr; | ||||
| static void* g_user_ptr = nullptr; | ||||
|  | ||||
| LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { | ||||
|   if (nCode == HC_ACTION && g_on_key_action) { | ||||
|     KBDLLHOOKSTRUCT* kbData = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam); | ||||
|  | ||||
|     if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { | ||||
|       g_on_key_action(kbData->vkCode, true, g_user_ptr); | ||||
|     } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { | ||||
|       g_on_key_action(kbData->vkCode, false, g_user_ptr); | ||||
|     } | ||||
|     return 1; | ||||
|   } | ||||
|  | ||||
|   return CallNextHookEx(NULL, nCode, wParam, lParam); | ||||
| } | ||||
|  | ||||
| KeyboardCapturer::KeyboardCapturer() {} | ||||
|  | ||||
| KeyboardCapturer::~KeyboardCapturer() {} | ||||
|  | ||||
| int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { | ||||
|   g_on_key_action = on_key_action; | ||||
|   g_user_ptr = user_ptr; | ||||
|  | ||||
|   keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0); | ||||
|   if (!keyboard_hook_) { | ||||
|     LOG_ERROR("Failed to install keyboard hook"); | ||||
|     return -1; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int KeyboardCapturer::Unhook() { | ||||
|   UnhookWindowsHookEx(keyboard_hook_); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| // apply remote keyboard commands to the local machine | ||||
| int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { | ||||
|   INPUT input = {0}; | ||||
|   input.type = INPUT_KEYBOARD; | ||||
|   input.ki.wVk = (WORD)key_code; | ||||
|  | ||||
|   if (!is_down) { | ||||
|     input.ki.dwFlags = KEYEVENTF_KEYUP; | ||||
|   } | ||||
|   SendInput(1, &input, sizeof(INPUT)); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-11-22 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _KEYBOARD_CAPTURER_H_ | ||||
| #define _KEYBOARD_CAPTURER_H_ | ||||
|  | ||||
| #include <Windows.h> | ||||
|  | ||||
| #include "device_controller.h" | ||||
|  | ||||
| class KeyboardCapturer : public DeviceController { | ||||
|  public: | ||||
|   KeyboardCapturer(); | ||||
|   virtual ~KeyboardCapturer(); | ||||
|  | ||||
|  public: | ||||
|   virtual int Hook(OnKeyAction on_key_action, void *user_ptr); | ||||
|   virtual int Unhook(); | ||||
|   virtual int SendKeyboardCommand(int key_code, bool is_down); | ||||
|  | ||||
|  private: | ||||
|   HHOOK keyboard_hook_ = nullptr; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										740
									
								
								src/device_controller/keyboard_converter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,740 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-11-25 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _KEYBOARD_CONVERTER_H_ | ||||
| #define _KEYBOARD_CONVERTER_H_ | ||||
|  | ||||
| #include <map> | ||||
|  | ||||
| // Windows vkCode to macOS CGKeyCode (104 keys) | ||||
| std::map<int, int> vkCodeToCGKeyCode = { | ||||
|     // A-Z | ||||
|     {0x41, 0x00},  // A | ||||
|     {0x42, 0x0B},  // B | ||||
|     {0x43, 0x08},  // C | ||||
|     {0x44, 0x02},  // D | ||||
|     {0x45, 0x0E},  // E | ||||
|     {0x46, 0x03},  // F | ||||
|     {0x47, 0x05},  // G | ||||
|     {0x48, 0x04},  // H | ||||
|     {0x49, 0x22},  // I | ||||
|     {0x4A, 0x26},  // J | ||||
|     {0x4B, 0x28},  // K | ||||
|     {0x4C, 0x25},  // L | ||||
|     {0x4D, 0x2E},  // M | ||||
|     {0x4E, 0x2D},  // N | ||||
|     {0x4F, 0x1F},  // O | ||||
|     {0x50, 0x23},  // P | ||||
|     {0x51, 0x0C},  // Q | ||||
|     {0x52, 0x0F},  // R | ||||
|     {0x53, 0x01},  // S | ||||
|     {0x54, 0x11},  // T | ||||
|     {0x55, 0x20},  // U | ||||
|     {0x56, 0x09},  // V | ||||
|     {0x57, 0x0D},  // W | ||||
|     {0x58, 0x07},  // X | ||||
|     {0x59, 0x10},  // Y | ||||
|     {0x5A, 0x06},  // Z | ||||
|  | ||||
|     // 0-9 | ||||
|     {0x30, 0x1D},  // 0 | ||||
|     {0x31, 0x12},  // 1 | ||||
|     {0x32, 0x13},  // 2 | ||||
|     {0x33, 0x14},  // 3 | ||||
|     {0x34, 0x15},  // 4 | ||||
|     {0x35, 0x17},  // 5 | ||||
|     {0x36, 0x16},  // 6 | ||||
|     {0x37, 0x1A},  // 7 | ||||
|     {0x38, 0x1C},  // 8 | ||||
|     {0x39, 0x19},  // 9 | ||||
|  | ||||
|     // F1-F12 | ||||
|     {0x70, 0x7A},  // F1 | ||||
|     {0x71, 0x78},  // F2 | ||||
|     {0x72, 0x63},  // F3 | ||||
|     {0x73, 0x76},  // F4 | ||||
|     {0x74, 0x60},  // F5 | ||||
|     {0x75, 0x61},  // F6 | ||||
|     {0x76, 0x62},  // F7 | ||||
|     {0x77, 0x64},  // F8 | ||||
|     {0x78, 0x65},  // F9 | ||||
|     {0x79, 0x6D},  // F10 | ||||
|     {0x7A, 0x67},  // F11 | ||||
|     {0x7B, 0x6F},  // F12 | ||||
|  | ||||
|     // control keys | ||||
|     {0x1B, 0x35},  // Escape | ||||
|     {0x0D, 0x24},  // Enter | ||||
|     {0x20, 0x31},  // Space | ||||
|     {0x08, 0x33},  // Backspace | ||||
|     {0x09, 0x30},  // Tab | ||||
|     {0x2C, 0x74},  // Print Screen | ||||
|     {0x2D, 0x72},  // Insert | ||||
|     {0x2E, 0x75},  // Delete | ||||
|     {0x24, 0x73},  // Home | ||||
|     {0x23, 0x77},  // End | ||||
|     {0x21, 0x79},  // Page Up | ||||
|     {0x22, 0x7A},  // Page Down | ||||
|  | ||||
|     // arrow keys | ||||
|     {0x25, 0x7B},  // Left Arrow | ||||
|     {0x27, 0x7C},  // Right Arrow | ||||
|     {0x26, 0x7E},  // Up Arrow | ||||
|     {0x28, 0x7D},  // Down Arrow | ||||
|  | ||||
|     // numpad | ||||
|     {0x60, 0x52},  // Numpad 0 | ||||
|     {0x61, 0x53},  // Numpad 1 | ||||
|     {0x62, 0x54},  // Numpad 2 | ||||
|     {0x63, 0x55},  // Numpad 3 | ||||
|     {0x64, 0x56},  // Numpad 4 | ||||
|     {0x65, 0x57},  // Numpad 5 | ||||
|     {0x66, 0x58},  // Numpad 6 | ||||
|     {0x67, 0x59},  // Numpad 7 | ||||
|     {0x68, 0x5B},  // Numpad 8 | ||||
|     {0x69, 0x5C},  // Numpad 9 | ||||
|     {0x6E, 0x41},  // Numpad . | ||||
|     {0x6F, 0x4B},  // Numpad / | ||||
|     {0x6A, 0x43},  // Numpad * | ||||
|     {0x6D, 0x4E},  // Numpad - | ||||
|     {0x6B, 0x45},  // Numpad + | ||||
|  | ||||
|     // symbol keys | ||||
|     {0xBA, 0x29},  // ; (Semicolon) | ||||
|     {0xDE, 0x27},  // ' (Quote) | ||||
|     {0xC0, 0x32},  // ` (Backtick) | ||||
|     {0xBC, 0x2B},  // , (Comma) | ||||
|     {0xBE, 0x2F},  // . (Period) | ||||
|     {0xBF, 0x2C},  // / (Slash) | ||||
|     {0xDC, 0x2A},  // \ (Backslash) | ||||
|     {0xDB, 0x21},  // [ (Left Bracket) | ||||
|     {0xDD, 0x1E},  // ] (Right Bracket) | ||||
|     {0xBD, 0x1B},  // - (Minus) | ||||
|     {0xBB, 0x18},  // = (Equals) | ||||
|  | ||||
|     // modifier keys | ||||
|     {0x14, 0x39},  // Caps Lock | ||||
|     {0xA0, 0x38},  // Shift (Left) | ||||
|     {0xA1, 0x3C},  // Shift (Right) | ||||
|     {0xA2, 0x3B},  // Ctrl (Left) | ||||
|     {0xA3, 0x3E},  // Ctrl (Right) | ||||
|     {0xA4, 0x3A},  // Alt (Left) | ||||
|     {0xA5, 0x3D},  // Alt (Right) | ||||
|     {0x5B, 0x37},  // Left Command (Windows key) | ||||
|     {0x5C, 0x36},  // Right Command | ||||
| }; | ||||
|  | ||||
| // macOS CGKeyCode to Windows vkCode | ||||
| std::map<int, int> CGKeyCodeToVkCode = { | ||||
|     // A-Z | ||||
|     {0x00, 0x41},  // A | ||||
|     {0x0B, 0x42},  // B | ||||
|     {0x08, 0x43},  // C | ||||
|     {0x02, 0x44},  // D | ||||
|     {0x0E, 0x45},  // E | ||||
|     {0x03, 0x46},  // F | ||||
|     {0x05, 0x47},  // G | ||||
|     {0x04, 0x48},  // H | ||||
|     {0x22, 0x49},  // I | ||||
|     {0x26, 0x4A},  // J | ||||
|     {0x28, 0x4B},  // K | ||||
|     {0x25, 0x4C},  // L | ||||
|     {0x2E, 0x4D},  // M | ||||
|     {0x2D, 0x4E},  // N | ||||
|     {0x1F, 0x4F},  // O | ||||
|     {0x23, 0x50},  // P | ||||
|     {0x0C, 0x51},  // Q | ||||
|     {0x0F, 0x52},  // R | ||||
|     {0x01, 0x53},  // S | ||||
|     {0x11, 0x54},  // T | ||||
|     {0x20, 0x55},  // U | ||||
|     {0x09, 0x56},  // V | ||||
|     {0x0D, 0x57},  // W | ||||
|     {0x07, 0x58},  // X | ||||
|     {0x10, 0x59},  // Y | ||||
|     {0x06, 0x5A},  // Z | ||||
|  | ||||
|     // 0-9 | ||||
|     {0x1D, 0x30},  // 0 | ||||
|     {0x12, 0x31},  // 1 | ||||
|     {0x13, 0x32},  // 2 | ||||
|     {0x14, 0x33},  // 3 | ||||
|     {0x15, 0x34},  // 4 | ||||
|     {0x17, 0x35},  // 5 | ||||
|     {0x16, 0x36},  // 6 | ||||
|     {0x1A, 0x37},  // 7 | ||||
|     {0x1C, 0x38},  // 8 | ||||
|     {0x19, 0x39},  // 9 | ||||
|  | ||||
|     // F1-F12 | ||||
|     {0x7A, 0x70},  // F1 | ||||
|     {0x78, 0x71},  // F2 | ||||
|     {0x63, 0x72},  // F3 | ||||
|     {0x76, 0x73},  // F4 | ||||
|     {0x60, 0x74},  // F5 | ||||
|     {0x61, 0x75},  // F6 | ||||
|     {0x62, 0x76},  // F7 | ||||
|     {0x64, 0x77},  // F8 | ||||
|     {0x65, 0x78},  // F9 | ||||
|     {0x6D, 0x79},  // F10 | ||||
|     {0x67, 0x7A},  // F11 | ||||
|     {0x6F, 0x7B},  // F12 | ||||
|  | ||||
|     // control keys | ||||
|     {0x35, 0x1B},  // Escape | ||||
|     {0x24, 0x0D},  // Enter | ||||
|     {0x31, 0x20},  // Space | ||||
|     {0x33, 0x08},  // Backspace | ||||
|     {0x30, 0x09},  // Tab | ||||
|     {0x74, 0x2C},  // Print Screen | ||||
|     {0x72, 0x2D},  // Insert | ||||
|     {0x75, 0x2E},  // Delete | ||||
|     {0x73, 0x24},  // Home | ||||
|     {0x77, 0x23},  // End | ||||
|     {0x79, 0x21},  // Page Up | ||||
|     {0x7A, 0x22},  // Page Down | ||||
|  | ||||
|     // arrow keys | ||||
|     {0x7B, 0x25},  // Left Arrow | ||||
|     {0x7C, 0x27},  // Right Arrow | ||||
|     {0x7E, 0x26},  // Up Arrow | ||||
|     {0x7D, 0x28},  // Down Arrow | ||||
|  | ||||
|     // numpad | ||||
|     {0x52, 0x60},  // Numpad 0 | ||||
|     {0x53, 0x61},  // Numpad 1 | ||||
|     {0x54, 0x62},  // Numpad 2 | ||||
|     {0x55, 0x63},  // Numpad 3 | ||||
|     {0x56, 0x64},  // Numpad 4 | ||||
|     {0x57, 0x65},  // Numpad 5 | ||||
|     {0x58, 0x66},  // Numpad 6 | ||||
|     {0x59, 0x67},  // Numpad 7 | ||||
|     {0x5B, 0x68},  // Numpad 8 | ||||
|     {0x5C, 0x69},  // Numpad 9 | ||||
|     {0x41, 0x6E},  // Numpad . | ||||
|     {0x4B, 0x6F},  // Numpad / | ||||
|     {0x43, 0x6A},  // Numpad * | ||||
|     {0x4E, 0x6D},  // Numpad - | ||||
|     {0x45, 0x6B},  // Numpad + | ||||
|  | ||||
|     // symbol keys | ||||
|     {0x29, 0xBA},  // ; (Semicolon) | ||||
|     {0x27, 0xDE},  // ' (Quote) | ||||
|     {0x32, 0xC0},  // ` (Backtick) | ||||
|     {0x2B, 0xBC},  // , (Comma) | ||||
|     {0x2F, 0xBE},  // . (Period) | ||||
|     {0x2C, 0xBF},  // / (Slash) | ||||
|     {0x2A, 0xDC},  // \ (Backslash) | ||||
|     {0x21, 0xDB},  // [ (Left Bracket) | ||||
|     {0x1E, 0xDD},  // ] (Right Bracket) | ||||
|     {0x1B, 0xBD},  // - (Minus) | ||||
|     {0x18, 0xBB},  // = (Equals) | ||||
|  | ||||
|     // modifier keys | ||||
|     {0x39, 0x14},  // Caps Lock | ||||
|     {0x38, 0xA0},  // Shift (Left) | ||||
|     {0x3C, 0xA1},  // Shift (Right) | ||||
|     {0x3B, 0xA2},  // Control (Left) | ||||
|     {0x3E, 0xA3},  // Control (Right) | ||||
|     {0x3A, 0xA4},  // Alt (Left) | ||||
|     {0x3D, 0xA5},  // Alt (Right) | ||||
|     {0x37, 0x5B},  // Left Command (Windows key) | ||||
|     {0x36, 0x5C},  // Right Command | ||||
| }; | ||||
|  | ||||
| // Windows vkCode to X11 KeySym | ||||
| std::map<int, int> vkCodeToX11KeySym = { | ||||
|     // A-Z | ||||
|     {0x41, 0x0041},  // A | ||||
|     {0x42, 0x0042},  // B | ||||
|     {0x43, 0x0043},  // C | ||||
|     {0x44, 0x0044},  // D | ||||
|     {0x45, 0x0045},  // E | ||||
|     {0x46, 0x0046},  // F | ||||
|     {0x47, 0x0047},  // G | ||||
|     {0x48, 0x0048},  // H | ||||
|     {0x49, 0x0049},  // I | ||||
|     {0x4A, 0x004A},  // J | ||||
|     {0x4B, 0x004B},  // K | ||||
|     {0x4C, 0x004C},  // L | ||||
|     {0x4D, 0x004D},  // M | ||||
|     {0x4E, 0x004E},  // N | ||||
|     {0x4F, 0x004F},  // O | ||||
|     {0x50, 0x0050},  // P | ||||
|     {0x51, 0x0051},  // Q | ||||
|     {0x52, 0x0052},  // R | ||||
|     {0x53, 0x0053},  // S | ||||
|     {0x54, 0x0054},  // T | ||||
|     {0x55, 0x0055},  // U | ||||
|     {0x56, 0x0056},  // V | ||||
|     {0x57, 0x0057},  // W | ||||
|     {0x58, 0x0058},  // X | ||||
|     {0x59, 0x0059},  // Y | ||||
|     {0x5A, 0x005A},  // Z | ||||
|  | ||||
|     // 0-9 | ||||
|     {0x30, 0x0030},  // 0 | ||||
|     {0x31, 0x0031},  // 1 | ||||
|     {0x32, 0x0032},  // 2 | ||||
|     {0x33, 0x0033},  // 3 | ||||
|     {0x34, 0x0034},  // 4 | ||||
|     {0x35, 0x0035},  // 5 | ||||
|     {0x36, 0x0036},  // 6 | ||||
|     {0x37, 0x0037},  // 7 | ||||
|     {0x38, 0x0038},  // 8 | ||||
|     {0x39, 0x0039},  // 9 | ||||
|  | ||||
|     // F1-F12 | ||||
|     {0x70, 0xFFBE},  // F1 | ||||
|     {0x71, 0xFFBF},  // F2 | ||||
|     {0x72, 0xFFC0},  // F3 | ||||
|     {0x73, 0xFFC1},  // F4 | ||||
|     {0x74, 0xFFC2},  // F5 | ||||
|     {0x75, 0xFFC3},  // F6 | ||||
|     {0x76, 0xFFC4},  // F7 | ||||
|     {0x77, 0xFFC5},  // F8 | ||||
|     {0x78, 0xFFC6},  // F9 | ||||
|     {0x79, 0xFFC7},  // F10 | ||||
|     {0x7A, 0xFFC8},  // F11 | ||||
|     {0x7B, 0xFFC9},  // F12 | ||||
|  | ||||
|     // control keys | ||||
|     {0x1B, 0xFF1B},  // Escape | ||||
|     {0x0D, 0xFF0D},  // Enter | ||||
|     {0x20, 0x0020},  // Space | ||||
|     {0x08, 0xFF08},  // Backspace | ||||
|     {0x09, 0xFF09},  // Tab | ||||
|     {0x2C, 0xFF15},  // Print Screen | ||||
|     {0x91, 0xFF14},  // Scroll Lock | ||||
|     {0x13, 0xFF13},  // Pause/Break | ||||
|     {0x2D, 0xFF63},  // Insert | ||||
|     {0x2E, 0xFFFF},  // Delete | ||||
|     {0x24, 0xFF50},  // Home | ||||
|     {0x23, 0xFF57},  // End | ||||
|     {0x21, 0xFF55},  // Page Up | ||||
|     {0x22, 0xFF56},  // Page Down | ||||
|  | ||||
|     // arrow keys | ||||
|     {0x25, 0xFF51},  // Left Arrow | ||||
|     {0x27, 0xFF53},  // Right Arrow | ||||
|     {0x26, 0xFF52},  // Up Arrow | ||||
|     {0x28, 0xFF54},  // Down Arrow | ||||
|  | ||||
|     // numpad | ||||
|     {0x60, 0x0030},  // Numpad 0 | ||||
|     {0x61, 0x0031},  // Numpad 1 | ||||
|     {0x62, 0x0032},  // Numpad 2 | ||||
|     {0x63, 0x0033},  // Numpad 3 | ||||
|     {0x64, 0x0034},  // Numpad 4 | ||||
|     {0x65, 0x0035},  // Numpad 5 | ||||
|     {0x66, 0x0036},  // Numpad 6 | ||||
|     {0x67, 0x0037},  // Numpad 7 | ||||
|     {0x68, 0x0038},  // Numpad 8 | ||||
|     {0x69, 0x0039},  // Numpad 9 | ||||
|     {0x6E, 0x003A},  // Numpad . | ||||
|     {0x6F, 0x002F},  // Numpad / | ||||
|     {0x6A, 0x002A},  // Numpad * | ||||
|     {0x6D, 0x002D},  // Numpad - | ||||
|     {0x6B, 0x002B},  // Numpad + | ||||
|  | ||||
|     // symbol keys | ||||
|     {0xBA, 0x003B},  // ; (Semicolon) | ||||
|     {0xDE, 0x0027},  // ' (Quote) | ||||
|     {0xC0, 0x007E},  // ` (Grave) | ||||
|     {0xBC, 0x002C},  // , (Comma) | ||||
|     {0xBE, 0x002E},  // . (Period) | ||||
|     {0xBF, 0x002F},  // / (Slash) | ||||
|     {0xDC, 0x005C},  // \ (Backslash) | ||||
|     {0xDB, 0x005B},  // [ (Left Bracket) | ||||
|     {0xDD, 0x005D},  // ] (Right Bracket) | ||||
|     {0xBD, 0x002D},  // - (Minus) | ||||
|     {0xBB, 0x003D},  // = (Equals) | ||||
|  | ||||
|     // modifier keys | ||||
|     {0x14, 0xFFE5},  // Caps Lock | ||||
|     {0xA0, 0xFFE1},  // Shift (Left) | ||||
|     {0xA1, 0xFFE2},  // Shift (Right) | ||||
|     {0xA2, 0xFFE3},  // Ctrl (Left) | ||||
|     {0xA3, 0xFFE4},  // Ctrl (Right) | ||||
|     {0xA4, 0xFFE9},  // Alt (Left) | ||||
|     {0xA5, 0xFFEA},  // Alt (Right) | ||||
|     {0x5B, 0xFFEB},  // Left Command (Windows key) | ||||
|     {0x5C, 0xFFEC},  // Right Command | ||||
| }; | ||||
|  | ||||
| // X11 KeySym to Windows vkCode | ||||
| // std::map<int, int> x11KeySymToVkCode = []() { | ||||
| //   std::map<int, int> result; | ||||
| //   for (const auto& pair : vkCodeToX11KeySym) { | ||||
| //     result[pair.second] = pair.first; | ||||
| //   } | ||||
| //   return result; | ||||
| // }(); | ||||
|  | ||||
| std::map<int, int> x11KeySymToVkCode = { | ||||
|     // A-Z | ||||
|     {0x0041, 0x41},  // A | ||||
|     {0x0042, 0x42},  // B | ||||
|     {0x0043, 0x43},  // C | ||||
|     {0x0044, 0x44},  // D | ||||
|     {0x0045, 0x45},  // E | ||||
|     {0x0046, 0x46},  // F | ||||
|     {0x0047, 0x47},  // G | ||||
|     {0x0048, 0x48},  // H | ||||
|     {0x0049, 0x49},  // I | ||||
|     {0x004A, 0x4A},  // J | ||||
|     {0x004B, 0x4B},  // K | ||||
|     {0x004C, 0x4C},  // L | ||||
|     {0x004D, 0x4D},  // M | ||||
|     {0x004E, 0x4E},  // N | ||||
|     {0x004F, 0x4F},  // O | ||||
|     {0x0050, 0x50},  // P | ||||
|     {0x0051, 0x51},  // Q | ||||
|     {0x0052, 0x52},  // R | ||||
|     {0x0053, 0x53},  // S | ||||
|     {0x0054, 0x54},  // T | ||||
|     {0x0055, 0x55},  // U | ||||
|     {0x0056, 0x56},  // V | ||||
|     {0x0057, 0x57},  // W | ||||
|     {0x0058, 0x58},  // X | ||||
|     {0x0059, 0x59},  // Y | ||||
|     {0x005A, 0x5A},  // Z | ||||
|  | ||||
|     // 0-9 | ||||
|     {0x0030, 0x30},  // 0 | ||||
|     {0x0031, 0x31},  // 1 | ||||
|     {0x0032, 0x32},  // 2 | ||||
|     {0x0033, 0x33},  // 3 | ||||
|     {0x0034, 0x34},  // 4 | ||||
|     {0x0035, 0x35},  // 5 | ||||
|     {0x0036, 0x36},  // 6 | ||||
|     {0x0037, 0x37},  // 7 | ||||
|     {0x0038, 0x38},  // 8 | ||||
|     {0x0039, 0x39},  // 9 | ||||
|  | ||||
|     // F1-F12 | ||||
|     {0xFFBE, 0x70},  // F1 | ||||
|     {0xFFBF, 0x71},  // F2 | ||||
|     {0xFFC0, 0x72},  // F3 | ||||
|     {0xFFC1, 0x73},  // F4 | ||||
|     {0xFFC2, 0x74},  // F5 | ||||
|     {0xFFC3, 0x75},  // F6 | ||||
|     {0xFFC4, 0x76},  // F7 | ||||
|     {0xFFC5, 0x77},  // F8 | ||||
|     {0xFFC6, 0x78},  // F9 | ||||
|     {0xFFC7, 0x79},  // F10 | ||||
|     {0xFFC8, 0x7A},  // F11 | ||||
|     {0xFFC9, 0x7B},  // F12 | ||||
|  | ||||
|     // control keys | ||||
|     {0xFF1B, 0x1B},  // Escape | ||||
|     {0xFF0D, 0x0D},  // Enter | ||||
|     {0x0020, 0x20},  // Space | ||||
|     {0xFF08, 0x08},  // Backspace | ||||
|     {0xFF09, 0x09},  // Tab | ||||
|     {0xFF15, 0x2C},  // Print Screen | ||||
|     {0xFF14, 0x91},  // Scroll Lock | ||||
|     {0xFF13, 0x13},  // Pause/Break | ||||
|     {0xFF63, 0x2D},  // Insert | ||||
|     {0xFFFF, 0x2E},  // Delete | ||||
|     {0xFF50, 0x24},  // Home | ||||
|     {0xFF57, 0x23},  // End | ||||
|     {0xFF55, 0x21},  // Page Up | ||||
|     {0xFF56, 0x22},  // Page Down | ||||
|  | ||||
|     // arrow keys | ||||
|     {0xFF51, 0x25},  // Left Arrow | ||||
|     {0xFF53, 0x27},  // Right Arrow | ||||
|     {0xFF52, 0x26},  // Up Arrow | ||||
|     {0xFF54, 0x28},  // Down Arrow | ||||
|  | ||||
|     // numpad | ||||
|     {0x0030, 0x60},  // Numpad 0 | ||||
|     {0x0031, 0x61},  // Numpad 1 | ||||
|     {0x0032, 0x62},  // Numpad 2 | ||||
|     {0x0033, 0x63},  // Numpad 3 | ||||
|     {0x0034, 0x64},  // Numpad 4 | ||||
|     {0x0035, 0x65},  // Numpad 5 | ||||
|     {0x0036, 0x66},  // Numpad 6 | ||||
|     {0x0037, 0x67},  // Numpad 7 | ||||
|     {0x0038, 0x68},  // Numpad 8 | ||||
|     {0x0039, 0x69},  // Numpad 9 | ||||
|     {0x003A, 0x6E},  // Numpad . | ||||
|     {0x002F, 0x6F},  // Numpad / | ||||
|     {0x002A, 0x6A},  // Numpad * | ||||
|     {0x002D, 0x6D},  // Numpad - | ||||
|     {0x002B, 0x6B},  // Numpad + | ||||
|  | ||||
|     // symbol keys | ||||
|     {0x003B, 0xBA},  // ; (Semicolon) | ||||
|     {0x0027, 0xDE},  // ' (Quote) | ||||
|     {0x007E, 0xC0},  // ` (Grave) | ||||
|     {0x002C, 0xBC},  // , (Comma) | ||||
|     {0x002E, 0xBE},  // . (Period) | ||||
|     {0x002F, 0xBF},  // / (Slash) | ||||
|     {0x005C, 0xDC},  // \ (Backslash) | ||||
|     {0x005B, 0xDB},  // [ (Left Bracket) | ||||
|     {0x005D, 0xDD},  // ] (Right Bracket) | ||||
|     {0x002D, 0xBD},  // - (Minus) | ||||
|     {0x003D, 0xBB},  // = (Equals) | ||||
|  | ||||
|     // modifier keys | ||||
|     {0xFFE5, 0x14},  // Caps Lock | ||||
|     {0xFFE1, 0xA0},  // Shift (Left) | ||||
|     {0xFFE2, 0xA1},  // Shift (Right) | ||||
|     {0xFFE3, 0xA2},  // Ctrl (Left) | ||||
|     {0xFFE4, 0xA3},  // Ctrl (Right) | ||||
|     {0xFFE9, 0xA4},  // Alt (Left) | ||||
|     {0xFFEA, 0xA5},  // Alt (Right) | ||||
|     {0xFFEB, 0x5B},  // Left Command (Windows key) | ||||
|     {0xFFEC, 0x5C},  // Right Command | ||||
| }; | ||||
|  | ||||
| // macOS CGKeyCode to X11 KeySym | ||||
| std::map<int, int> cgKeyCodeToX11KeySym = { | ||||
|     // A-Z | ||||
|     {0x00, 0x0041},  // A | ||||
|     {0x0B, 0x0042},  // B | ||||
|     {0x08, 0x0043},  // C | ||||
|     {0x02, 0x0044},  // D | ||||
|     {0x0E, 0x0045},  // E | ||||
|     {0x03, 0x0046},  // F | ||||
|     {0x05, 0x0047},  // G | ||||
|     {0x04, 0x0048},  // H | ||||
|     {0x22, 0x0049},  // I | ||||
|     {0x26, 0x004A},  // J | ||||
|     {0x28, 0x004B},  // K | ||||
|     {0x25, 0x004C},  // L | ||||
|     {0x2E, 0x004D},  // M | ||||
|     {0x2D, 0x004E},  // N | ||||
|     {0x1F, 0x004F},  // O | ||||
|     {0x23, 0x0050},  // P | ||||
|     {0x0C, 0x0051},  // Q | ||||
|     {0x0F, 0x0052},  // R | ||||
|     {0x01, 0x0053},  // S | ||||
|     {0x11, 0x0054},  // T | ||||
|     {0x20, 0x0055},  // U | ||||
|     {0x09, 0x0056},  // V | ||||
|     {0x0D, 0x0057},  // W | ||||
|     {0x07, 0x0058},  // X | ||||
|     {0x10, 0x0059},  // Y | ||||
|     {0x06, 0x005A},  // Z | ||||
|  | ||||
|     // 0-9 | ||||
|     {0x1D, 0x0030},  // 0 | ||||
|     {0x12, 0x0031},  // 1 | ||||
|     {0x13, 0x0032},  // 2 | ||||
|     {0x14, 0x0033},  // 3 | ||||
|     {0x15, 0x0034},  // 4 | ||||
|     {0x17, 0x0035},  // 5 | ||||
|     {0x16, 0x0036},  // 6 | ||||
|     {0x1A, 0x0037},  // 7 | ||||
|     {0x1C, 0x0038},  // 8 | ||||
|     {0x19, 0x0039},  // 9 | ||||
|  | ||||
|     // F1-F12 | ||||
|     {0x7A, 0xFFBE},  // F1 | ||||
|     {0x78, 0xFFBF},  // F2 | ||||
|     {0x63, 0xFFC0},  // F3 | ||||
|     {0x76, 0xFFC1},  // F4 | ||||
|     {0x60, 0xFFC2},  // F5 | ||||
|     {0x61, 0xFFC3},  // F6 | ||||
|     {0x62, 0xFFC4},  // F7 | ||||
|     {0x64, 0xFFC5},  // F8 | ||||
|     {0x65, 0xFFC6},  // F9 | ||||
|     {0x6D, 0xFFC7},  // F10 | ||||
|     {0x67, 0xFFC8},  // F11 | ||||
|     {0x6F, 0xFFC9},  // F12 | ||||
|  | ||||
|     // control keys | ||||
|     {0x35, 0xFF1B},  // Escape | ||||
|     {0x24, 0xFF0D},  // Enter | ||||
|     {0x31, 0x0020},  // Space | ||||
|     {0x33, 0xFF08},  // Backspace | ||||
|     {0x30, 0xFF09},  // Tab | ||||
|     {0x74, 0xFF15},  // Print Screen | ||||
|     {0x72, 0xFF63},  // Insert | ||||
|     {0x75, 0xFFFF},  // Delete | ||||
|     {0x73, 0xFF50},  // Home | ||||
|     {0x77, 0xFF57},  // End | ||||
|     {0x79, 0xFF55},  // Page Up | ||||
|     {0x7A, 0xFF56},  // Page Down | ||||
|  | ||||
|     // arrow keys | ||||
|     {0x7B, 0xFF51},  // Left Arrow | ||||
|     {0x7C, 0xFF53},  // Right Arrow | ||||
|     {0x7E, 0xFF52},  // Up Arrow | ||||
|     {0x7D, 0xFF54},  // Down Arrow | ||||
|  | ||||
|     // numpad | ||||
|     {0x52, 0x0030},  // Numpad 0 | ||||
|     {0x53, 0x0031},  // Numpad 1 | ||||
|     {0x54, 0x0032},  // Numpad 2 | ||||
|     {0x55, 0x0033},  // Numpad 3 | ||||
|     {0x56, 0x0034},  // Numpad 4 | ||||
|     {0x57, 0x0035},  // Numpad 5 | ||||
|     {0x58, 0x0036},  // Numpad 6 | ||||
|     {0x59, 0x0037},  // Numpad 7 | ||||
|     {0x5B, 0x0038},  // Numpad 8 | ||||
|     {0x5C, 0x0039},  // Numpad 9 | ||||
|     {0x41, 0x003A},  // Numpad . | ||||
|     {0x4B, 0x002F},  // Numpad / | ||||
|     {0x43, 0x002A},  // Numpad * | ||||
|     {0x4E, 0x002D},  // Numpad - | ||||
|     {0x45, 0x002B},  // Numpad + | ||||
|  | ||||
|     // symbol keys | ||||
|     {0x29, 0x003B},  // ; (Semicolon) | ||||
|     {0x27, 0x0027},  // ' (Quote) | ||||
|     {0x32, 0x007E},  // ` (Backtick) | ||||
|     {0x2B, 0x002C},  // , (Comma) | ||||
|     {0x2F, 0x002E},  // . (Period) | ||||
|     {0x2C, 0x002F},  // / (Slash) | ||||
|     {0x2A, 0x005C},  // \ (Backslash) | ||||
|     {0x21, 0x005B},  // [ (Left Bracket) | ||||
|     {0x1E, 0x005D},  // ] (Right Bracket) | ||||
|     {0x1B, 0x002D},  // - (Minus) | ||||
|     {0x18, 0x003D},  // = (Equals) | ||||
|  | ||||
|     // modifier keys | ||||
|     {0x39, 0xFFE5},  // Caps Lock | ||||
|     {0x38, 0xFFE1},  // Shift (Left) | ||||
|     {0x3C, 0xFFE2},  // Shift (Right) | ||||
|     {0x3B, 0xFFE3},  // Control (Left) | ||||
|     {0x3E, 0xFFE4},  // Control (Right) | ||||
|     {0x3A, 0xFFE9},  // Alt (Left) | ||||
|     {0x3D, 0xFFEA},  // Alt (Right) | ||||
|     {0x37, 0xFFEB},  // Left Command (Windows key) | ||||
|     {0x36, 0xFFEC},  // Right Command | ||||
| }; | ||||
|  | ||||
| // X11 KeySym to macOS CGKeyCode | ||||
| // std::map<int, int> x11KeySymToCgKeyCode = []() { | ||||
| //   std::map<int, int> result; | ||||
| //   for (const auto& pair : cgKeyCodeToX11KeySym) { | ||||
| //     result[pair.second] = pair.first; | ||||
| //   } | ||||
| //   return result; | ||||
| // }(); | ||||
|  | ||||
| std::map<int, int> x11KeySymToCgKeyCode = { | ||||
|     // A-Z | ||||
|     {0x0041, 0x00},  // A | ||||
|     {0x0042, 0x0B},  // B | ||||
|     {0x0043, 0x08},  // C | ||||
|     {0x0044, 0x02},  // D | ||||
|     {0x0045, 0x0E},  // E | ||||
|     {0x0046, 0x03},  // F | ||||
|     {0x0047, 0x05},  // G | ||||
|     {0x0048, 0x04},  // H | ||||
|     {0x0049, 0x22},  // I | ||||
|     {0x004A, 0x26},  // J | ||||
|     {0x004B, 0x28},  // K | ||||
|     {0x004C, 0x25},  // L | ||||
|     {0x004D, 0x2E},  // M | ||||
|     {0x004E, 0x2D},  // N | ||||
|     {0x004F, 0x1F},  // O | ||||
|     {0x0050, 0x23},  // P | ||||
|     {0x0051, 0x0C},  // Q | ||||
|     {0x0052, 0x0F},  // R | ||||
|     {0x0053, 0x01},  // S | ||||
|     {0x0054, 0x11},  // T | ||||
|     {0x0055, 0x20},  // U | ||||
|     {0x0056, 0x09},  // V | ||||
|     {0x0057, 0x0D},  // W | ||||
|     {0x0058, 0x07},  // X | ||||
|     {0x0059, 0x10},  // Y | ||||
|     {0x005A, 0x06},  // Z | ||||
|  | ||||
|     // 0-9 | ||||
|     {0x0030, 0x1D},  // 0 | ||||
|     {0x0031, 0x12},  // 1 | ||||
|     {0x0032, 0x13},  // 2 | ||||
|     {0x0033, 0x14},  // 3 | ||||
|     {0x0034, 0x15},  // 4 | ||||
|     {0x0035, 0x17},  // 5 | ||||
|     {0x0036, 0x16},  // 6 | ||||
|     {0x0037, 0x1A},  // 7 | ||||
|     {0x0038, 0x1C},  // 8 | ||||
|     {0x0039, 0x19},  // 9 | ||||
|  | ||||
|     // F1-F12 | ||||
|     {0xFFBE, 0x7A},  // F1 | ||||
|     {0xFFBF, 0x78},  // F2 | ||||
|     {0xFFC0, 0x63},  // F3 | ||||
|     {0xFFC1, 0x76},  // F4 | ||||
|     {0xFFC2, 0x60},  // F5 | ||||
|     {0xFFC3, 0x61},  // F6 | ||||
|     {0xFFC4, 0x62},  // F7 | ||||
|     {0xFFC5, 0x64},  // F8 | ||||
|     {0xFFC6, 0x65},  // F9 | ||||
|     {0xFFC7, 0x6D},  // F10 | ||||
|     {0xFFC8, 0x67},  // F11 | ||||
|     {0xFFC9, 0x6F},  // F12 | ||||
|  | ||||
|     // control keys | ||||
|     {0xFF1B, 0x35},  // Escape | ||||
|     {0xFF0D, 0x24},  // Enter | ||||
|     {0x0020, 0x31},  // Space | ||||
|     {0xFF08, 0x33},  // Backspace | ||||
|     {0xFF09, 0x30},  // Tab | ||||
|     {0xFF15, 0x74},  // Print Screen | ||||
|     {0xFF63, 0x72},  // Insert | ||||
|     {0xFFFF, 0x75},  // Delete | ||||
|     {0xFF50, 0x73},  // Home | ||||
|     {0xFF57, 0x77},  // End | ||||
|     {0xFF55, 0x79},  // Page Up | ||||
|     {0xFF56, 0x7A},  // Page Down | ||||
|  | ||||
|     // arrow keys | ||||
|     {0xFF51, 0x7B},  // Left Arrow | ||||
|     {0xFF53, 0x7C},  // Right Arrow | ||||
|     {0xFF52, 0x7E},  // Up Arrow | ||||
|     {0xFF54, 0x7D},  // Down Arrow | ||||
|  | ||||
|     // numpad | ||||
|     {0x0030, 0x52},  // Numpad 0 | ||||
|     {0x0031, 0x53},  // Numpad 1 | ||||
|     {0x0032, 0x54},  // Numpad 2 | ||||
|     {0x0033, 0x55},  // Numpad 3 | ||||
|     {0x0034, 0x56},  // Numpad 4 | ||||
|     {0x0035, 0x57},  // Numpad 5 | ||||
|     {0x0036, 0x58},  // Numpad 6 | ||||
|     {0x0037, 0x59},  // Numpad 7 | ||||
|     {0x0038, 0x5B},  // Numpad 8 | ||||
|     {0x0039, 0x5C},  // Numpad 9 | ||||
|     {0x003A, 0x41},  // Numpad . | ||||
|     {0x002F, 0x4B},  // Numpad / | ||||
|     {0x002A, 0x43},  // Numpad * | ||||
|     {0x002D, 0x4E},  // Numpad - | ||||
|     {0x002B, 0x45},  // Numpad + | ||||
|  | ||||
|     // symbol keys | ||||
|     {0x003B, 0x29},  // ; (Semicolon) | ||||
|     {0x0027, 0x27},  // ' (Quote) | ||||
|     {0x007E, 0x32},  // ` (Backtick) | ||||
|     {0x002C, 0x2B},  // , (Comma) | ||||
|     {0x002E, 0x2F},  // . (Period) | ||||
|     {0x002F, 0x2C},  // / (Slash) | ||||
|     {0x005C, 0x2A},  // \ (Backslash) | ||||
|     {0x005B, 0x21},  // [ (Left Bracket) | ||||
|     {0x005D, 0x1E},  // ] (Right Bracket) | ||||
|     {0x002D, 0x1B},  // - (Minus) | ||||
|     {0x003D, 0x18},  // = (Equals) | ||||
|  | ||||
|     // modifier keys | ||||
|     {0xFFE5, 0x39},  // Caps Lock | ||||
|     {0xFFE1, 0x38},  // Shift (Left) | ||||
|     {0xFFE2, 0x3C},  // Shift (Right) | ||||
|     {0xFFE3, 0x3B},  // Control (Left) | ||||
|     {0xFFE4, 0x3E},  // Control (Right) | ||||
|     {0xFFE9, 0x3A},  // Alt (Left) | ||||
|     {0xFFEA, 0x3D},  // Alt (Right) | ||||
|     {0xFFEB, 0x37},  // Left Command | ||||
|     {0xFFEC, 0x36},  // Right Command | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										2802
									
								
								src/device_controller/linux_keycode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										861
									
								
								src/device_controller/macos_keycode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,861 @@ | ||||
| /* | ||||
|      File:       HIToolbox/Events.h | ||||
|  | ||||
|      Contains:   Event Manager Interfaces. | ||||
|  | ||||
|      Copyright:  锟<> 1985-2008 by Apple Computer, Inc., all rights reserved | ||||
|  | ||||
|      Bugs?:      For bug reports, consult the following page on | ||||
|                  the World Wide Web: | ||||
|  | ||||
|                      http://developer.apple.com/bugreporter/ | ||||
|  | ||||
| */ | ||||
| #ifndef __EVENTS__ | ||||
| #define __EVENTS__ | ||||
|  | ||||
| #ifndef __APPLICATIONSERVICES__ | ||||
| #include <ApplicationServices/ApplicationServices.h> | ||||
| #endif | ||||
|  | ||||
| #include <AvailabilityMacros.h> | ||||
|  | ||||
| #if PRAGMA_ONCE | ||||
| #pragma once | ||||
| #endif | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| #pragma pack(push, 2) | ||||
|  | ||||
| typedef UInt16 EventKind; | ||||
| typedef UInt16 EventMask; | ||||
| enum { | ||||
|   nullEvent = 0, | ||||
|   mouseDown = 1, | ||||
|   mouseUp = 2, | ||||
|   keyDown = 3, | ||||
|   keyUp = 4, | ||||
|   autoKey = 5, | ||||
|   updateEvt = 6, | ||||
|   diskEvt = 7, /* Not sent in Carbon. See kEventClassVolume in CarbonEvents.h*/ | ||||
|   activateEvt = 8, | ||||
|   osEvt = 15, | ||||
|   kHighLevelEvent = 23 | ||||
| }; | ||||
|  | ||||
| enum { | ||||
|   mDownMask = 1 << mouseDown,   /* mouse button pressed*/ | ||||
|   mUpMask = 1 << mouseUp,       /* mouse button released*/ | ||||
|   keyDownMask = 1 << keyDown,   /* key pressed*/ | ||||
|   keyUpMask = 1 << keyUp,       /* key released*/ | ||||
|   autoKeyMask = 1 << autoKey,   /* key repeatedly held down*/ | ||||
|   updateMask = 1 << updateEvt,  /* window needs updating*/ | ||||
|   diskMask = 1 << diskEvt,      /* disk inserted*/ | ||||
|   activMask = 1 << activateEvt, /* activate/deactivate window*/ | ||||
|   highLevelEventMask = 0x0400,  /* high-level events (includes AppleEvents)*/ | ||||
|   osMask = 1 << osEvt,          /* operating system events (suspend, resume)*/ | ||||
|   everyEvent = 0xFFFF           /* all of the above*/ | ||||
| }; | ||||
|  | ||||
| enum { | ||||
|   charCodeMask = 0x000000FF, | ||||
|   keyCodeMask = 0x0000FF00, | ||||
|   adbAddrMask = 0x00FF0000, | ||||
|   osEvtMessageMask = (UInt32)0xFF000000 | ||||
| }; | ||||
|  | ||||
| enum { | ||||
|   /* OS event messages.  Event (sub)code is in the high byte of the message | ||||
|      field.*/ | ||||
|   mouseMovedMessage = 0x00FA, | ||||
|   suspendResumeMessage = 0x0001 | ||||
| }; | ||||
|  | ||||
| enum { | ||||
|   resumeFlag = 1 /* Bit 0 of message indicates resume vs suspend*/ | ||||
| }; | ||||
|  | ||||
| #if CALL_NOT_IN_CARBON | ||||
| /*  convertClipboardFlag is not ever set under Carbon. This is because scrap | ||||
|  * conversion is  */ | ||||
| /*  not tied to suspend/resume events any longer. Your application should | ||||
|  * instead use the   */ | ||||
| /*  scrap promise mechanism and fulfill scrap requests only when your promise | ||||
|  * keeper proc   */ | ||||
| /*  is called. If you need to know if the scrap has changed, you can cache the | ||||
|  * last         */ | ||||
| /*  ScrapRef you received and compare it with the current ScrapRef */ | ||||
| enum { | ||||
|   convertClipboardFlag = | ||||
|       2 /* Bit 1 in resume message indicates clipboard change*/ | ||||
| }; | ||||
|  | ||||
| #endif /* CALL_NOT_IN_CARBON */ | ||||
|  | ||||
| /* | ||||
|     CARBON ALERT! BATTLESTATIONS! | ||||
|  | ||||
|     The EventModifiers bits defined here are also used in the newer Carbon Event | ||||
|     key modifiers parameters. There are two main differences: | ||||
|  | ||||
|     1)  The Carbon key modifiers parameter is a UInt32, not a UInt16. Never try | ||||
|    to extract the key modifiers parameter from a Carbon Event into an | ||||
|    EventModifiers type. You will probably get your stack trashed. 2)  The Carbon | ||||
|    key modifiers is just that: key modifiers. That parameter will never contain | ||||
|    the button state bit. | ||||
| */ | ||||
| typedef UInt16 EventModifiers; | ||||
| enum { | ||||
|   /* modifiers */ | ||||
|   activeFlagBit = 0,      /* activate? (activateEvt and mouseDown)*/ | ||||
|   btnStateBit = 7,        /* state of button?*/ | ||||
|   cmdKeyBit = 8,          /* command key down?*/ | ||||
|   shiftKeyBit = 9,        /* shift key down?*/ | ||||
|   alphaLockBit = 10,      /* alpha lock down?*/ | ||||
|   optionKeyBit = 11,      /* option key down?*/ | ||||
|   controlKeyBit = 12,     /* control key down?*/ | ||||
|   rightShiftKeyBit = 13,  /* right shift key down? Not supported on Mac OS X.*/ | ||||
|   rightOptionKeyBit = 14, /* right Option key down? Not supported on Mac OS X.*/ | ||||
|   rightControlKeyBit = | ||||
|       15 /* right Control key down? Not supported on Mac OS X.*/ | ||||
| }; | ||||
|  | ||||
| enum { | ||||
|   activeFlag = 1 << activeFlagBit, | ||||
|   btnState = 1 << btnStateBit, | ||||
|   cmdKey = 1 << cmdKeyBit, | ||||
|   shiftKey = 1 << shiftKeyBit, | ||||
|   alphaLock = 1 << alphaLockBit, | ||||
|   optionKey = 1 << optionKeyBit, | ||||
|   controlKey = 1 << controlKeyBit, | ||||
|   rightShiftKey = 1 << rightShiftKeyBit,    /* Not supported on Mac OS X.*/ | ||||
|   rightOptionKey = 1 << rightOptionKeyBit,  /* Not supported on Mac OS X.*/ | ||||
|   rightControlKey = 1 << rightControlKeyBit /* Not supported on Mac OS X.*/ | ||||
| }; | ||||
|  | ||||
| /* MacRoman character codes*/ | ||||
| enum { | ||||
|   kNullCharCode = 0, | ||||
|   kHomeCharCode = 1, | ||||
|   kEnterCharCode = 3, | ||||
|   kEndCharCode = 4, | ||||
|   kHelpCharCode = 5, | ||||
|   kBellCharCode = 7, | ||||
|   kBackspaceCharCode = 8, | ||||
|   kTabCharCode = 9, | ||||
|   kLineFeedCharCode = 10, | ||||
|   kVerticalTabCharCode = 11, | ||||
|   kPageUpCharCode = 11, | ||||
|   kFormFeedCharCode = 12, | ||||
|   kPageDownCharCode = 12, | ||||
|   kReturnCharCode = 13, | ||||
|   kFunctionKeyCharCode = 16, | ||||
|   kCommandCharCode = 17,   /* glyph available only in system fonts*/ | ||||
|   kCheckCharCode = 18,     /* glyph available only in system fonts*/ | ||||
|   kDiamondCharCode = 19,   /* glyph available only in system fonts*/ | ||||
|   kAppleLogoCharCode = 20, /* glyph available only in system fonts*/ | ||||
|   kEscapeCharCode = 27, | ||||
|   kClearCharCode = 27, | ||||
|   kLeftArrowCharCode = 28, | ||||
|   kRightArrowCharCode = 29, | ||||
|   kUpArrowCharCode = 30, | ||||
|   kDownArrowCharCode = 31, | ||||
|   kSpaceCharCode = 32, | ||||
|   kDeleteCharCode = 127, | ||||
|   kBulletCharCode = 165, | ||||
|   kNonBreakingSpaceCharCode = 202 | ||||
| }; | ||||
|  | ||||
| /* useful Unicode code points*/ | ||||
| enum { | ||||
|   kShiftUnicode = 0x21E7,   /* Unicode UPWARDS WHITE ARROW*/ | ||||
|   kControlUnicode = 0x2303, /* Unicode UP ARROWHEAD*/ | ||||
|   kOptionUnicode = 0x2325,  /* Unicode OPTION KEY*/ | ||||
|   kCommandUnicode = 0x2318, /* Unicode PLACE OF INTEREST SIGN*/ | ||||
|   kPencilUnicode = 0x270E,  /* Unicode LOWER RIGHT PENCIL; actually pointed left | ||||
|                                until Mac OS X 10.3*/ | ||||
|   kPencilLeftUnicode = 0xF802, /* Unicode LOWER LEFT PENCIL; available in Mac OS | ||||
|                                   X 10.3 and later*/ | ||||
|   kCheckUnicode = 0x2713,      /* Unicode CHECK MARK*/ | ||||
|   kDiamondUnicode = 0x25C6,    /* Unicode BLACK DIAMOND*/ | ||||
|   kBulletUnicode = 0x2022,     /* Unicode BULLET*/ | ||||
|   kAppleLogoUnicode = 0xF8FF   /* Unicode APPLE LOGO*/ | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  *  Summary: | ||||
|  *    Virtual keycodes | ||||
|  * | ||||
|  *  Discussion: | ||||
|  *    These constants are the virtual keycodes defined originally in | ||||
|  *    Inside Mac Volume V, pg. V-191. They identify physical keys on a | ||||
|  *    keyboard. Those constants with "ANSI" in the name are labeled | ||||
|  *    according to the key position on an ANSI-standard US keyboard. | ||||
|  *    For example, kVK_ANSI_A indicates the virtual keycode for the key | ||||
|  *    with the letter 'A' in the US keyboard layout. Other keyboard | ||||
|  *    layouts may have the 'A' key label on a different physical key; | ||||
|  *    in this case, pressing 'A' will generate a different virtual | ||||
|  *    keycode. | ||||
|  */ | ||||
| enum { | ||||
|   kVK_ANSI_A = 0x00, | ||||
|   kVK_ANSI_S = 0x01, | ||||
|   kVK_ANSI_D = 0x02, | ||||
|   kVK_ANSI_F = 0x03, | ||||
|   kVK_ANSI_H = 0x04, | ||||
|   kVK_ANSI_G = 0x05, | ||||
|   kVK_ANSI_Z = 0x06, | ||||
|   kVK_ANSI_X = 0x07, | ||||
|   kVK_ANSI_C = 0x08, | ||||
|   kVK_ANSI_V = 0x09, | ||||
|   kVK_ANSI_B = 0x0B, | ||||
|   kVK_ANSI_Q = 0x0C, | ||||
|   kVK_ANSI_W = 0x0D, | ||||
|   kVK_ANSI_E = 0x0E, | ||||
|   kVK_ANSI_R = 0x0F, | ||||
|   kVK_ANSI_Y = 0x10, | ||||
|   kVK_ANSI_T = 0x11, | ||||
|   kVK_ANSI_1 = 0x12, | ||||
|   kVK_ANSI_2 = 0x13, | ||||
|   kVK_ANSI_3 = 0x14, | ||||
|   kVK_ANSI_4 = 0x15, | ||||
|   kVK_ANSI_6 = 0x16, | ||||
|   kVK_ANSI_5 = 0x17, | ||||
|   kVK_ANSI_Equal = 0x18, | ||||
|   kVK_ANSI_9 = 0x19, | ||||
|   kVK_ANSI_7 = 0x1A, | ||||
|   kVK_ANSI_Minus = 0x1B, | ||||
|   kVK_ANSI_8 = 0x1C, | ||||
|   kVK_ANSI_0 = 0x1D, | ||||
|   kVK_ANSI_RightBracket = 0x1E, | ||||
|   kVK_ANSI_O = 0x1F, | ||||
|   kVK_ANSI_U = 0x20, | ||||
|   kVK_ANSI_LeftBracket = 0x21, | ||||
|   kVK_ANSI_I = 0x22, | ||||
|   kVK_ANSI_P = 0x23, | ||||
|   kVK_ANSI_L = 0x25, | ||||
|   kVK_ANSI_J = 0x26, | ||||
|   kVK_ANSI_Quote = 0x27, | ||||
|   kVK_ANSI_K = 0x28, | ||||
|   kVK_ANSI_Semicolon = 0x29, | ||||
|   kVK_ANSI_Backslash = 0x2A, | ||||
|   kVK_ANSI_Comma = 0x2B, | ||||
|   kVK_ANSI_Slash = 0x2C, | ||||
|   kVK_ANSI_N = 0x2D, | ||||
|   kVK_ANSI_M = 0x2E, | ||||
|   kVK_ANSI_Period = 0x2F, | ||||
|   kVK_ANSI_Grave = 0x32, | ||||
|   kVK_ANSI_KeypadDecimal = 0x41, | ||||
|   kVK_ANSI_KeypadMultiply = 0x43, | ||||
|   kVK_ANSI_KeypadPlus = 0x45, | ||||
|   kVK_ANSI_KeypadClear = 0x47, | ||||
|   kVK_ANSI_KeypadDivide = 0x4B, | ||||
|   kVK_ANSI_KeypadEnter = 0x4C, | ||||
|   kVK_ANSI_KeypadMinus = 0x4E, | ||||
|   kVK_ANSI_KeypadEquals = 0x51, | ||||
|   kVK_ANSI_Keypad0 = 0x52, | ||||
|   kVK_ANSI_Keypad1 = 0x53, | ||||
|   kVK_ANSI_Keypad2 = 0x54, | ||||
|   kVK_ANSI_Keypad3 = 0x55, | ||||
|   kVK_ANSI_Keypad4 = 0x56, | ||||
|   kVK_ANSI_Keypad5 = 0x57, | ||||
|   kVK_ANSI_Keypad6 = 0x58, | ||||
|   kVK_ANSI_Keypad7 = 0x59, | ||||
|   kVK_ANSI_Keypad8 = 0x5B, | ||||
|   kVK_ANSI_Keypad9 = 0x5C | ||||
| }; | ||||
|  | ||||
| /* keycodes for keys that are independent of keyboard layout*/ | ||||
| enum { | ||||
|   kVK_Return = 0x24, | ||||
|   kVK_Tab = 0x30, | ||||
|   kVK_Space = 0x31, | ||||
|   kVK_Delete = 0x33, | ||||
|   kVK_Escape = 0x35, | ||||
|   kVK_Command = 0x37, | ||||
|   kVK_Shift = 0x38, | ||||
|   kVK_CapsLock = 0x39, | ||||
|   kVK_Option = 0x3A, | ||||
|   kVK_Control = 0x3B, | ||||
|   kVK_RightCommand = 0x36, | ||||
|   kVK_RightShift = 0x3C, | ||||
|   kVK_RightOption = 0x3D, | ||||
|   kVK_RightControl = 0x3E, | ||||
|   kVK_Function = 0x3F, | ||||
|   kVK_F17 = 0x40, | ||||
|   kVK_VolumeUp = 0x48, | ||||
|   kVK_VolumeDown = 0x49, | ||||
|   kVK_Mute = 0x4A, | ||||
|   kVK_F18 = 0x4F, | ||||
|   kVK_F19 = 0x50, | ||||
|   kVK_F20 = 0x5A, | ||||
|   kVK_F5 = 0x60, | ||||
|   kVK_F6 = 0x61, | ||||
|   kVK_F7 = 0x62, | ||||
|   kVK_F3 = 0x63, | ||||
|   kVK_F8 = 0x64, | ||||
|   kVK_F9 = 0x65, | ||||
|   kVK_F11 = 0x67, | ||||
|   kVK_F13 = 0x69, | ||||
|   kVK_F16 = 0x6A, | ||||
|   kVK_F14 = 0x6B, | ||||
|   kVK_F10 = 0x6D, | ||||
|   kVK_ContextualMenu = 0x6E, | ||||
|   kVK_F12 = 0x6F, | ||||
|   kVK_F15 = 0x71, | ||||
|   kVK_Help = 0x72, | ||||
|   kVK_Home = 0x73, | ||||
|   kVK_PageUp = 0x74, | ||||
|   kVK_ForwardDelete = 0x75, | ||||
|   kVK_F4 = 0x76, | ||||
|   kVK_End = 0x77, | ||||
|   kVK_F2 = 0x78, | ||||
|   kVK_PageDown = 0x79, | ||||
|   kVK_F1 = 0x7A, | ||||
|   kVK_LeftArrow = 0x7B, | ||||
|   kVK_RightArrow = 0x7C, | ||||
|   kVK_DownArrow = 0x7D, | ||||
|   kVK_UpArrow = 0x7E | ||||
| }; | ||||
|  | ||||
| /* ISO keyboards only*/ | ||||
| enum { kVK_ISO_Section = 0x0A }; | ||||
|  | ||||
| /* JIS keyboards only*/ | ||||
| enum { | ||||
|   kVK_JIS_Yen = 0x5D, | ||||
|   kVK_JIS_Underscore = 0x5E, | ||||
|   kVK_JIS_KeypadComma = 0x5F, | ||||
|   kVK_JIS_Eisu = 0x66, | ||||
|   kVK_JIS_Kana = 0x68 | ||||
| }; | ||||
|  | ||||
| struct EventRecord { | ||||
|   EventKind what; | ||||
|   unsigned long message; | ||||
|   UInt32 when; | ||||
|   Point where; | ||||
|   EventModifiers modifiers; | ||||
| }; | ||||
| typedef struct EventRecord EventRecord; | ||||
| typedef CALLBACK_API(void, FKEYProcPtr)(void); | ||||
| typedef STACK_UPP_TYPE(FKEYProcPtr) FKEYUPP; | ||||
| /* | ||||
|  *  NewFKEYUPP() | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         not available | ||||
|  *    CarbonLib:        not available | ||||
|  *    Non-Carbon CFM:   available as macro/inline | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  *  DisposeFKEYUPP() | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         not available | ||||
|  *    CarbonLib:        not available | ||||
|  *    Non-Carbon CFM:   available as macro/inline | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  *  InvokeFKEYUPP() | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         not available | ||||
|  *    CarbonLib:        not available | ||||
|  *    Non-Carbon CFM:   available as macro/inline | ||||
|  */ | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  GetMouse()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use HIGetMousePosition instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.5 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void GetMouse(Point *mouseLoc) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|  *  Button()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use GetCurrentButtonState or GetCurrentEventButtonState instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework but | ||||
|  * deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later Non-Carbon | ||||
|  * CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean Button(void) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  StillDown() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean StillDown(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| /* | ||||
|  *  WaitMouseUp() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean WaitMouseUp(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| /* | ||||
|  *  KeyTranslate()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use UCKeyTranslate instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern UInt32 KeyTranslate(const void *transData, UInt16 keycode, UInt32 *state) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| /* | ||||
|  *  GetCaretTime() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern UInt32 GetCaretTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|     QuickTime 3.0 supports GetKeys() on unix and win32 | ||||
|     But, on little endian machines you will have to be | ||||
|     careful about bit numberings and/or use a KeyMapByteArray | ||||
|     instead. | ||||
| */ | ||||
| #if TARGET_API_MAC_OS8 | ||||
|  | ||||
| typedef UInt32 KeyMap[4]; | ||||
| #else | ||||
| typedef BigEndianUInt32 KeyMap[4]; | ||||
| #endif /* TARGET_API_MAC_OS8 */ | ||||
|  | ||||
| typedef UInt8 KeyMapByteArray[16]; | ||||
| /* | ||||
|  *  GetKeys() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework | ||||
|  *    CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void GetKeys(KeyMap theKeys) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| /* Obsolete event types & masks */ | ||||
| enum { | ||||
|   networkEvt = 10, | ||||
|   driverEvt = 11, | ||||
|   app1Evt = 12, | ||||
|   app2Evt = 13, | ||||
|   app3Evt = 14, | ||||
|   app4Evt = 15, | ||||
|   networkMask = 0x0400, | ||||
|   driverMask = 0x0800, | ||||
|   app1Mask = 0x1000, | ||||
|   app2Mask = 0x2000, | ||||
|   app3Mask = 0x4000, | ||||
|   app4Mask = 0x8000 | ||||
| }; | ||||
|  | ||||
| struct EvQEl { | ||||
|   QElemPtr qLink; | ||||
|   SInt16 qType; | ||||
|   EventKind | ||||
|       evtQWhat; /* this part is identical to the EventRecord as defined above */ | ||||
|   unsigned long evtQMessage; | ||||
|   UInt32 evtQWhen; | ||||
|   Point evtQWhere; | ||||
|   EventModifiers evtQModifiers; | ||||
| }; | ||||
| typedef struct EvQEl EvQEl; | ||||
| typedef EvQEl *EvQElPtr; | ||||
| typedef CALLBACK_API(void, GetNextEventFilterProcPtr)(EventRecord *theEvent, | ||||
|                                                       Boolean *result); | ||||
| typedef STACK_UPP_TYPE(GetNextEventFilterProcPtr) GetNextEventFilterUPP; | ||||
| /* | ||||
|  *  NewGetNextEventFilterUPP() | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         not available | ||||
|  *    CarbonLib:        not available | ||||
|  *    Non-Carbon CFM:   available as macro/inline | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  *  DisposeGetNextEventFilterUPP() | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         not available | ||||
|  *    CarbonLib:        not available | ||||
|  *    Non-Carbon CFM:   available as macro/inline | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  *  InvokeGetNextEventFilterUPP() | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         not available | ||||
|  *    CarbonLib:        not available | ||||
|  *    Non-Carbon CFM:   available as macro/inline | ||||
|  */ | ||||
|  | ||||
| typedef GetNextEventFilterUPP GNEFilterUPP; | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  GetDblTime() | ||||
|  * | ||||
|  *  Summary: | ||||
|  *    Returns the maximum time (in units of 1/60th of a second) allowed | ||||
|  *    between two consecutive mouse-down events in order for the second | ||||
|  *    click to be considered a double-click. | ||||
|  * | ||||
|  *  Discussion: | ||||
|  *    In 64-bit applications, you may replace calls to this API with | ||||
|  *    calls to NXClickTime (declared in | ||||
|  *    <IOKit/hidsystem/event_status_driver.h>) or with +[NSEvent | ||||
|  *    doubleClickInterval] (available in Mac OS X 10.6 and later). | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Result: | ||||
|  *    The maximum time between mouse-downs allowed for a double-click. | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern UInt32 GetDblTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| /* | ||||
|  *  SetEventMask() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void SetEventMask(EventMask value) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| /* | ||||
|  *  GetNextEvent()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use ReceiveNextEvent instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean GetNextEvent(EventMask eventMask, EventRecord *theEvent) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| /* | ||||
|  *  WaitNextEvent()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use ReceiveNextEvent instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean WaitNextEvent(EventMask eventMask, EventRecord *theEvent, | ||||
|                              UInt32 sleep, RgnHandle mouseRgn) /* can be NULL */ | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| /* | ||||
|  *  EventAvail()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use FindSpecificEventInQueue instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean EventAvail(EventMask eventMask, EventRecord *theEvent) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| /* | ||||
|  *  PostEvent()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use PostEventToQueue or CGEventPost instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern OSErr PostEvent(EventKind eventNum, UInt32 eventMsg) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|  *  FlushEvents()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use FlushEventsMatchingListFromQueue, | ||||
|  *    FlushSpecificEventsFromQueue, or FlushEventQueue instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework but | ||||
|  * deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later Non-Carbon | ||||
|  * CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void FlushEvents(EventMask whichMask, EventMask stopMask) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| #if OLDROUTINENAMES | ||||
| #define KeyTrans(transData, keycode, state) \ | ||||
|   KeyTranslate(transData, keycode, state) | ||||
| #endif /* OLDROUTINENAMES */ | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  KeyScript()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use TISSelectInputSource API for positive verbs (ScriptCode). | ||||
|  *     Use TSMDocument properties to restrict input sources: | ||||
|  *     kTSMDocumentEnabledInputSourcesPropertyTag | ||||
|  *     kTSMDocumentInputSourceOverridePropertyTag | ||||
|  * | ||||
|  *  Summary: | ||||
|  *    Switch to the specified script's default (last used) input source. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] but deprecated in 10.5 CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void KeyScript(short code) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|  *  IsCmdChar()   *** DEPRECATED *** | ||||
|  * | ||||
|  *  Deprecated: | ||||
|  *    Use IsUserCancelEventRef or CheckEventQueueForUserCancel instead. | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework but | ||||
|  * deprecated in 10.6 CarbonLib:        in CarbonLib 1.0 and later Non-Carbon | ||||
|  * CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern Boolean IsCmdChar(const EventRecord *event, short test) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6; | ||||
|  | ||||
| /* | ||||
|     LowMem accessor functions previously in LowMem.h | ||||
| */ | ||||
| /* | ||||
|  *  LMGetKeyThresh() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework | ||||
|  *    CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern SInt16 LMGetKeyThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  LMSetKeyThresh() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void LMSetKeyThresh(SInt16 value) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|  *  LMGetKeyRepThresh() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework | ||||
|  *    CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern SInt16 LMGetKeyRepThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  LMSetKeyRepThresh() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void LMSetKeyRepThresh(SInt16 value) | ||||
|     AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|  *  LMGetKbdLast() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework | ||||
|  *    CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern UInt8 LMGetKbdLast(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  LMSetKbdLast() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void LMSetKbdLast(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| /* | ||||
|  *  LMGetKbdType() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework | ||||
|  *    CarbonLib:        in CarbonLib 1.0 and later | ||||
|  *    Non-Carbon CFM:   in InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern UInt8 LMGetKbdType(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #if !__LP64__ | ||||
| /* | ||||
|  *  LMSetKbdType() | ||||
|  * | ||||
|  *  Mac OS X threading: | ||||
|  *    Not thread safe | ||||
|  * | ||||
|  *  Availability: | ||||
|  *    Mac OS X:         in version 10.0 and later in Carbon.framework [32-bit | ||||
|  * only] CarbonLib:        in CarbonLib 1.0 and later Non-Carbon CFM:   in | ||||
|  * InterfaceLib 7.1 and later | ||||
|  */ | ||||
| extern void LMSetKbdType(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER; | ||||
|  | ||||
| #endif /* !__LP64__ */ | ||||
|  | ||||
| #pragma pack(pop) | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif /* __EVENTS__ */ | ||||
							
								
								
									
										124
									
								
								src/device_controller/mouse/linux/mouse_controller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,124 @@ | ||||
| #include "mouse_controller.h" | ||||
|  | ||||
| #include <X11/extensions/XTest.h> | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| MouseController::MouseController() {} | ||||
|  | ||||
| MouseController::~MouseController() { Destroy(); } | ||||
|  | ||||
| int MouseController::Init(std::vector<DisplayInfo> display_info_list) { | ||||
|   display_info_list_ = display_info_list; | ||||
|   display_ = XOpenDisplay(NULL); | ||||
|   if (!display_) { | ||||
|     LOG_ERROR("Cannot connect to X server"); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   root_ = DefaultRootWindow(display_); | ||||
|  | ||||
|   int event_base, error_base, major_version, minor_version; | ||||
|   if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version, | ||||
|                            &minor_version)) { | ||||
|     LOG_ERROR("XTest extension not available"); | ||||
|     XCloseDisplay(display_); | ||||
|     return -2; | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int MouseController::Destroy() { | ||||
|   if (display_) { | ||||
|     XCloseDisplay(display_); | ||||
|     display_ = nullptr; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int MouseController::SendMouseCommand(RemoteAction remote_action, | ||||
|                                       int display_index) { | ||||
|   switch (remote_action.type) { | ||||
|     case mouse: | ||||
|       switch (remote_action.m.flag) { | ||||
|         case MouseFlag::move: | ||||
|           SetMousePosition( | ||||
|               static_cast<int>(remote_action.m.x * | ||||
|                                    display_info_list_[display_index].width + | ||||
|                                display_info_list_[display_index].left), | ||||
|               static_cast<int>(remote_action.m.y * | ||||
|                                    display_info_list_[display_index].height + | ||||
|                                display_info_list_[display_index].top)); | ||||
|           break; | ||||
|         case MouseFlag::left_down: | ||||
|           XTestFakeButtonEvent(display_, 1, True, CurrentTime); | ||||
|           XFlush(display_); | ||||
|           break; | ||||
|         case MouseFlag::left_up: | ||||
|           XTestFakeButtonEvent(display_, 1, False, CurrentTime); | ||||
|           XFlush(display_); | ||||
|           break; | ||||
|         case MouseFlag::right_down: | ||||
|           XTestFakeButtonEvent(display_, 3, True, CurrentTime); | ||||
|           XFlush(display_); | ||||
|           break; | ||||
|         case MouseFlag::right_up: | ||||
|           XTestFakeButtonEvent(display_, 3, False, CurrentTime); | ||||
|           XFlush(display_); | ||||
|           break; | ||||
|         case MouseFlag::middle_down: | ||||
|           XTestFakeButtonEvent(display_, 2, True, CurrentTime); | ||||
|           XFlush(display_); | ||||
|           break; | ||||
|         case MouseFlag::middle_up: | ||||
|           XTestFakeButtonEvent(display_, 2, False, CurrentTime); | ||||
|           XFlush(display_); | ||||
|           break; | ||||
|         case MouseFlag::wheel_vertical: { | ||||
|           if (remote_action.m.s > 0) { | ||||
|             SimulateMouseWheel(4, remote_action.m.s); | ||||
|           } else if (remote_action.m.s < 0) { | ||||
|             SimulateMouseWheel(5, -remote_action.m.s); | ||||
|           } | ||||
|           break; | ||||
|         } | ||||
|         case MouseFlag::wheel_horizontal: { | ||||
|           if (remote_action.m.s > 0) { | ||||
|             SimulateMouseWheel(6, remote_action.m.s); | ||||
|           } else if (remote_action.m.s < 0) { | ||||
|             SimulateMouseWheel(7, -remote_action.m.s); | ||||
|           } | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| void MouseController::SetMousePosition(int x, int y) { | ||||
|   XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y); | ||||
|   XFlush(display_); | ||||
| } | ||||
|  | ||||
| void MouseController::SimulateKeyDown(int kval) { | ||||
|   XTestFakeKeyEvent(display_, kval, True, CurrentTime); | ||||
|   XFlush(display_); | ||||
| } | ||||
|  | ||||
| void MouseController::SimulateKeyUp(int kval) { | ||||
|   XTestFakeKeyEvent(display_, kval, False, CurrentTime); | ||||
|   XFlush(display_); | ||||
| } | ||||
|  | ||||
| void MouseController::SimulateMouseWheel(int direction_button, int count) { | ||||
|   for (int i = 0; i < count; ++i) { | ||||
|     XTestFakeButtonEvent(display_, direction_button, True, CurrentTime); | ||||
|     XTestFakeButtonEvent(display_, direction_button, False, CurrentTime); | ||||
|   } | ||||
|   XFlush(display_); | ||||
| } | ||||
							
								
								
									
										41
									
								
								src/device_controller/mouse/linux/mouse_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2025-05-07 | ||||
|  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _MOUSE_CONTROLLER_H_ | ||||
| #define _MOUSE_CONTROLLER_H_ | ||||
|  | ||||
| #include <X11/Xlib.h> | ||||
| #include <X11/Xutil.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include "device_controller.h" | ||||
|  | ||||
| class MouseController : public DeviceController { | ||||
|  public: | ||||
|   MouseController(); | ||||
|   virtual ~MouseController(); | ||||
|  | ||||
|  public: | ||||
|   virtual int Init(std::vector<DisplayInfo> display_info_list); | ||||
|   virtual int Destroy(); | ||||
|   virtual int SendMouseCommand(RemoteAction remote_action, int display_index); | ||||
|  | ||||
|  private: | ||||
|   void SimulateKeyDown(int kval); | ||||
|   void SimulateKeyUp(int kval); | ||||
|   void SetMousePosition(int x, int y); | ||||
|   void SimulateMouseWheel(int direction_button, int count); | ||||
|  | ||||
|   Display* display_ = nullptr; | ||||
|   Window root_ = 0; | ||||
|   std::vector<DisplayInfo> display_info_list_; | ||||
|   int screen_width_ = 0; | ||||
|   int screen_height_ = 0; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										101
									
								
								src/device_controller/mouse/mac/mouse_controller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,101 @@ | ||||
| #include "mouse_controller.h" | ||||
|  | ||||
| #include <ApplicationServices/ApplicationServices.h> | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| MouseController::MouseController() {} | ||||
|  | ||||
| MouseController::~MouseController() {} | ||||
|  | ||||
| int MouseController::Init(std::vector<DisplayInfo> display_info_list) { | ||||
|   display_info_list_ = display_info_list; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int MouseController::Destroy() { return 0; } | ||||
|  | ||||
| int MouseController::SendMouseCommand(RemoteAction remote_action, | ||||
|                                       int display_index) { | ||||
|   int mouse_pos_x = | ||||
|       remote_action.m.x * display_info_list_[display_index].width + | ||||
|       display_info_list_[display_index].left; | ||||
|   int mouse_pos_y = | ||||
|       remote_action.m.y * display_info_list_[display_index].height + | ||||
|       display_info_list_[display_index].top; | ||||
|  | ||||
|   if (remote_action.type == ControlType::mouse) { | ||||
|     CGEventRef mouse_event = nullptr; | ||||
|     CGEventType mouse_type; | ||||
|     CGMouseButton mouse_button; | ||||
|     CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y); | ||||
|  | ||||
|     switch (remote_action.m.flag) { | ||||
|       case MouseFlag::left_down: | ||||
|         mouse_type = kCGEventLeftMouseDown; | ||||
|         left_dragging_ = true; | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               kCGMouseButtonLeft); | ||||
|         break; | ||||
|       case MouseFlag::left_up: | ||||
|         mouse_type = kCGEventLeftMouseUp; | ||||
|         left_dragging_ = false; | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               kCGMouseButtonLeft); | ||||
|         break; | ||||
|       case MouseFlag::right_down: | ||||
|         mouse_type = kCGEventRightMouseDown; | ||||
|         right_dragging_ = true; | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               kCGMouseButtonRight); | ||||
|         break; | ||||
|       case MouseFlag::right_up: | ||||
|         mouse_type = kCGEventRightMouseUp; | ||||
|         right_dragging_ = false; | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               kCGMouseButtonRight); | ||||
|         break; | ||||
|       case MouseFlag::middle_down: | ||||
|         mouse_type = kCGEventOtherMouseDown; | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               kCGMouseButtonCenter); | ||||
|         break; | ||||
|       case MouseFlag::middle_up: | ||||
|         mouse_type = kCGEventOtherMouseUp; | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               kCGMouseButtonCenter); | ||||
|         break; | ||||
|       case MouseFlag::wheel_vertical: | ||||
|         mouse_event = CGEventCreateScrollWheelEvent( | ||||
|             NULL, kCGScrollEventUnitLine, 2, remote_action.m.s, 0); | ||||
|         break; | ||||
|       case MouseFlag::wheel_horizontal: | ||||
|         mouse_event = CGEventCreateScrollWheelEvent( | ||||
|             NULL, kCGScrollEventUnitLine, 2, 0, remote_action.m.s); | ||||
|         break; | ||||
|       default: | ||||
|         if (left_dragging_) { | ||||
|           mouse_type = kCGEventLeftMouseDragged; | ||||
|           mouse_button = kCGMouseButtonLeft; | ||||
|         } else if (right_dragging_) { | ||||
|           mouse_type = kCGEventRightMouseDragged; | ||||
|           mouse_button = kCGMouseButtonRight; | ||||
|         } else { | ||||
|           mouse_type = kCGEventMouseMoved; | ||||
|           mouse_button = kCGMouseButtonLeft; | ||||
|         } | ||||
|  | ||||
|         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||
|                                               mouse_button); | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     if (mouse_event) { | ||||
|       CGEventPost(kCGHIDEventTap, mouse_event); | ||||
|       CFRelease(mouse_event); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/device_controller/mouse/mac/mouse_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,30 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-14 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _MOUSE_CONTROLLER_H_ | ||||
| #define _MOUSE_CONTROLLER_H_ | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include "device_controller.h" | ||||
|  | ||||
| class MouseController : public DeviceController { | ||||
|  public: | ||||
|   MouseController(); | ||||
|   virtual ~MouseController(); | ||||
|  | ||||
|  public: | ||||
|   virtual int Init(std::vector<DisplayInfo> display_info_list); | ||||
|   virtual int Destroy(); | ||||
|   virtual int SendMouseCommand(RemoteAction remote_action, int display_index); | ||||
|  | ||||
|  private: | ||||
|   std::vector<DisplayInfo> display_info_list_; | ||||
|   bool left_dragging_ = false; | ||||
|   bool right_dragging_ = false; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										72
									
								
								src/device_controller/mouse/windows/mouse_controller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,72 @@ | ||||
| #include "mouse_controller.h" | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| MouseController::MouseController() {} | ||||
|  | ||||
| MouseController::~MouseController() {} | ||||
|  | ||||
| int MouseController::Init(std::vector<DisplayInfo> display_info_list) { | ||||
|   display_info_list_ = display_info_list; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int MouseController::Destroy() { return 0; } | ||||
|  | ||||
| int MouseController::SendMouseCommand(RemoteAction remote_action, | ||||
|                                       int display_index) { | ||||
|   INPUT ip; | ||||
|  | ||||
|   if (remote_action.type == ControlType::mouse) { | ||||
|     ip.type = INPUT_MOUSE; | ||||
|     ip.mi.dx = | ||||
|         (LONG)(remote_action.m.x * display_info_list_[display_index].width) + | ||||
|         display_info_list_[display_index].left; | ||||
|     ip.mi.dy = | ||||
|         (LONG)(remote_action.m.y * display_info_list_[display_index].height) + | ||||
|         display_info_list_[display_index].top; | ||||
|  | ||||
|     switch (remote_action.m.flag) { | ||||
|       case MouseFlag::left_down: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE; | ||||
|         break; | ||||
|       case MouseFlag::left_up: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE; | ||||
|         break; | ||||
|       case MouseFlag::right_down: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE; | ||||
|         break; | ||||
|       case MouseFlag::right_up: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE; | ||||
|         break; | ||||
|       case MouseFlag::middle_down: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_ABSOLUTE; | ||||
|         break; | ||||
|       case MouseFlag::middle_up: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_MIDDLEUP | MOUSEEVENTF_ABSOLUTE; | ||||
|         break; | ||||
|       case MouseFlag::wheel_vertical: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_WHEEL; | ||||
|         ip.mi.mouseData = remote_action.m.s * 120; | ||||
|         break; | ||||
|       case MouseFlag::wheel_horizontal: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_HWHEEL; | ||||
|         ip.mi.mouseData = remote_action.m.s * 120; | ||||
|         break; | ||||
|       default: | ||||
|         ip.mi.dwFlags = MOUSEEVENTF_MOVE; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     ip.mi.time = 0; | ||||
|  | ||||
|     SetCursorPos(ip.mi.dx, ip.mi.dy); | ||||
|  | ||||
|     if (ip.mi.dwFlags != MOUSEEVENTF_MOVE) { | ||||
|       SendInput(1, &ip, sizeof(INPUT)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/device_controller/mouse/windows/mouse_controller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-14 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _MOUSE_CONTROLLER_H_ | ||||
| #define _MOUSE_CONTROLLER_H_ | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include "device_controller.h" | ||||
|  | ||||
| class MouseController : public DeviceController { | ||||
|  public: | ||||
|   MouseController(); | ||||
|   virtual ~MouseController(); | ||||
|  | ||||
|  public: | ||||
|   virtual int Init(std::vector<DisplayInfo> display_info_list); | ||||
|   virtual int Destroy(); | ||||
|   virtual int SendMouseCommand(RemoteAction remote_action, int display_index); | ||||
|  | ||||
|  private: | ||||
|   std::vector<DisplayInfo> display_info_list_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										206
									
								
								src/device_controller/windows_keycode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,206 @@ | ||||
| // virtual_key_codes.h | ||||
| #ifndef VIRTUAL_KEY_CODES_H | ||||
| #define VIRTUAL_KEY_CODES_H | ||||
|  | ||||
| #define VK_LBUTTON 0x01   // Left mouse button | ||||
| #define VK_RBUTTON 0x02   // Right mouse button | ||||
| #define VK_CANCEL 0x03    // Control-break processing | ||||
| #define VK_MBUTTON 0x04   // Middle mouse button | ||||
| #define VK_XBUTTON1 0x05  // X1 mouse button | ||||
| #define VK_XBUTTON2 0x06  // X2 mouse button | ||||
| // 0x07 Reserved | ||||
| #define VK_BACK 0x08  // Backspace key | ||||
| #define VK_TAB 0x09   // Tab key | ||||
| // 0x0A-0B Reserved | ||||
| #define VK_CLEAR 0x0C   // Clear key | ||||
| #define VK_RETURN 0x0D  // Enter key | ||||
| // 0x0E-0F Unassigned | ||||
| #define VK_SHIFT 0x10       // Shift key | ||||
| #define VK_CONTROL 0x11     // Ctrl key | ||||
| #define VK_MENU 0x12        // Alt key | ||||
| #define VK_PAUSE 0x13       // Pause key | ||||
| #define VK_CAPITAL 0x14     // Caps lock key | ||||
| #define VK_KANA 0x15        // IME Kana mode | ||||
| #define VK_HANGUL 0x15      // IME Hangul mode | ||||
| #define VK_IME_ON 0x16      // IME On | ||||
| #define VK_JUNJA 0x17       // IME Junja mode | ||||
| #define VK_FINAL 0x18       // IME final mode | ||||
| #define VK_HANJA 0x19       // IME Hanja mode | ||||
| #define VK_KANJI 0x19       // IME Kanji mode | ||||
| #define VK_IME_OFF 0x1A     // IME Off | ||||
| #define VK_ESCAPE 0x1B      // Esc key | ||||
| #define VK_CONVERT 0x1C     // IME convert | ||||
| #define VK_NONCONVERT 0x1D  // IME nonconvert | ||||
| #define VK_ACCEPT 0x1E      // IME accept | ||||
| #define VK_MODECHANGE 0x1F  // IME mode change request | ||||
| #define VK_SPACE 0x20       // Spacebar key | ||||
| #define VK_PRIOR 0x21       // Page up key | ||||
| #define VK_NEXT 0x22        // Page down key | ||||
| #define VK_END 0x23         // End key | ||||
| #define VK_HOME 0x24        // Home key | ||||
| #define VK_LEFT 0x25        // Left arrow key | ||||
| #define VK_UP 0x26          // Up arrow key | ||||
| #define VK_RIGHT 0x27       // Right arrow key | ||||
| #define VK_DOWN 0x28        // Down arrow key | ||||
| #define VK_SELECT 0x29      // Select key | ||||
| #define VK_PRINT 0x2A       // Print key | ||||
| #define VK_EXECUTE 0x2B     // Execute key | ||||
| #define VK_SNAPSHOT 0x2C    // Print screen key | ||||
| #define VK_INSERT 0x2D      // Insert key | ||||
| #define VK_DELETE 0x2E      // Delete key | ||||
| #define VK_HELP 0x2F        // Help key | ||||
|  | ||||
| #define VK_0 0x30  // 0 key | ||||
| #define VK_1 0x31  // 1 key | ||||
| #define VK_2 0x32  // 2 key | ||||
| #define VK_3 0x33  // 3 key | ||||
| #define VK_4 0x34  // 4 key | ||||
| #define VK_5 0x35  // 5 key | ||||
| #define VK_6 0x36  // 6 key | ||||
| #define VK_7 0x37  // 7 key | ||||
| #define VK_8 0x38  // 8 key | ||||
| #define VK_9 0x39  // 9 key | ||||
| // 0x3A-40 Undefined | ||||
|  | ||||
| #define VK_A 0x41  // A key | ||||
| #define VK_B 0x42  // B key | ||||
| #define VK_C 0x43  // C key | ||||
| #define VK_D 0x44  // D key | ||||
| #define VK_E 0x45  // E key | ||||
| #define VK_F 0x46  // F key | ||||
| #define VK_G 0x47  // G key | ||||
| #define VK_H 0x48  // H key | ||||
| #define VK_I 0x49  // I key | ||||
| #define VK_J 0x4A  // J key | ||||
| #define VK_K 0x4B  // K key | ||||
| #define VK_L 0x4C  // L key | ||||
| #define VK_M 0x4D  // M key | ||||
| #define VK_N 0x4E  // N key | ||||
| #define VK_O 0x4F  // O key | ||||
| #define VK_P 0x50  // P key | ||||
| #define VK_Q 0x51  // Q key | ||||
| #define VK_R 0x52  // R key | ||||
| #define VK_S 0x53  // S key | ||||
| #define VK_T 0x54  // T key | ||||
| #define VK_U 0x55  // U key | ||||
| #define VK_V 0x56  // V key | ||||
| #define VK_W 0x57  // W key | ||||
| #define VK_X 0x58  // X key | ||||
| #define VK_Y 0x59  // Y key | ||||
| #define VK_Z 0x5A  // Z key | ||||
|  | ||||
| #define VK_LWIN 0x5B  // Left Windows logo key | ||||
| #define VK_RWIN 0x5C  // Right Windows logo key | ||||
| #define VK_APPS 0x5D  // Application key | ||||
| // 0x5E Reserved | ||||
| #define VK_SLEEP 0x5F  // Computer Sleep key | ||||
|  | ||||
| #define VK_NUMPAD0 0x60    // Numeric keypad 0 key | ||||
| #define VK_NUMPAD1 0x61    // Numeric keypad 1 key | ||||
| #define VK_NUMPAD2 0x62    // Numeric keypad 2 key | ||||
| #define VK_NUMPAD3 0x63    // Numeric keypad 3 key | ||||
| #define VK_NUMPAD4 0x64    // Numeric keypad 4 key | ||||
| #define VK_NUMPAD5 0x65    // Numeric keypad 5 key | ||||
| #define VK_NUMPAD6 0x66    // Numeric keypad 6 key | ||||
| #define VK_NUMPAD7 0x67    // Numeric keypad 7 key | ||||
| #define VK_NUMPAD8 0x68    // Numeric keypad 8 key | ||||
| #define VK_NUMPAD9 0x69    // Numeric keypad 9 key | ||||
| #define VK_MULTIPLY 0x6A   // Multiply key | ||||
| #define VK_ADD 0x6B        // Add key | ||||
| #define VK_SEPARATOR 0x6C  // Separator key | ||||
| #define VK_SUBTRACT 0x6D   // Subtract key | ||||
| #define VK_DECIMAL 0x6E    // Decimal key | ||||
| #define VK_DIVIDE 0x6F     // Divide key | ||||
|  | ||||
| #define VK_F1 0x70   // F1 key | ||||
| #define VK_F2 0x71   // F2 key | ||||
| #define VK_F3 0x72   // F3 key | ||||
| #define VK_F4 0x73   // F4 key | ||||
| #define VK_F5 0x74   // F5 key | ||||
| #define VK_F6 0x75   // F6 key | ||||
| #define VK_F7 0x76   // F7 key | ||||
| #define VK_F8 0x77   // F8 key | ||||
| #define VK_F9 0x78   // F9 key | ||||
| #define VK_F10 0x79  // F10 key | ||||
| #define VK_F11 0x7A  // F11 key | ||||
| #define VK_F12 0x7B  // F12 key | ||||
| #define VK_F13 0x7C  // F13 key | ||||
| #define VK_F14 0x7D  // F14 key | ||||
| #define VK_F15 0x7E  // F15 key | ||||
| #define VK_F16 0x7F  // F16 key | ||||
| #define VK_F17 0x80  // F17 key | ||||
| #define VK_F18 0x81  // F18 key | ||||
| #define VK_F19 0x82  // F19 key | ||||
| #define VK_F20 0x83  // F20 key | ||||
| #define VK_F21 0x84  // F21 key | ||||
| #define VK_F22 0x85  // F22 key | ||||
| #define VK_F23 0x86  // F23 key | ||||
| #define VK_F24 0x87  // F24 key | ||||
| // 0x88–0x8F Reserved | ||||
|  | ||||
| #define VK_NUMLOCK 0x90  // Num lock key | ||||
| #define VK_SCROLL 0x91   // Scroll lock key | ||||
| // 0x92–0x96 OEM specific | ||||
| // 0x97–0x9F Unassigned | ||||
|  | ||||
| #define VK_LSHIFT 0xA0    // Left Shift key | ||||
| #define VK_RSHIFT 0xA1    // Right Shift key | ||||
| #define VK_LCONTROL 0xA2  // Left Ctrl key | ||||
| #define VK_RCONTROL 0xA3  // Right Ctrl key | ||||
| #define VK_LMENU 0xA4     // Left Alt key | ||||
| #define VK_RMENU 0xA5     // Right Alt key | ||||
|  | ||||
| #define VK_BROWSER_BACK 0xA6       // Browser Back key | ||||
| #define VK_BROWSER_FORWARD 0xA7    // Browser Forward key | ||||
| #define VK_BROWSER_REFRESH 0xA8    // Browser Refresh key | ||||
| #define VK_BROWSER_STOP 0xA9       // Browser Stop key | ||||
| #define VK_BROWSER_SEARCH 0xAA     // Browser Search key | ||||
| #define VK_BROWSER_FAVORITES 0xAB  // Browser Favorites key | ||||
| #define VK_BROWSER_HOME 0xAC       // Browser Start and Home key | ||||
| #define VK_VOLUME_MUTE 0xAD        // Volume Mute key | ||||
| #define VK_VOLUME_DOWN 0xAE        // Volume Down key | ||||
| #define VK_VOLUME_UP 0xAF          // Volume Up key | ||||
|  | ||||
| #define VK_MEDIA_NEXT_TRACK 0xB0     // Next Track key | ||||
| #define VK_MEDIA_PREV_TRACK 0xB1     // Previous Track key | ||||
| #define VK_MEDIA_STOP 0xB2           // Stop Media key | ||||
| #define VK_MEDIA_PLAY_PAUSE 0xB3     // Play/Pause Media key | ||||
| #define VK_LAUNCH_MAIL 0xB4          // Start Mail key | ||||
| #define VK_LAUNCH_MEDIA_SELECT 0xB5  // Select Media key | ||||
| #define VK_LAUNCH_APP1 0xB6          // Start Application 1 key | ||||
| #define VK_LAUNCH_APP2 0xB7          // Start Application 2 key | ||||
| // 0xB8–0xB9 Reserved | ||||
|  | ||||
| #define VK_OEM_1 0xBA       // For US: Semicolon/Colon key | ||||
| #define VK_OEM_PLUS 0xBB    // Equals/Plus key | ||||
| #define VK_OEM_COMMA 0xBC   // Comma/Less Than key | ||||
| #define VK_OEM_MINUS 0xBD   // Dash/Underscore key | ||||
| #define VK_OEM_PERIOD 0xBE  // Period/Greater Than key | ||||
| #define VK_OEM_2 0xBF       // Slash/Question Mark key | ||||
| #define VK_OEM_3 0xC0       // Grave Accent/Tilde key | ||||
| // 0xC1–0xDA Reserved | ||||
|  | ||||
| #define VK_OEM_4 0xDB  // Left Brace key | ||||
| #define VK_OEM_5 0xDC  // Backslash/Pipe key | ||||
| #define VK_OEM_6 0xDD  // Right Brace key | ||||
| #define VK_OEM_7 0xDE  // Apostrophe/Quote key | ||||
| #define VK_OEM_8 0xDF  // (Canadian CSA: Right Ctrl key) | ||||
| // 0xE0 Reserved | ||||
| #define VK_OEM_102 0xE2  // (European ISO: Backslash/Pipe key) | ||||
| // 0xE3–E4 OEM specific | ||||
| #define VK_PROCESSKEY 0xE5  // IME PROCESS key | ||||
| // 0xE6 OEM specific | ||||
| #define VK_PACKET 0xE7  // Unicode characters as keystrokes | ||||
| // 0xE8 Unassigned | ||||
| // 0xE9–F5 OEM specific | ||||
| #define VK_ATTN 0xF6       // Attn key | ||||
| #define VK_CRSEL 0xF7      // CrSel key | ||||
| #define VK_EXSEL 0xF8      // ExSel key | ||||
| #define VK_EREOF 0xF9      // Erase EOF key | ||||
| #define VK_PLAY 0xFA       // Play key | ||||
| #define VK_ZOOM 0xFB       // Zoom key | ||||
| #define VK_NONAME 0xFC     // Reserved | ||||
| #define VK_PA1 0xFD        // PA1 key | ||||
| #define VK_OEM_CLEAR 0xFE  // Clear key | ||||
|  | ||||
| #endif  // VIRTUAL_KEY_CODES_H | ||||
							
								
								
									
										2878
									
								
								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 165 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 165 | ||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 120 | ||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167 | ||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 171 | ||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_TURN_CHECKBOX_PADDING_CN 171 | ||||
| #define ENABLE_TURN_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_SRTP_CHECKBOX_PADDING_CN 171 | ||||
| #define ENABLE_SRTP_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171 | ||||
| #define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218 | ||||
| #define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171 | ||||
| #define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218 | ||||
| #define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90 | ||||
| #define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN 137 | ||||
| #define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90 | ||||
| #define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN 137 | ||||
| #define SETTINGS_SELECT_WINDOW_WIDTH 73 | ||||
| #define SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH 130 | ||||
| #define SETTINGS_OK_BUTTON_PADDING_CN 65 | ||||
| #define SETTINGS_OK_BUTTON_PADDING_EN 83 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78 | ||||
| #define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91 | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										170
									
								
								src/gui/assets/localization/localization.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,170 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-05-29 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
| #ifndef _LOCALIZATION_H_ | ||||
| #define _LOCALIZATION_H_ | ||||
|  | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #if _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
| namespace localization { | ||||
|  | ||||
| static std::vector<std::string> local_desktop = { | ||||
|     reinterpret_cast<const char*>(u8"本桌面"), "Local Desktop"}; | ||||
| static std::vector<std::string> local_id = { | ||||
|     reinterpret_cast<const char*>(u8"本机ID"), "Local ID"}; | ||||
| static std::vector<std::string> local_id_copied_to_clipboard = { | ||||
|     reinterpret_cast<const char*>(u8"已复制到剪贴板"), "Copied to clipboard"}; | ||||
| static std::vector<std::string> password = { | ||||
|     reinterpret_cast<const char*>(u8"密码"), "Password"}; | ||||
| static std::vector<std::string> max_password_len = { | ||||
|     reinterpret_cast<const char*>(u8"最大6个字符"), "Max 6 chars"}; | ||||
|  | ||||
| static std::vector<std::string> remote_desktop = { | ||||
|     reinterpret_cast<const char*>(u8"控制远程桌面"), "Control Remote Desktop"}; | ||||
| static std::vector<std::string> remote_id = { | ||||
|     reinterpret_cast<const char*>(u8"对端ID"), "Remote ID"}; | ||||
| static std::vector<std::string> connect = { | ||||
|     reinterpret_cast<const char*>(u8"连接"), "Connect"}; | ||||
| static std::vector<std::string> recent_connections = { | ||||
|     reinterpret_cast<const char*>(u8"近期连接"), "Recent Connections"}; | ||||
| static std::vector<std::string> disconnect = { | ||||
|     reinterpret_cast<const char*>(u8"断开连接"), "Disconnect"}; | ||||
| static std::vector<std::string> fullscreen = { | ||||
|     reinterpret_cast<const char*>(u8"全屏"), " Fullscreen"}; | ||||
| static std::vector<std::string> show_net_traffic_stats = { | ||||
|     reinterpret_cast<const char*>(u8"显示流量统计"), "Show Net Traffic Stats"}; | ||||
| static std::vector<std::string> hide_net_traffic_stats = { | ||||
|     reinterpret_cast<const char*>(u8"隐藏流量统计"), "Hide Net Traffic Stats"}; | ||||
| static std::vector<std::string> video = { | ||||
|     reinterpret_cast<const char*>(u8"视频"), "Video"}; | ||||
| static std::vector<std::string> audio = { | ||||
|     reinterpret_cast<const char*>(u8"音频"), "Audio"}; | ||||
| static std::vector<std::string> data = {reinterpret_cast<const char*>(u8"数据"), | ||||
|                                         "Data"}; | ||||
| static std::vector<std::string> total = { | ||||
|     reinterpret_cast<const char*>(u8"总计"), "Total"}; | ||||
| static std::vector<std::string> in = {reinterpret_cast<const char*>(u8"输入"), | ||||
|                                       "In"}; | ||||
| static std::vector<std::string> out = {reinterpret_cast<const char*>(u8"输出"), | ||||
|                                        "Out"}; | ||||
| static std::vector<std::string> loss_rate = { | ||||
|     reinterpret_cast<const char*>(u8"丢包率"), "Loss Rate"}; | ||||
| static std::vector<std::string> exit_fullscreen = { | ||||
|     reinterpret_cast<const char*>(u8"退出全屏"), "Exit fullscreen"}; | ||||
| static std::vector<std::string> control_mouse = { | ||||
|     reinterpret_cast<const char*>(u8"控制"), "Control"}; | ||||
| static std::vector<std::string> release_mouse = { | ||||
|     reinterpret_cast<const char*>(u8"释放"), "Release"}; | ||||
| static std::vector<std::string> audio_capture = { | ||||
|     reinterpret_cast<const char*>(u8"声音"), "Audio"}; | ||||
| static std::vector<std::string> mute = { | ||||
|     reinterpret_cast<const char*>(u8" 静音"), " Mute"}; | ||||
| static std::vector<std::string> settings = { | ||||
|     reinterpret_cast<const char*>(u8"设置"), "Settings"}; | ||||
| static std::vector<std::string> language = { | ||||
|     reinterpret_cast<const char*>(u8"语言:"), "Language:"}; | ||||
| static std::vector<std::string> language_zh = { | ||||
|     reinterpret_cast<const char*>(u8"中文"), "Chinese"}; | ||||
| static std::vector<std::string> language_en = { | ||||
|     reinterpret_cast<const char*>(u8"英文"), "English"}; | ||||
| static std::vector<std::string> video_quality = { | ||||
|     reinterpret_cast<const char*>(u8"视频质量:"), "Video Quality:"}; | ||||
| static std::vector<std::string> video_frame_rate = { | ||||
|     reinterpret_cast<const char*>(u8"画面采集帧率:"), | ||||
|     "Video Capture Frame Rate:"}; | ||||
| static std::vector<std::string> video_quality_high = { | ||||
|     reinterpret_cast<const char*>(u8"高"), "High"}; | ||||
| static std::vector<std::string> video_quality_medium = { | ||||
|     reinterpret_cast<const char*>(u8"中"), "Medium"}; | ||||
| static std::vector<std::string> video_quality_low = { | ||||
|     reinterpret_cast<const char*>(u8"低"), "Low"}; | ||||
| static std::vector<std::string> video_encode_format = { | ||||
|     reinterpret_cast<const char*>(u8"视频编码格式:"), "Video Encode Format:"}; | ||||
| static std::vector<std::string> av1 = {reinterpret_cast<const char*>(u8"AV1"), | ||||
|                                        "AV1"}; | ||||
| static std::vector<std::string> h264 = { | ||||
|     reinterpret_cast<const char*>(u8"H.264"), "H.264"}; | ||||
| static std::vector<std::string> enable_hardware_video_codec = { | ||||
|     reinterpret_cast<const char*>(u8"启用硬件编解码器:"), | ||||
|     "Enable Hardware Video Codec:"}; | ||||
| static std::vector<std::string> enable_turn = { | ||||
|     reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"}; | ||||
| static std::vector<std::string> enable_srtp = { | ||||
|     reinterpret_cast<const char*>(u8"启用SRTP:"), "Enable SRTP:"}; | ||||
| static std::vector<std::string> self_hosted_server_config = { | ||||
|     reinterpret_cast<const char*>(u8"自托管服务器配置"), | ||||
|     "Self-Hosted Server Config"}; | ||||
| static std::vector<std::string> self_hosted_server_settings = { | ||||
|     reinterpret_cast<const char*>(u8"自托管服务器设置"), | ||||
|     "Self-Hosted Server Settings"}; | ||||
| static std::vector<std::string> self_hosted_server_address = { | ||||
|     reinterpret_cast<const char*>(u8"服务器地址:"), "Server Address:"}; | ||||
| static std::vector<std::string> self_hosted_server_port = { | ||||
|     reinterpret_cast<const char*>(u8"服务器端口:"), "Server Port:"}; | ||||
| static std::vector<std::string> self_hosted_server_certificate_path = { | ||||
|     reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"}; | ||||
| static std::vector<std::string> select_a_file = { | ||||
|     reinterpret_cast<const char*>(u8"请选择文件"), "Please select a file"}; | ||||
| static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"), | ||||
|                                       "OK"}; | ||||
| static std::vector<std::string> cancel = { | ||||
|     reinterpret_cast<const char*>(u8"取消"), "Cancel"}; | ||||
|  | ||||
| static std::vector<std::string> new_password = { | ||||
|     reinterpret_cast<const char*>(u8"请输入六位密码:"), | ||||
|     "Please input a six-char password:"}; | ||||
|  | ||||
| static std::vector<std::string> input_password = { | ||||
|     reinterpret_cast<const char*>(u8"请输入密码:"), "Please input password:"}; | ||||
| static std::vector<std::string> validate_password = { | ||||
|     reinterpret_cast<const char*>(u8"验证密码中..."), "Validate password ..."}; | ||||
| static std::vector<std::string> reinput_password = { | ||||
|     reinterpret_cast<const char*>(u8"请重新输入密码"), | ||||
|     "Please input password again"}; | ||||
|  | ||||
| static std::vector<std::string> remember_password = { | ||||
|     reinterpret_cast<const char*>(u8"记住密码"), "Remember password"}; | ||||
|  | ||||
| static std::vector<std::string> signal_connected = { | ||||
|     reinterpret_cast<const char*>(u8"已连接服务器"), "Connected"}; | ||||
| static std::vector<std::string> signal_disconnected = { | ||||
|     reinterpret_cast<const char*>(u8"未连接服务器"), "Disconnected"}; | ||||
|  | ||||
| static std::vector<std::string> p2p_connected = { | ||||
|     reinterpret_cast<const char*>(u8"对等连接已建立"), "P2P Connected"}; | ||||
| static std::vector<std::string> p2p_disconnected = { | ||||
|     reinterpret_cast<const char*>(u8"对等连接已断开"), "P2P Disconnected"}; | ||||
| static std::vector<std::string> p2p_connecting = { | ||||
|     reinterpret_cast<const char*>(u8"正在建立对等连接..."), | ||||
|     "P2P Connecting ..."}; | ||||
| static std::vector<std::string> p2p_failed = { | ||||
|     reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"}; | ||||
| static std::vector<std::string> p2p_closed = { | ||||
|     reinterpret_cast<const char*>(u8"对等连接已关闭"), "P2P closed"}; | ||||
|  | ||||
| static std::vector<std::string> no_such_id = { | ||||
|     reinterpret_cast<const char*>(u8"无此ID"), "No such ID"}; | ||||
|  | ||||
| static std::vector<std::string> about = { | ||||
|     reinterpret_cast<const char*>(u8"关于"), "About"}; | ||||
| static std::vector<std::string> version = { | ||||
|     reinterpret_cast<const char*>(u8"版本"), "Version"}; | ||||
|  | ||||
| static std::vector<std::string> confirm_delete_connection = { | ||||
|     reinterpret_cast<const char*>(u8"确认删除此连接"), | ||||
|     "Confirm to delete this connection"}; | ||||
| #if _WIN32 | ||||
|  | ||||
| static std::vector<std::string> minimize_to_tray = { | ||||
|     reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"), | ||||
|     "Minimize to system tray when exit:"}; | ||||
| static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"}; | ||||
| #endif | ||||
| }  // namespace localization | ||||
| #endif | ||||
							
								
								
									
										1150
									
								
								src/gui/main.cpp
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										290
									
								
								src/gui/panels/local_peer_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,290 @@ | ||||
| #include <random> | ||||
|  | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::LocalWindow() { | ||||
|   ImGui::SetNextWindowPos(ImVec2(-1.0f, title_bar_height_), ImGuiCond_Always); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   ImGui::BeginChild("LocalDesktopWindow", | ||||
|                     ImVec2(local_window_width_, local_window_height_), | ||||
|                     ImGuiChildFlags_None, | ||||
|                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                         ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_); | ||||
|   ImGui::Indent(main_child_window_x_padding_); | ||||
|  | ||||
|   ImGui::TextColored( | ||||
|       ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s", | ||||
|       localization::local_desktop[localization_language_index_].c_str()); | ||||
|  | ||||
|   ImGui::Spacing(); | ||||
|   { | ||||
|     ImGui::SetNextWindowPos( | ||||
|         ImVec2(main_child_window_x_padding_, | ||||
|                title_bar_height_ + main_child_window_y_padding_), | ||||
|         ImGuiCond_Always); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255, | ||||
|                                                    242.0f / 255, 1.0f)); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||
|     ImGui::BeginChild( | ||||
|         "LocalDesktopWindow_1", | ||||
|         ImVec2(local_child_window_width_, local_child_window_height_), | ||||
|         ImGuiChildFlags_Border, | ||||
|         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|             ImGuiWindowFlags_NoTitleBar | | ||||
|             ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|     ImGui::PopStyleVar(); | ||||
|     ImGui::PopStyleColor(); | ||||
|     { | ||||
|       ImGui::SetWindowFontScale(0.8f); | ||||
|       ImGui::Text("%s", | ||||
|                   localization::local_id[localization_language_index_].c_str()); | ||||
|  | ||||
|       ImGui::Spacing(); | ||||
|  | ||||
|       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|  | ||||
|       if (strcmp(client_id_display_, client_id_)) { | ||||
|         for (int i = 0, j = 0; i < sizeof(client_id_); i++, j++) { | ||||
|           client_id_display_[j] = client_id_[i]; | ||||
|           if (i == 2 || i == 5) { | ||||
|             client_id_display_[++j] = ' '; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       ImGui::InputText( | ||||
|           "##local_id", client_id_display_, IM_ARRAYSIZE(client_id_display_), | ||||
|           ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_ReadOnly); | ||||
|       ImGui::PopStyleVar(); | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|  | ||||
|       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0)); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       if (ImGui::Button(ICON_FA_COPY, ImVec2(22, 38))) { | ||||
|         local_id_copied_ = true; | ||||
|         ImGui::SetClipboardText(client_id_); | ||||
|         copy_start_time_ = ImGui::GetTime(); | ||||
|       } | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::PopStyleColor(3); | ||||
|  | ||||
|       double time_duration = ImGui::GetTime() - copy_start_time_; | ||||
|       if (local_id_copied_ && time_duration < 1.0f) { | ||||
|         const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     notification_window_width_) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     notification_window_height_) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(notification_window_width_, notification_window_height_)); | ||||
|         ImGui::PushStyleColor( | ||||
|             ImGuiCol_WindowBg, | ||||
|             ImVec4(1.0f, 1.0f, 1.0f, 1.0f - (float)time_duration)); | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|         ImGui::Begin("ConnectionStatusWindow", nullptr, | ||||
|                      ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | | ||||
|                          ImGuiWindowFlags_NoSavedSettings); | ||||
|         ImGui::PopStyleVar(2); | ||||
|         ImGui::PopStyleColor(); | ||||
|  | ||||
|         auto window_width = ImGui::GetWindowSize().x; | ||||
|         auto window_height = ImGui::GetWindowSize().y; | ||||
|         ImGui::SetWindowFontScale(0.8f); | ||||
|         std::string text = localization::local_id_copied_to_clipboard | ||||
|             [localization_language_index_]; | ||||
|         auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
|         ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||
|         ImGui::SetCursorPosY(window_height * 0.5f); | ||||
|         ImGui::PushStyleColor(ImGuiCol_Text, | ||||
|                               ImVec4(0, 0, 0, 1.0f - (float)time_duration)); | ||||
|         ImGui::Text("%s", text.c_str()); | ||||
|         ImGui::PopStyleColor(); | ||||
|         ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|         ImGui::End(); | ||||
|       } | ||||
|  | ||||
|       ImGui::Spacing(); | ||||
|       ImGui::Separator(); | ||||
|       ImGui::Spacing(); | ||||
|  | ||||
|       ImGui::SetWindowFontScale(0.8f); | ||||
|       ImGui::Text("%s", | ||||
|                   localization::password[localization_language_index_].c_str()); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); | ||||
|       ImGui::Spacing(); | ||||
|  | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|       ImGui::InputTextWithHint( | ||||
|           "##server_pwd", | ||||
|           localization::max_password_len[localization_language_index_].c_str(), | ||||
|           password_saved_, IM_ARRAYSIZE(password_saved_), | ||||
|           show_password_ | ||||
|               ? ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_ReadOnly | ||||
|               : ImGuiInputTextFlags_CharsNoBlank | | ||||
|                     ImGuiInputTextFlags_Password | | ||||
|                     ImGuiInputTextFlags_ReadOnly); | ||||
|       ImGui::PopStyleVar(); | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0)); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       auto l_x = ImGui::GetCursorScreenPos().x; | ||||
|       auto l_y = ImGui::GetCursorScreenPos().y; | ||||
|       if (ImGui::Button(ICON_FA_EYE, ImVec2(22, 38))) { | ||||
|         show_password_ = !show_password_; | ||||
|       } | ||||
|  | ||||
|       if (!show_password_) { | ||||
|         ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||
|         draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 12.5f), | ||||
|                            ImVec2(l_x + 20.3f, l_y + 26.5f), | ||||
|                            IM_COL32(239, 240, 242, 255), 2.0f); | ||||
|         draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 11.0f), | ||||
|                            ImVec2(l_x + 20.3f, l_y + 25.0f), | ||||
|                            IM_COL32(0, 0, 0, 255), 1.5f); | ||||
|       } | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|  | ||||
|       if (ImGui::Button(ICON_FA_PEN, ImVec2(22, 38))) { | ||||
|         show_reset_password_window_ = true; | ||||
|       } | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::PopStyleColor(3); | ||||
|  | ||||
|       if (show_reset_password_window_) { | ||||
|         const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
|  | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     connection_status_window_width_) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     connection_status_window_height_) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_, | ||||
|                                         connection_status_window_height_)); | ||||
|  | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|         ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0)); | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|  | ||||
|         ImGui::Begin("ResetPasswordWindow", nullptr, | ||||
|                      ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                          ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                          ImGuiWindowFlags_NoSavedSettings); | ||||
|         ImGui::PopStyleVar(2); | ||||
|         ImGui::PopStyleColor(); | ||||
|  | ||||
|         auto window_width = ImGui::GetWindowSize().x; | ||||
|         auto window_height = ImGui::GetWindowSize().y; | ||||
|         std::string text = | ||||
|             localization::new_password[localization_language_index_]; | ||||
|         ImGui::SetWindowFontScale(0.5f); | ||||
|         auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
|         ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||
|         ImGui::SetCursorPosY(window_height * 0.2f); | ||||
|         ImGui::Text("%s", text.c_str()); | ||||
|  | ||||
|         ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f); | ||||
|         ImGui::SetCursorPosY(window_height * 0.4f); | ||||
|         ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2); | ||||
|  | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|  | ||||
|         if (focus_on_input_widget_) { | ||||
|           ImGui::SetKeyboardFocusHere(); | ||||
|           focus_on_input_widget_ = false; | ||||
|         } | ||||
|  | ||||
|         bool enter_pressed = ImGui::InputText( | ||||
|             "##new_password", new_password_, IM_ARRAYSIZE(new_password_), | ||||
|             ImGuiInputTextFlags_CharsNoBlank | | ||||
|                 ImGuiInputTextFlags_EnterReturnsTrue); | ||||
|  | ||||
|         ImGui::PopStyleVar(); | ||||
|  | ||||
|         ImGui::SetCursorPosX(window_width * 0.315f); | ||||
|         ImGui::SetCursorPosY(window_height * 0.75f); | ||||
|  | ||||
|         // OK | ||||
|         if (ImGui::Button( | ||||
|                 localization::ok[localization_language_index_].c_str()) || | ||||
|             enter_pressed) { | ||||
|           if (6 != strlen(new_password_)) { | ||||
|             LOG_ERROR("Invalid password length"); | ||||
|             show_reset_password_window_ = true; | ||||
|             focus_on_input_widget_ = true; | ||||
|           } else { | ||||
|             show_reset_password_window_ = false; | ||||
|             memset(&password_saved_, 0, sizeof(password_saved_)); | ||||
|             strncpy(password_saved_, new_password_, | ||||
|                     sizeof(password_saved_) - 1); | ||||
|             password_saved_[sizeof(password_saved_) - 1] = '\0'; | ||||
|  | ||||
|             std::string client_id_with_password = | ||||
|                 std::string(client_id_) + "@" + password_saved_; | ||||
|             strncpy(client_id_with_password_, client_id_with_password.c_str(), | ||||
|                     sizeof(client_id_with_password_) - 1); | ||||
|             client_id_with_password_[sizeof(client_id_with_password_) - 1] = | ||||
|                 '\0'; | ||||
|  | ||||
|             SaveSettingsIntoCacheFile(); | ||||
|  | ||||
|             LeaveConnection(peer_, client_id_); | ||||
|             DestroyPeer(&peer_); | ||||
|             focus_on_input_widget_ = true; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         ImGui::SameLine(); | ||||
|  | ||||
|         if (ImGui::Button( | ||||
|                 localization::cancel[localization_language_index_].c_str())) { | ||||
|           show_reset_password_window_ = false; | ||||
|           focus_on_input_widget_ = true; | ||||
|           memset(new_password_, 0, sizeof(new_password_)); | ||||
|         } | ||||
|  | ||||
|         ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|         ImGui::End(); | ||||
|         ImGui::PopStyleVar(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ImGui::EndChild(); | ||||
|   } | ||||
|  | ||||
|   ImGui::EndChild(); | ||||
|   ImGui::PopStyleVar(); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										286
									
								
								src/gui/panels/recent_connections_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,286 @@ | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::RecentConnectionsWindow() { | ||||
|   ImGui::SetNextWindowPos( | ||||
|       ImVec2(0, title_bar_height_ + local_window_height_ - 1.0f), | ||||
|       ImGuiCond_Always); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   ImGui::BeginChild( | ||||
|       "RecentConnectionsWindow", | ||||
|       ImVec2(main_window_width_default_, | ||||
|              main_window_height_default_ - title_bar_height_ - | ||||
|                  local_window_height_ - status_bar_height_ + 1.0f), | ||||
|       ImGuiChildFlags_Border, | ||||
|       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|           ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|           ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|   ImGui::PopStyleVar(); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_); | ||||
|   ImGui::Indent(main_child_window_x_padding_); | ||||
|  | ||||
|   ImGui::TextColored( | ||||
|       ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s", | ||||
|       localization::recent_connections[localization_language_index_].c_str()); | ||||
|  | ||||
|   ShowRecentConnections(); | ||||
|  | ||||
|   ImGui::EndChild(); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::ShowRecentConnections() { | ||||
|   ImGui::SetCursorPosX(25.0f); | ||||
|   ImVec2 sub_window_pos = ImGui::GetCursorPos(); | ||||
|   std::map<std::string, ImVec2> sub_containers_pos; | ||||
|   float recent_connection_sub_container_width = | ||||
|       recent_connection_image_width_ + 16.0f; | ||||
|   float recent_connection_sub_container_height = | ||||
|       recent_connection_image_height_ + 36.0f; | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, | ||||
|                         ImVec4(239.0f / 255, 240.0f / 255, 242.0f / 255, 1.0f)); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||
|   ImGui::BeginChild("RecentConnectionsContainer", | ||||
|                     ImVec2(main_window_width_default_ - 50.0f, 145.0f), | ||||
|                     ImGuiChildFlags_Border, | ||||
|                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                         ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||
|                         ImGuiWindowFlags_AlwaysHorizontalScrollbar | | ||||
|                         ImGuiWindowFlags_NoScrollbar | | ||||
|                         ImGuiWindowFlags_NoScrollWithMouse); | ||||
|   ImGui::PopStyleVar(); | ||||
|   ImGui::PopStyleColor(); | ||||
|   size_t recent_connections_count = recent_connections_.size(); | ||||
|   int count = 0; | ||||
|   float button_width = 22; | ||||
|   float button_height = 22; | ||||
|   for (auto& it : recent_connections_) { | ||||
|     sub_containers_pos[it.first] = ImGui::GetCursorPos(); | ||||
|     std::string recent_connection_sub_window_name = | ||||
|         "RecentConnectionsSubContainer" + it.first; | ||||
|     // recent connections sub container | ||||
|     ImGui::BeginChild(recent_connection_sub_window_name.c_str(), | ||||
|                       ImVec2(recent_connection_sub_container_width, | ||||
|                              recent_connection_sub_container_height), | ||||
|                       ImGuiChildFlags_None, | ||||
|                       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                           ImGuiWindowFlags_NoMove | | ||||
|                           ImGuiWindowFlags_NoTitleBar | | ||||
|                           ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||
|                           ImGuiWindowFlags_NoScrollbar); | ||||
|     std::string connection_info = it.first; | ||||
|  | ||||
|     // remote id length is 9 | ||||
|     // password length is 6 | ||||
|     // connection_info -> remote_id + 'Y' + host_name + '@' + password | ||||
|     //                 -> remote_id + 'N' + host_name | ||||
|     if ('Y' == connection_info[9] && connection_info.size() >= 16) { | ||||
|       size_t pos_y = connection_info.find('Y'); | ||||
|       size_t pos_at = connection_info.find('@'); | ||||
|  | ||||
|       if (pos_y == std::string::npos || pos_at == std::string::npos || | ||||
|           pos_y >= pos_at) { | ||||
|         LOG_ERROR("Invalid filename"); | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       it.second.remote_id = connection_info.substr(0, pos_y); | ||||
|       it.second.remote_host_name = | ||||
|           connection_info.substr(pos_y + 1, pos_at - pos_y - 1); | ||||
|       it.second.password = connection_info.substr(pos_at + 1); | ||||
|       it.second.remember_password = true; | ||||
|     } else if ('N' == connection_info[9] && connection_info.size() >= 10) { | ||||
|       size_t pos_n = connection_info.find('N'); | ||||
|       size_t pos_at = connection_info.find('@'); | ||||
|  | ||||
|       if (pos_n == std::string::npos) { | ||||
|         LOG_ERROR("Invalid filename"); | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       it.second.remote_id = connection_info.substr(0, pos_n); | ||||
|       it.second.remote_host_name = connection_info.substr(pos_n + 1); | ||||
|       it.second.password = ""; | ||||
|       it.second.remember_password = false; | ||||
|     } else { | ||||
|       it.second.remote_host_name = "unknown"; | ||||
|     } | ||||
|  | ||||
|     ImVec2 image_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x + 5.0f, | ||||
|                                      ImGui::GetCursorScreenPos().y + 5.0f); | ||||
|     ImVec2 image_pos = | ||||
|         ImVec2(ImGui::GetCursorPosX() + 5.0f, ImGui::GetCursorPosY() + 5.0f); | ||||
|     ImGui::SetCursorPos(image_pos); | ||||
|     ImGui::Image((ImTextureID)(intptr_t)it.second.texture, | ||||
|                  ImVec2((float)recent_connection_image_width_, | ||||
|                         (float)recent_connection_image_height_)); | ||||
|  | ||||
|     // remote id display button | ||||
|     { | ||||
|       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.2f)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0.2f)); | ||||
|  | ||||
|       ImVec2 dummy_button_pos = | ||||
|           ImVec2(image_pos.x, image_pos.y + recent_connection_image_height_); | ||||
|       std::string dummy_button_name = "##DummyButton" + it.second.remote_id; | ||||
|       ImGui::SetCursorPos(dummy_button_pos); | ||||
|       ImGui::SetWindowFontScale(0.6f); | ||||
|       ImGui::Button(dummy_button_name.c_str(), | ||||
|                     ImVec2(recent_connection_image_width_ - 2 * button_width, | ||||
|                            button_height)); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetCursorPos( | ||||
|           ImVec2(dummy_button_pos.x + 2.0f, dummy_button_pos.y + 1.0f)); | ||||
|       ImGui::SetWindowFontScale(0.65f); | ||||
|       ImGui::Text("%s", it.second.remote_id.c_str()); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::PopStyleColor(3); | ||||
|  | ||||
|       if (ImGui::IsItemHovered()) { | ||||
|         ImGui::BeginTooltip(); | ||||
|         ImGui::SetWindowFontScale(0.5f); | ||||
|         ImGui::Text("%s", it.second.remote_host_name.c_str()); | ||||
|         ImGui::SetWindowFontScale(1.0f); | ||||
|         ImGui::EndTooltip(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, | ||||
|                           ImVec4(0.1f, 0.4f, 0.8f, 1.0f)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ButtonActive, | ||||
|                           ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); | ||||
|     ImGui::SetWindowFontScale(0.5f); | ||||
|     // trash button | ||||
|     { | ||||
|       ImVec2 trash_can_button_pos = ImVec2( | ||||
|           image_pos.x + recent_connection_image_width_ - 2 * button_width, | ||||
|           image_pos.y + recent_connection_image_height_); | ||||
|       ImGui::SetCursorPos(trash_can_button_pos); | ||||
|       std::string trash_can = ICON_FA_TRASH_CAN; | ||||
|       std::string recent_connection_delete_button_name = | ||||
|           trash_can + "##RecentConnectionDelete" + | ||||
|           std::to_string(trash_can_button_pos.x); | ||||
|       if (ImGui::Button(recent_connection_delete_button_name.c_str(), | ||||
|                         ImVec2(button_width, button_height))) { | ||||
|         show_confirm_delete_connection_ = true; | ||||
|         delete_connection_name_ = it.first; | ||||
|       } | ||||
|  | ||||
|       if (delete_connection_ && delete_connection_name_ == it.first) { | ||||
|         if (!thumbnail_->DeleteThumbnail(it.first)) { | ||||
|           reload_recent_connections_ = true; | ||||
|           delete_connection_ = false; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // connect button | ||||
|     { | ||||
|       ImVec2 connect_button_pos = | ||||
|           ImVec2(image_pos.x + recent_connection_image_width_ - button_width, | ||||
|                  image_pos.y + recent_connection_image_height_); | ||||
|       ImGui::SetCursorPos(connect_button_pos); | ||||
|       std::string connect = ICON_FA_ARROW_RIGHT_LONG; | ||||
|       std::string connect_to_this_connection_button_name = | ||||
|           connect + "##ConnectionTo" + it.first; | ||||
|       if (ImGui::Button(connect_to_this_connection_button_name.c_str(), | ||||
|                         ImVec2(button_width, button_height))) { | ||||
|         ConnectTo(it.second.remote_id, it.second.password.c_str(), | ||||
|                   it.second.remember_password); | ||||
|       } | ||||
|     } | ||||
|     ImGui::SetWindowFontScale(1.0f); | ||||
|     ImGui::PopStyleColor(3); | ||||
|  | ||||
|     ImGui::EndChild(); | ||||
|  | ||||
|     if (count != recent_connections_count - 1) { | ||||
|       ImVec2 line_start = | ||||
|           ImVec2(image_screen_pos.x + recent_connection_image_width_ + 20.0f, | ||||
|                  image_screen_pos.y); | ||||
|       ImVec2 line_end = ImVec2( | ||||
|           image_screen_pos.x + recent_connection_image_width_ + 20.0f, | ||||
|           image_screen_pos.y + recent_connection_image_height_ + button_height); | ||||
|       ImGui::GetWindowDrawList()->AddLine(line_start, line_end, | ||||
|                                           IM_COL32(0, 0, 0, 122), 1.0f); | ||||
|     } | ||||
|  | ||||
|     count++; | ||||
|     ImGui::SameLine(0, count != recent_connections_count ? 26.0f : 0.0f); | ||||
|   } | ||||
|  | ||||
|   ImGui::EndChild(); | ||||
|  | ||||
|   if (show_confirm_delete_connection_) { | ||||
|     ConfirmDeleteConnection(); | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::ConfirmDeleteConnection() { | ||||
|   const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
|   ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                                   connection_status_window_width_) / | ||||
|                                      2, | ||||
|                                  (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                                   connection_status_window_height_) / | ||||
|                                      2)); | ||||
|  | ||||
|   ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_, | ||||
|                                   connection_status_window_height_)); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0)); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|  | ||||
|   ImGui::Begin("ConfirmDeleteConnectionWindow", nullptr, | ||||
|                ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                    ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                    ImGuiWindowFlags_NoSavedSettings); | ||||
|   ImGui::PopStyleVar(2); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   std::string text = | ||||
|       localization::confirm_delete_connection[localization_language_index_]; | ||||
|   ImGui::SetCursorPosX(connection_status_window_width_ * 6 / 19); | ||||
|   ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|  | ||||
|   // ok | ||||
|   ImGui::SetWindowFontScale(0.5f); | ||||
|   if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||
|       ImGui::IsKeyPressed(ImGuiKey_Enter)) { | ||||
|     delete_connection_ = true; | ||||
|     show_confirm_delete_connection_ = false; | ||||
|   } | ||||
|  | ||||
|   ImGui::SameLine(); | ||||
|   // cancel | ||||
|   if (ImGui::Button( | ||||
|           localization::cancel[localization_language_index_].c_str()) || | ||||
|       ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||
|     delete_connection_ = false; | ||||
|     show_confirm_delete_connection_ = false; | ||||
|   } | ||||
|  | ||||
|   auto window_width = ImGui::GetWindowSize().x; | ||||
|   auto window_height = ImGui::GetWindowSize().y; | ||||
|  | ||||
|   auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
|   ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||
|   ImGui::SetCursorPosY(window_height * 0.2f); | ||||
|   ImGui::Text("%s", text.c_str()); | ||||
|   ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|   ImGui::End(); | ||||
|   ImGui::PopStyleVar(); | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										181
									
								
								src/gui/panels/remote_peer_panel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,181 @@ | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| static int InputTextCallback(ImGuiInputTextCallbackData *data); | ||||
|  | ||||
| int Render::RemoteWindow() { | ||||
|   ImGui::SetNextWindowPos(ImVec2(local_window_width_ + 1.0f, title_bar_height_), | ||||
|                           ImGuiCond_Always); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   ImGui::BeginChild("RemoteDesktopWindow", | ||||
|                     ImVec2(remote_window_width_, remote_window_height_), | ||||
|                     ImGuiChildFlags_None, | ||||
|                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                         ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_); | ||||
|   ImGui::Indent(main_child_window_x_padding_ - 1.0f); | ||||
|  | ||||
|   ImGui::TextColored( | ||||
|       ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s", | ||||
|       localization::remote_desktop[localization_language_index_].c_str()); | ||||
|  | ||||
|   ImGui::Spacing(); | ||||
|   { | ||||
|     ImGui::SetNextWindowPos( | ||||
|         ImVec2(local_window_width_ + main_child_window_x_padding_ - 1.0f, | ||||
|                title_bar_height_ + main_child_window_y_padding_), | ||||
|         ImGuiCond_Always); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255, | ||||
|                                                    242.0f / 255, 1.0f)); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||
|  | ||||
|     ImGui::BeginChild( | ||||
|         "RemoteDesktopWindow_1", | ||||
|         ImVec2(remote_child_window_width_, remote_child_window_height_), | ||||
|         ImGuiChildFlags_Border, | ||||
|         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|             ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|             ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|     ImGui::PopStyleVar(); | ||||
|     ImGui::PopStyleColor(); | ||||
|     { | ||||
|       ImGui::SetWindowFontScale(0.8f); | ||||
|       ImGui::Text( | ||||
|           "%s", localization::remote_id[localization_language_index_].c_str()); | ||||
|  | ||||
|       ImGui::Spacing(); | ||||
|       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|       if (re_enter_remote_id_) { | ||||
|         ImGui::SetKeyboardFocusHere(); | ||||
|         re_enter_remote_id_ = false; | ||||
|         memset(remote_id_display_, 0, sizeof(remote_id_display_)); | ||||
|       } | ||||
|       bool enter_pressed = ImGui::InputText( | ||||
|           "##remote_id_", remote_id_display_, IM_ARRAYSIZE(remote_id_display_), | ||||
|           ImGuiInputTextFlags_CharsDecimal | | ||||
|               ImGuiInputTextFlags_EnterReturnsTrue | | ||||
|               ImGuiInputTextFlags_CallbackEdit, | ||||
|           InputTextCallback); | ||||
|  | ||||
|       ImGui::PopStyleVar(); | ||||
|       ImGui::SameLine(); | ||||
|  | ||||
|       std::string remote_id = remote_id_display_; | ||||
|       remote_id.erase(remove_if(remote_id.begin(), remote_id.end(), | ||||
|                                 static_cast<int (*)(int)>(&isspace)), | ||||
|                       remote_id.end()); | ||||
|       if (ImGui::Button(ICON_FA_ARROW_RIGHT_LONG, ImVec2(55, 38)) || | ||||
|           enter_pressed) { | ||||
|         connect_button_pressed_ = true; | ||||
|         bool found = false; | ||||
|         for (auto &[id, props] : recent_connections_) { | ||||
|           if (id.find(remote_id) != std::string::npos) { | ||||
|             found = true; | ||||
|             if (client_properties_.find(remote_id) != | ||||
|                 client_properties_.end()) { | ||||
|               if (!client_properties_[remote_id]->connection_established_) { | ||||
|                 ConnectTo(props.remote_id, props.password.c_str(), false); | ||||
|               } else { | ||||
|                 // todo: show warning message | ||||
|                 LOG_INFO("Already connected to [{}]", remote_id); | ||||
|               } | ||||
|             } else { | ||||
|               ConnectTo(props.remote_id, props.password.c_str(), false); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         if (!found) { | ||||
|           ConnectTo(remote_id, "", false); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (need_to_rejoin_) { | ||||
|         need_to_rejoin_ = false; | ||||
|         for (const auto &[_, props] : client_properties_) { | ||||
|           if (props->rejoin_) { | ||||
|             ConnectTo(props->remote_id_, props->remote_password_, | ||||
|                       props->remember_password_); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     ImGui::EndChild(); | ||||
|   } | ||||
|   ImGui::EndChild(); | ||||
|   ImGui::PopStyleVar(); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| static int InputTextCallback(ImGuiInputTextCallbackData *data) { | ||||
|   if (data->BufTextLen > 3 && data->Buf[3] != ' ') { | ||||
|     data->InsertChars(3, " "); | ||||
|   } | ||||
|  | ||||
|   if (data->BufTextLen > 7 && data->Buf[7] != ' ') { | ||||
|     data->InsertChars(7, " "); | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::ConnectTo(const std::string &remote_id, const char *password, | ||||
|                       bool remember_password) { | ||||
|   LOG_INFO("Connect to [{}]", remote_id); | ||||
|   focused_remote_id_ = remote_id; | ||||
|  | ||||
|   if (client_properties_.find(remote_id) == client_properties_.end()) { | ||||
|     client_properties_[remote_id] = | ||||
|         std::make_shared<SubStreamWindowProperties>(); | ||||
|     auto props = client_properties_[remote_id]; | ||||
|     props->local_id_ = "C-" + std::string(client_id_); | ||||
|     props->remote_id_ = remote_id; | ||||
|     memcpy(&props->params_, ¶ms_, sizeof(Params)); | ||||
|     props->params_.user_id = props->local_id_.c_str(); | ||||
|     props->peer_ = CreatePeer(&props->params_); | ||||
|     AddAudioStream(props->peer_, props->audio_label_.c_str()); | ||||
|     AddDataStream(props->peer_, props->data_label_.c_str()); | ||||
|  | ||||
|     if (props->peer_) { | ||||
|       LOG_INFO("[{}] Create peer instance successful", props->local_id_); | ||||
|       Init(props->peer_); | ||||
|       LOG_INFO("[{}] Peer init finish", props->local_id_); | ||||
|     } else { | ||||
|       LOG_INFO("Create peer [{}] instance failed", props->local_id_); | ||||
|     } | ||||
|  | ||||
|     props->connection_status_ = ConnectionStatus::Connecting; | ||||
|   } | ||||
|   int ret = -1; | ||||
|   auto props = client_properties_[remote_id]; | ||||
|   if (!props->connection_established_) { | ||||
|     props->remember_password_ = remember_password; | ||||
|     if (strcmp(password, "") != 0 && | ||||
|         strcmp(password, props->remote_password_) != 0) { | ||||
|       strncpy(props->remote_password_, password, | ||||
|               sizeof(props->remote_password_) - 1); | ||||
|       props->remote_password_[sizeof(props->remote_password_) - 1] = '\0'; | ||||
|     } | ||||
|  | ||||
|     std::string remote_id_with_pwd = remote_id + "@" + password; | ||||
|     ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str()); | ||||
|     if (0 == ret) { | ||||
|       props->rejoin_ = false; | ||||
|     } else { | ||||
|       props->rejoin_ = true; | ||||
|       need_to_rejoin_ = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										1415
									
								
								src/gui/render.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										470
									
								
								src/gui/render.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,470 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-05-29 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _MAIN_WINDOW_H_ | ||||
| #define _MAIN_WINDOW_H_ | ||||
|  | ||||
| #include <SDL3/SDL.h> | ||||
|  | ||||
| #include <atomic> | ||||
| #include <chrono> | ||||
| #include <fstream> | ||||
| #include <mutex> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
|  | ||||
| #include "IconsFontAwesome6.h" | ||||
| #include "config_center.h" | ||||
| #include "device_controller_factory.h" | ||||
| #include "imgui.h" | ||||
| #include "imgui_impl_sdl3.h" | ||||
| #include "imgui_impl_sdlrenderer3.h" | ||||
| #include "imgui_internal.h" | ||||
| #include "minirtc.h" | ||||
| #include "path_manager.h" | ||||
| #include "screen_capturer_factory.h" | ||||
| #include "speaker_capturer_factory.h" | ||||
| #include "thumbnail.h" | ||||
| #if _WIN32 | ||||
| #include "win_tray.h" | ||||
| #endif | ||||
|  | ||||
| class Render { | ||||
|  public: | ||||
|   struct SubStreamWindowProperties { | ||||
|     Params params_; | ||||
|     PeerPtr* peer_ = nullptr; | ||||
|     std::string audio_label_ = "control_audio"; | ||||
|     std::string data_label_ = "control_data"; | ||||
|     std::string local_id_ = ""; | ||||
|     std::string remote_id_ = ""; | ||||
|     bool exit_ = false; | ||||
|     bool signal_connected_ = false; | ||||
|     SignalStatus signal_status_ = SignalStatus::SignalClosed; | ||||
|     bool connection_established_ = false; | ||||
|     bool rejoin_ = false; | ||||
|     bool net_traffic_stats_button_pressed_ = false; | ||||
|     bool mouse_control_button_pressed_ = false; | ||||
|     bool mouse_controller_is_started_ = false; | ||||
|     bool audio_capture_button_pressed_ = false; | ||||
|     bool control_mouse_ = false; | ||||
|     bool streaming_ = false; | ||||
|     bool is_control_bar_in_left_ = true; | ||||
|     bool control_bar_hovered_ = false; | ||||
|     bool display_selectable_hovered_ = false; | ||||
|     bool control_bar_expand_ = true; | ||||
|     bool reset_control_bar_pos_ = false; | ||||
|     bool control_window_width_is_changing_ = false; | ||||
|     bool control_window_height_is_changing_ = false; | ||||
|     bool p2p_mode_ = true; | ||||
|     bool remember_password_ = false; | ||||
|     char remote_password_[7] = ""; | ||||
|     float sub_stream_window_width_ = 1280; | ||||
|     float sub_stream_window_height_ = 720; | ||||
|     float control_window_min_width_ = 20; | ||||
|     float control_window_max_width_ = 230; | ||||
|     float control_window_min_height_ = 40; | ||||
|     float control_window_max_height_ = 170; | ||||
|     float control_window_width_ = 230; | ||||
|     float control_window_height_ = 40; | ||||
|     float control_bar_pos_x_ = 0; | ||||
|     float control_bar_pos_y_ = 30; | ||||
|     float mouse_diff_control_bar_pos_x_ = 0; | ||||
|     float mouse_diff_control_bar_pos_y_ = 0; | ||||
|     double control_bar_button_pressed_time_ = 0; | ||||
|     double net_traffic_stats_button_pressed_time_ = 0; | ||||
|     unsigned char* dst_buffer_ = nullptr; | ||||
|     size_t dst_buffer_capacity_ = 0; | ||||
|     float mouse_pos_x_ = 0; | ||||
|     float mouse_pos_y_ = 0; | ||||
|     float mouse_pos_x_last_ = 0; | ||||
|     float mouse_pos_y_last_ = 0; | ||||
|     int texture_width_ = 1280; | ||||
|     int texture_height_ = 720; | ||||
|     int video_width_ = 0; | ||||
|     int video_height_ = 0; | ||||
|     int video_width_last_ = 0; | ||||
|     int video_height_last_ = 0; | ||||
|     int selected_display_ = 0; | ||||
|     size_t video_size_ = 0; | ||||
|     bool tab_selected_ = false; | ||||
|     bool tab_opened_ = true; | ||||
|     std::optional<float> pos_x_before_docked_; | ||||
|     std::optional<float> pos_y_before_docked_; | ||||
|     float render_window_x_ = 0; | ||||
|     float render_window_y_ = 0; | ||||
|     float render_window_width_ = 0; | ||||
|     float render_window_height_ = 0; | ||||
|     std::string fullscreen_button_label_ = "Fullscreen"; | ||||
|     std::string net_traffic_stats_button_label_ = "Show Net Traffic Stats"; | ||||
|     std::string mouse_control_button_label_ = "Mouse Control"; | ||||
|     std::string audio_capture_button_label_ = "Audio Capture"; | ||||
|     std::string remote_host_name_ = ""; | ||||
|     std::vector<DisplayInfo> display_info_list_; | ||||
|     SDL_Texture* stream_texture_ = nullptr; | ||||
|     uint8_t* argb_buffer_ = nullptr; | ||||
|     int argb_buffer_size_ = 0; | ||||
|     SDL_Rect stream_render_rect_; | ||||
|     SDL_Rect stream_render_rect_last_; | ||||
|     ImVec2 control_window_pos_; | ||||
|     ConnectionStatus connection_status_ = ConnectionStatus::Closed; | ||||
|     TraversalMode traversal_mode_ = TraversalMode::UnknownMode; | ||||
|     int fps_ = 0; | ||||
|     int frame_count_ = 0; | ||||
|     std::chrono::steady_clock::time_point last_time_; | ||||
|     XNetTrafficStats net_traffic_stats_; | ||||
|   }; | ||||
|  | ||||
|  public: | ||||
|   Render(); | ||||
|   ~Render(); | ||||
|  | ||||
|  public: | ||||
|   int Run(); | ||||
|  | ||||
|  private: | ||||
|   void InitializeLogger(); | ||||
|   void InitializeSettings(); | ||||
|   void InitializeSDL(); | ||||
|   void InitializeModules(); | ||||
|   void InitializeMainWindow(); | ||||
|   void MainLoop(); | ||||
|   void UpdateLabels(); | ||||
|   void UpdateInteractions(); | ||||
|   void HandleRecentConnections(); | ||||
|   void HandleStreamWindow(); | ||||
|   void Cleanup(); | ||||
|   void CleanupFactories(); | ||||
|   void CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props); | ||||
|   void CleanupPeers(); | ||||
|   void CleanSubStreamWindowProperties( | ||||
|       std::shared_ptr<SubStreamWindowProperties> props); | ||||
|   void UpdateRenderRect(); | ||||
|   void ProcessSdlEvent(const SDL_Event& event); | ||||
|  | ||||
|  private: | ||||
|   int CreateStreamRenderWindow(); | ||||
|   int TitleBar(bool main_window); | ||||
|   int MainWindow(); | ||||
|   int StreamWindow(); | ||||
|   int LocalWindow(); | ||||
|   int RemoteWindow(); | ||||
|   int RecentConnectionsWindow(); | ||||
|   int SettingWindow(); | ||||
|   int SelfHostedServerWindow(); | ||||
|   int ShowSimpleFileBrowser(); | ||||
|   int ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|   int ControlBar(std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|   int AboutWindow(); | ||||
|   int StatusBar(); | ||||
|   bool ConnectionStatusWindow( | ||||
|       std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|   int ShowRecentConnections(); | ||||
|  | ||||
|  private: | ||||
|   int ConnectTo(const std::string& remote_id, const char* password, | ||||
|                 bool remember_password); | ||||
|   int CreateMainWindow(); | ||||
|   int DestroyMainWindow(); | ||||
|   int CreateStreamWindow(); | ||||
|   int DestroyStreamWindow(); | ||||
|   int SetupFontAndStyle(); | ||||
|   int SetupMainWindow(); | ||||
|   int DestroyMainWindowContext(); | ||||
|   int SetupStreamWindow(); | ||||
|   int DestroyStreamWindowContext(); | ||||
|   int DrawMainWindow(); | ||||
|   int DrawStreamWindow(); | ||||
|   int ConfirmDeleteConnection(); | ||||
|   int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|   void DrawConnectionStatusText( | ||||
|       std::shared_ptr<SubStreamWindowProperties>& props); | ||||
|  | ||||
|  public: | ||||
|   static void OnReceiveVideoBufferCb(const XVideoFrame* video_frame, | ||||
|                                      const char* user_id, size_t user_id_size, | ||||
|                                      void* user_data); | ||||
|  | ||||
|   static void OnReceiveAudioBufferCb(const char* data, size_t size, | ||||
|                                      const char* user_id, size_t user_id_size, | ||||
|                                      void* user_data); | ||||
|  | ||||
|   static void OnReceiveDataBufferCb(const char* data, size_t size, | ||||
|                                     const char* user_id, size_t user_id_size, | ||||
|                                     void* user_data); | ||||
|  | ||||
|   static void OnSignalStatusCb(SignalStatus status, const char* user_id, | ||||
|                                size_t user_id_size, void* user_data); | ||||
|  | ||||
|   static void OnConnectionStatusCb(ConnectionStatus status, const char* user_id, | ||||
|                                    size_t user_id_size, void* user_data); | ||||
|  | ||||
|   static void NetStatusReport(const char* client_id, size_t client_id_size, | ||||
|                               TraversalMode mode, | ||||
|                               const XNetTrafficStats* net_traffic_stats, | ||||
|                               const char* user_id, const size_t user_id_size, | ||||
|                               void* user_data); | ||||
|  | ||||
|   static SDL_HitTestResult HitTestCallback(SDL_Window* window, | ||||
|                                            const SDL_Point* area, void* data); | ||||
|  | ||||
|   static std::vector<char> SerializeRemoteAction(const RemoteAction& action); | ||||
|  | ||||
|   static bool DeserializeRemoteAction(const char* data, size_t size, | ||||
|                                       RemoteAction& out); | ||||
|  | ||||
|   static void FreeRemoteAction(RemoteAction& action); | ||||
|  | ||||
|  private: | ||||
|   int SendKeyCommand(int key_code, bool is_down); | ||||
|   int ProcessMouseEvent(const SDL_Event& event); | ||||
|  | ||||
|   static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len); | ||||
|   static void SdlCaptureAudioOut(void* userdata, Uint8* stream, int len); | ||||
|  | ||||
|  private: | ||||
|   int SaveSettingsIntoCacheFile(); | ||||
|   int LoadSettingsFromCacheFile(); | ||||
|  | ||||
|   int ScreenCapturerInit(); | ||||
|   int StartScreenCapturer(); | ||||
|   int StopScreenCapturer(); | ||||
|  | ||||
|   int StartSpeakerCapturer(); | ||||
|   int StopSpeakerCapturer(); | ||||
|  | ||||
|   int StartMouseController(); | ||||
|   int StopMouseController(); | ||||
|  | ||||
|   int StartKeyboardCapturer(); | ||||
|   int StopKeyboardCapturer(); | ||||
|  | ||||
|   int CreateConnectionPeer(); | ||||
|  | ||||
|   int AudioDeviceInit(); | ||||
|   int AudioDeviceDestroy(); | ||||
|  | ||||
|  private: | ||||
|   struct CDCache { | ||||
|     char client_id_with_password[17]; | ||||
|     int language; | ||||
|     int video_quality; | ||||
|     int video_frame_rate; | ||||
|     int video_encode_format; | ||||
|     bool enable_hardware_video_codec; | ||||
|     bool enable_turn; | ||||
|     bool enable_srtp; | ||||
|  | ||||
|     unsigned char key[16]; | ||||
|     unsigned char iv[16]; | ||||
|   }; | ||||
|  | ||||
|  private: | ||||
|   CDCache cd_cache_; | ||||
|   std::mutex cd_cache_mutex_; | ||||
|   std::unique_ptr<ConfigCenter> config_center_; | ||||
|   ConfigCenter::LANGUAGE localization_language_ = | ||||
|       ConfigCenter::LANGUAGE::CHINESE; | ||||
|   std::unique_ptr<PathManager> path_manager_; | ||||
|   std::string cert_path_; | ||||
|   std::string exec_log_path_; | ||||
|   std::string dll_log_path_; | ||||
|   std::string cache_path_; | ||||
|   int localization_language_index_ = -1; | ||||
|   int localization_language_index_last_ = -1; | ||||
|   bool modules_inited_ = false; | ||||
|   /* ------ all windows property start ------ */ | ||||
|   float title_bar_width_ = 640; | ||||
|   float title_bar_height_ = 30; | ||||
|   /* ------ all windows property end ------ */ | ||||
|  | ||||
|   /* ------ main window property start ------ */ | ||||
|   // thumbnail | ||||
|   unsigned char aes128_key_[16]; | ||||
|   unsigned char aes128_iv_[16]; | ||||
|   std::unique_ptr<Thumbnail> thumbnail_; | ||||
|  | ||||
|   // recent connections | ||||
|   std::vector<std::pair<std::string, Thumbnail::RecentConnection>> | ||||
|       recent_connections_; | ||||
|   int recent_connection_image_width_ = 160; | ||||
|   int recent_connection_image_height_ = 90; | ||||
|   uint32_t recent_connection_image_save_time_ = 0; | ||||
|  | ||||
|   // main window render | ||||
|   SDL_Window* main_window_ = nullptr; | ||||
|   SDL_Renderer* main_renderer_ = nullptr; | ||||
|   ImGuiContext* main_ctx_ = nullptr; | ||||
|   bool exit_ = false; | ||||
|   const int sdl_refresh_ms_ = 16;  // ~60 FPS | ||||
| #if _WIN32 | ||||
|   std::unique_ptr<WinTray> tray_; | ||||
| #endif | ||||
|  | ||||
|   // main window properties | ||||
|   bool start_mouse_controller_ = false; | ||||
|   bool mouse_controller_is_started_ = false; | ||||
|   bool start_screen_capturer_ = false; | ||||
|   bool screen_capturer_is_started_ = false; | ||||
|   bool start_keyboard_capturer_ = false; | ||||
|   bool keyboard_capturer_is_started_ = false; | ||||
|   bool foucs_on_main_window_ = false; | ||||
|   bool foucs_on_stream_window_ = false; | ||||
|   bool audio_capture_ = false; | ||||
|   int main_window_width_real_ = 720; | ||||
|   int main_window_height_real_ = 540; | ||||
|   float main_window_dpi_scaling_w_ = 1.0f; | ||||
|   float main_window_dpi_scaling_h_ = 1.0f; | ||||
|   float main_window_width_default_ = 640; | ||||
|   float main_window_height_default_ = 480; | ||||
|   float main_window_width_ = 640; | ||||
|   float main_window_height_ = 480; | ||||
|   float main_window_width_last_ = 640; | ||||
|   float main_window_height_last_ = 480; | ||||
|   float local_window_width_ = 320; | ||||
|   float local_window_height_ = 235; | ||||
|   float remote_window_width_ = 320; | ||||
|   float remote_window_height_ = 235; | ||||
|   float local_child_window_width_ = 266; | ||||
|   float local_child_window_height_ = 180; | ||||
|   float remote_child_window_width_ = 266; | ||||
|   float remote_child_window_height_ = 180; | ||||
|   float main_window_text_y_padding_ = 10; | ||||
|   float main_child_window_x_padding_ = 27; | ||||
|   float main_child_window_y_padding_ = 45; | ||||
|   float status_bar_height_ = 22; | ||||
|   float connection_status_window_width_ = 200; | ||||
|   float connection_status_window_height_ = 150; | ||||
|   float notification_window_width_ = 200; | ||||
|   float notification_window_height_ = 80; | ||||
|   float about_window_width_ = 300; | ||||
|   float about_window_height_ = 170; | ||||
|   int screen_width_ = 1280; | ||||
|   int screen_height_ = 720; | ||||
|   int selected_display_ = 0; | ||||
|   std::string connect_button_label_ = "Connect"; | ||||
|   char input_password_tmp_[7] = ""; | ||||
|   char input_password_[7] = ""; | ||||
|   std::string random_password_ = ""; | ||||
|   char new_password_[7] = ""; | ||||
|   char remote_id_display_[12] = ""; | ||||
|   unsigned char audio_buffer_[720]; | ||||
|   int audio_len_ = 0; | ||||
|   bool audio_buffer_fresh_ = false; | ||||
|   bool need_to_rejoin_ = false; | ||||
|   bool just_created_ = false; | ||||
|   std::string controlled_remote_id_ = ""; | ||||
|   std::string focused_remote_id_ = ""; | ||||
|   bool need_to_send_host_info_ = false; | ||||
|   SDL_Event last_mouse_event; | ||||
|   SDL_AudioStream* output_stream_; | ||||
|   uint32_t STREAM_REFRESH_EVENT = 0; | ||||
|  | ||||
|   // stream window render | ||||
|   SDL_Window* stream_window_ = nullptr; | ||||
|   SDL_Renderer* stream_renderer_ = nullptr; | ||||
|   ImGuiContext* stream_ctx_ = nullptr; | ||||
|  | ||||
|   // stream window properties | ||||
|   bool need_to_create_stream_window_ = false; | ||||
|   bool stream_window_created_ = false; | ||||
|   bool stream_window_inited_ = false; | ||||
|   bool window_maximized_ = false; | ||||
|   bool stream_window_grabbed_ = false; | ||||
|   bool control_mouse_ = false; | ||||
|   int stream_window_width_default_ = 1280; | ||||
|   int stream_window_height_default_ = 720; | ||||
|   float stream_window_width_ = 1280; | ||||
|   float stream_window_height_ = 720; | ||||
|   SDL_PixelFormat stream_pixformat_ = SDL_PIXELFORMAT_NV12; | ||||
|   int stream_window_width_real_ = 1280; | ||||
|   int stream_window_height_real_ = 720; | ||||
|   float stream_window_dpi_scaling_w_ = 1.0f; | ||||
|   float stream_window_dpi_scaling_h_ = 1.0f; | ||||
|  | ||||
|   bool label_inited_ = false; | ||||
|   bool connect_button_pressed_ = false; | ||||
|   bool password_validating_ = false; | ||||
|   uint32_t password_validating_time_ = 0; | ||||
|   bool show_settings_window_ = false; | ||||
|   bool show_self_hosted_server_config_window_ = false; | ||||
|   bool rejoin_ = false; | ||||
|   bool local_id_copied_ = false; | ||||
|   bool show_password_ = true; | ||||
|   bool show_about_window_ = false; | ||||
|   bool show_connection_status_window_ = false; | ||||
|   bool show_reset_password_window_ = false; | ||||
|   bool fullscreen_button_pressed_ = false; | ||||
|   bool focus_on_input_widget_ = true; | ||||
|   bool is_client_mode_ = false; | ||||
|   bool reload_recent_connections_ = true; | ||||
|   bool show_confirm_delete_connection_ = false; | ||||
|   bool delete_connection_ = false; | ||||
|   bool is_tab_bar_hovered_ = false; | ||||
|   std::string delete_connection_name_ = ""; | ||||
|   bool re_enter_remote_id_ = false; | ||||
|   double copy_start_time_ = 0; | ||||
|   SignalStatus signal_status_ = SignalStatus::SignalClosed; | ||||
|   std::string signal_status_str_ = ""; | ||||
|   bool signal_connected_ = false; | ||||
|   PeerPtr* peer_ = nullptr; | ||||
|   PeerPtr* peer_reserved_ = nullptr; | ||||
|   std::string video_primary_label_ = "primary_display"; | ||||
|   std::string video_secondary_label_ = "secondary_display"; | ||||
|   std::string audio_label_ = "audio"; | ||||
|   std::string data_label_ = "data"; | ||||
|   Params params_; | ||||
|   SDL_AudioDeviceID input_dev_; | ||||
|   SDL_AudioDeviceID output_dev_; | ||||
|   ScreenCapturerFactory* screen_capturer_factory_ = nullptr; | ||||
|   ScreenCapturer* screen_capturer_ = nullptr; | ||||
|   SpeakerCapturerFactory* speaker_capturer_factory_ = nullptr; | ||||
|   SpeakerCapturer* speaker_capturer_ = nullptr; | ||||
|   DeviceControllerFactory* device_controller_factory_ = nullptr; | ||||
|   MouseController* mouse_controller_ = nullptr; | ||||
|   KeyboardCapturer* keyboard_capturer_ = nullptr; | ||||
|   std::vector<DisplayInfo> display_info_list_; | ||||
|   uint64_t last_frame_time_; | ||||
|   char client_id_[10] = ""; | ||||
|   char client_id_display_[12] = ""; | ||||
|   char client_id_with_password_[17] = ""; | ||||
|   char password_saved_[7] = ""; | ||||
|   int language_button_value_ = 0; | ||||
|   int video_quality_button_value_ = 0; | ||||
|   int video_frame_rate_button_value_ = 0; | ||||
|   int video_encode_format_button_value_ = 0; | ||||
|   bool enable_hardware_video_codec_ = false; | ||||
|   bool enable_turn_ = false; | ||||
|   bool enable_srtp_ = false; | ||||
|   char signal_server_ip_[256] = "api.crossdesk.cn"; | ||||
|   char signal_server_port_[6] = "9099"; | ||||
|   char cert_file_path_[256] = ""; | ||||
|   bool enable_self_hosted_server_ = false; | ||||
|   int language_button_value_last_ = 0; | ||||
|   int video_quality_button_value_last_ = 0; | ||||
|   int video_encode_format_button_value_last_ = 0; | ||||
|   bool enable_hardware_video_codec_last_ = false; | ||||
|   bool enable_turn_last_ = false; | ||||
|   bool enable_srtp_last_ = false; | ||||
|   bool enable_minimize_to_tray_ = false; | ||||
|   bool enable_minimize_to_tray_last_ = false; | ||||
|   char signal_server_ip_tmp_[256] = "api.crossdesk.cn"; | ||||
|   char signal_server_port_tmp_[6] = "9099"; | ||||
|   bool settings_window_pos_reset_ = true; | ||||
|   bool self_hosted_server_config_window_pos_reset_ = true; | ||||
|   std::string selected_current_file_path_ = ""; | ||||
|   std::string selected_file_ = ""; | ||||
|   /* ------ main window property end ------ */ | ||||
|  | ||||
|   /* ------ sub stream window property start ------ */ | ||||
|   std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>> | ||||
|       client_properties_; | ||||
|   void CloseTab(decltype(client_properties_)::iterator& it); | ||||
|   /* ------ stream window property end ------ */ | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										545
									
								
								src/gui/render_callback.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,545 @@ | ||||
| #include "device_controller.h" | ||||
| #include "localization.h" | ||||
| #include "platform.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 | ||||
|  | ||||
| #ifdef DESK_PORT_DEBUG | ||||
| #else | ||||
| #define MOUSE_CONTROL 1 | ||||
| #endif | ||||
|  | ||||
| int Render::SendKeyCommand(int key_code, bool is_down) { | ||||
|   RemoteAction remote_action; | ||||
|   remote_action.type = ControlType::keyboard; | ||||
|   if (is_down) { | ||||
|     remote_action.k.flag = KeyFlag::key_down; | ||||
|   } else { | ||||
|     remote_action.k.flag = KeyFlag::key_up; | ||||
|   } | ||||
|   remote_action.k.key_value = key_code; | ||||
|  | ||||
|   if (!controlled_remote_id_.empty()) { | ||||
|     if (client_properties_.find(controlled_remote_id_) != | ||||
|         client_properties_.end()) { | ||||
|       auto props = client_properties_[controlled_remote_id_]; | ||||
|       if (props->connection_status_ == ConnectionStatus::Connected) { | ||||
|         SendDataFrame(props->peer_, (const char*)&remote_action, | ||||
|                       sizeof(remote_action), props->data_label_.c_str()); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::ProcessMouseEvent(const SDL_Event& event) { | ||||
|   controlled_remote_id_ = ""; | ||||
|   int video_width, video_height = 0; | ||||
|   int render_width, render_height = 0; | ||||
|   float ratio_x, ratio_y = 0; | ||||
|   RemoteAction remote_action; | ||||
|  | ||||
|   for (auto& it : client_properties_) { | ||||
|     auto props = it.second; | ||||
|     if (!props->control_mouse_) { | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     if (event.button.x >= props->stream_render_rect_.x && | ||||
|         event.button.x <= | ||||
|             props->stream_render_rect_.x + props->stream_render_rect_.w && | ||||
|         event.button.y >= props->stream_render_rect_.y && | ||||
|         event.button.y <= | ||||
|             props->stream_render_rect_.y + props->stream_render_rect_.h) { | ||||
|       controlled_remote_id_ = it.first; | ||||
|       render_width = props->stream_render_rect_.w; | ||||
|       render_height = props->stream_render_rect_.h; | ||||
|       last_mouse_event.button.x = event.button.x; | ||||
|       last_mouse_event.button.y = event.button.y; | ||||
|  | ||||
|       remote_action.m.x = | ||||
|           (float)(event.button.x - props->stream_render_rect_.x) / render_width; | ||||
|       remote_action.m.y = | ||||
|           (float)(event.button.y - props->stream_render_rect_.y) / | ||||
|           render_height; | ||||
|  | ||||
|       if (SDL_EVENT_MOUSE_BUTTON_DOWN == event.type) { | ||||
|         remote_action.type = ControlType::mouse; | ||||
|         if (SDL_BUTTON_LEFT == event.button.button) { | ||||
|           remote_action.m.flag = MouseFlag::left_down; | ||||
|         } else if (SDL_BUTTON_RIGHT == event.button.button) { | ||||
|           remote_action.m.flag = MouseFlag::right_down; | ||||
|         } else if (SDL_BUTTON_MIDDLE == event.button.button) { | ||||
|           remote_action.m.flag = MouseFlag::middle_down; | ||||
|         } | ||||
|       } else if (SDL_EVENT_MOUSE_BUTTON_UP == event.type) { | ||||
|         remote_action.type = ControlType::mouse; | ||||
|         if (SDL_BUTTON_LEFT == event.button.button) { | ||||
|           remote_action.m.flag = MouseFlag::left_up; | ||||
|         } else if (SDL_BUTTON_RIGHT == event.button.button) { | ||||
|           remote_action.m.flag = MouseFlag::right_up; | ||||
|         } else if (SDL_BUTTON_MIDDLE == event.button.button) { | ||||
|           remote_action.m.flag = MouseFlag::middle_up; | ||||
|         } | ||||
|       } else if (SDL_EVENT_MOUSE_MOTION == event.type) { | ||||
|         remote_action.type = ControlType::mouse; | ||||
|         remote_action.m.flag = MouseFlag::move; | ||||
|       } | ||||
|  | ||||
|       if (props->control_bar_hovered_ || props->display_selectable_hovered_) { | ||||
|         remote_action.m.flag = MouseFlag::move; | ||||
|       } | ||||
|       SendDataFrame(props->peer_, (const char*)&remote_action, | ||||
|                     sizeof(remote_action), props->data_label_.c_str()); | ||||
|     } else if (SDL_EVENT_MOUSE_WHEEL == event.type && | ||||
|                last_mouse_event.button.x >= props->stream_render_rect_.x && | ||||
|                last_mouse_event.button.x <= props->stream_render_rect_.x + | ||||
|                                                 props->stream_render_rect_.w && | ||||
|                last_mouse_event.button.y >= props->stream_render_rect_.y && | ||||
|                last_mouse_event.button.y <= props->stream_render_rect_.y + | ||||
|                                                 props->stream_render_rect_.h) { | ||||
|       int scroll_x = event.wheel.x; | ||||
|       int scroll_y = event.wheel.y; | ||||
|       if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { | ||||
|         scroll_x = -scroll_x; | ||||
|         scroll_y = -scroll_y; | ||||
|       } | ||||
|  | ||||
|       remote_action.type = ControlType::mouse; | ||||
|       if (scroll_x == 0) { | ||||
|         remote_action.m.flag = MouseFlag::wheel_vertical; | ||||
|         remote_action.m.s = scroll_y; | ||||
|       } else if (scroll_y == 0) { | ||||
|         remote_action.m.flag = MouseFlag::wheel_horizontal; | ||||
|         remote_action.m.s = scroll_x; | ||||
|       } | ||||
|  | ||||
|       render_width = props->stream_render_rect_.w; | ||||
|       render_height = props->stream_render_rect_.h; | ||||
|       remote_action.m.x = | ||||
|           (float)(event.button.x - props->stream_render_rect_.x) / render_width; | ||||
|       remote_action.m.y = | ||||
|           (float)(event.button.y - props->stream_render_rect_.y) / | ||||
|           render_height; | ||||
|  | ||||
|       SendDataFrame(props->peer_, (const char*)&remote_action, | ||||
|                     sizeof(remote_action), props->data_label_.c_str()); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| void Render::SdlCaptureAudioIn(void* userdata, Uint8* stream, int len) { | ||||
|   Render* render = (Render*)userdata; | ||||
|   if (!render) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (1) { | ||||
|     for (auto it : render->client_properties_) { | ||||
|       auto props = it.second; | ||||
|       if (props->connection_status_ == ConnectionStatus::Connected) { | ||||
|         SendAudioFrame(props->peer_, (const char*)stream, len, | ||||
|                        render->audio_label_.c_str()); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   } else { | ||||
|     memcpy(render->audio_buffer_, stream, len); | ||||
|     render->audio_len_ = len; | ||||
|     SDL_Delay(10); | ||||
|     render->audio_buffer_fresh_ = true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::SdlCaptureAudioOut([[maybe_unused]] void* userdata, | ||||
|                                 [[maybe_unused]] Uint8* stream, | ||||
|                                 [[maybe_unused]] int len) { | ||||
|   // Render *render = (Render *)userdata; | ||||
|   // for (auto it : render->client_properties_) { | ||||
|   //   auto props = it.second; | ||||
|   //   if (props->connection_status_ == SignalStatus::SignalConnected) { | ||||
|   //     SendAudioFrame(props->peer_, (const char *)stream, len); | ||||
|   //   } | ||||
|   // } | ||||
|  | ||||
|   // if (!render->audio_buffer_fresh_) { | ||||
|   //   return; | ||||
|   // } | ||||
|  | ||||
|   // SDL_memset(stream, 0, len); | ||||
|  | ||||
|   // if (render->audio_len_ == 0) { | ||||
|   //   return; | ||||
|   // } else { | ||||
|   // } | ||||
|  | ||||
|   // len = (len > render->audio_len_ ? render->audio_len_ : len); | ||||
|   // SDL_MixAudioFormat(stream, render->audio_buffer_, AUDIO_S16LSB, len, | ||||
|   //                    SDL_MIX_MAXVOLUME); | ||||
|   // render->audio_buffer_fresh_ = false; | ||||
| } | ||||
|  | ||||
| void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame, | ||||
|                                     const char* user_id, size_t user_id_size, | ||||
|                                     void* user_data) { | ||||
|   Render* render = (Render*)user_data; | ||||
|   if (!render) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   std::string remote_id(user_id, user_id_size); | ||||
|   if (render->client_properties_.find(remote_id) == | ||||
|       render->client_properties_.end()) { | ||||
|     return; | ||||
|   } | ||||
|   SubStreamWindowProperties* props = | ||||
|       render->client_properties_.find(remote_id)->second.get(); | ||||
|  | ||||
|   if (props->connection_established_) { | ||||
|     if (!props->dst_buffer_) { | ||||
|       props->dst_buffer_capacity_ = video_frame->size; | ||||
|       props->dst_buffer_ = new unsigned char[video_frame->size]; | ||||
|     } | ||||
|  | ||||
|     if (props->dst_buffer_capacity_ < video_frame->size) { | ||||
|       delete props->dst_buffer_; | ||||
|       props->dst_buffer_capacity_ = video_frame->size; | ||||
|       props->dst_buffer_ = new unsigned char[video_frame->size]; | ||||
|     } | ||||
|  | ||||
|     memcpy(props->dst_buffer_, video_frame->data, video_frame->size); | ||||
|     bool need_to_update_render_rect = false; | ||||
|     if (props->video_width_ != props->video_width_last_ || | ||||
|         props->video_height_ != props->video_height_last_) { | ||||
|       need_to_update_render_rect = true; | ||||
|       props->video_width_last_ = props->video_width_; | ||||
|       props->video_height_last_ = props->video_height_; | ||||
|     } | ||||
|     props->video_width_ = video_frame->width; | ||||
|     props->video_height_ = video_frame->height; | ||||
|     props->video_size_ = video_frame->size; | ||||
|  | ||||
|     if (need_to_update_render_rect) { | ||||
|       render->UpdateRenderRect(); | ||||
|     } | ||||
|  | ||||
|     SDL_Event event; | ||||
|     event.type = render->STREAM_REFRESH_EVENT; | ||||
|     event.user.data1 = props; | ||||
|     SDL_PushEvent(&event); | ||||
|     props->streaming_ = true; | ||||
|  | ||||
|     if (props->net_traffic_stats_button_pressed_) { | ||||
|       props->frame_count_++; | ||||
|       auto now = std::chrono::steady_clock::now(); | ||||
|       auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( | ||||
|                          now - props->last_time_) | ||||
|                          .count(); | ||||
|  | ||||
|       if (elapsed >= 1000) { | ||||
|         props->fps_ = props->frame_count_ * 1000 / elapsed; | ||||
|         props->frame_count_ = 0; | ||||
|         props->last_time_ = now; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::OnReceiveAudioBufferCb(const char* data, size_t size, | ||||
|                                     const char* user_id, size_t user_id_size, | ||||
|                                     void* user_data) { | ||||
|   Render* render = (Render*)user_data; | ||||
|   if (!render) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   render->audio_buffer_fresh_ = true; | ||||
|  | ||||
|   if (render->output_stream_) { | ||||
|     int pushed = SDL_PutAudioStreamData( | ||||
|         render->output_stream_, (const Uint8*)data, static_cast<int>(size)); | ||||
|     if (pushed < 0) { | ||||
|       LOG_ERROR("Failed to push audio data: {}", SDL_GetError()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::OnReceiveDataBufferCb(const char* data, size_t size, | ||||
|                                    const char* user_id, size_t user_id_size, | ||||
|                                    void* user_data) { | ||||
|   Render* render = (Render*)user_data; | ||||
|   if (!render) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   RemoteAction remote_action; | ||||
|   memcpy(&remote_action, data, size); | ||||
|  | ||||
|   std::string remote_id(user_id, user_id_size); | ||||
|   if (render->client_properties_.find(remote_id) != | ||||
|       render->client_properties_.end()) { | ||||
|     // local | ||||
|     auto props = render->client_properties_.find(remote_id)->second; | ||||
|     RemoteAction host_info; | ||||
|     if (DeserializeRemoteAction(data, size, host_info)) { | ||||
|       if (ControlType::host_infomation == host_info.type && | ||||
|           props->remote_host_name_.empty()) { | ||||
|         props->remote_host_name_ = | ||||
|             std::string(host_info.i.host_name, host_info.i.host_name_size); | ||||
|         LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); | ||||
|  | ||||
|         for (int i = 0; i < host_info.i.display_num; i++) { | ||||
|           props->display_info_list_.push_back(DisplayInfo( | ||||
|               std::string(host_info.i.display_list[i]), host_info.i.left[i], | ||||
|               host_info.i.top[i], host_info.i.right[i], host_info.i.bottom[i])); | ||||
|           LOG_INFO("Remote display [{}:{}], bound [({}, {}) ({}, {})]", i + 1, | ||||
|                    props->display_info_list_[i].name, | ||||
|                    props->display_info_list_[i].left, | ||||
|                    props->display_info_list_[i].top, | ||||
|                    props->display_info_list_[i].right, | ||||
|                    props->display_info_list_[i].bottom); | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       props->remote_host_name_ = std::string(remote_action.i.host_name, | ||||
|                                              remote_action.i.host_name_size); | ||||
|       LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); | ||||
|       LOG_ERROR("No remote display detected"); | ||||
|     } | ||||
|     FreeRemoteAction(host_info); | ||||
|   } else { | ||||
|     // remote | ||||
|     if (ControlType::mouse == remote_action.type && render->mouse_controller_) { | ||||
|       render->mouse_controller_->SendMouseCommand(remote_action, | ||||
|                                                   render->selected_display_); | ||||
|     } else if (ControlType::audio_capture == remote_action.type) { | ||||
|       if (remote_action.a) { | ||||
|         render->StartSpeakerCapturer(); | ||||
|         render->audio_capture_ = true; | ||||
|       } else { | ||||
|         render->StopSpeakerCapturer(); | ||||
|         render->audio_capture_ = false; | ||||
|       } | ||||
|     } else if (ControlType::keyboard == remote_action.type && | ||||
|                render->keyboard_capturer_) { | ||||
|       render->keyboard_capturer_->SendKeyboardCommand( | ||||
|           (int)remote_action.k.key_value, | ||||
|           remote_action.k.flag == KeyFlag::key_down); | ||||
|     } else if (ControlType::display_id == remote_action.type) { | ||||
|       if (render->screen_capturer_) { | ||||
|         render->selected_display_ = remote_action.d; | ||||
|         render->screen_capturer_->SwitchTo(remote_action.d); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::OnSignalStatusCb(SignalStatus status, const char* user_id, | ||||
|                               size_t user_id_size, void* user_data) { | ||||
|   Render* render = (Render*)user_data; | ||||
|   if (!render) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   std::string client_id(user_id, user_id_size); | ||||
|   if (client_id == render->client_id_) { | ||||
|     render->signal_status_ = status; | ||||
|     if (SignalStatus::SignalConnecting == status) { | ||||
|       render->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalConnected == status) { | ||||
|       render->signal_connected_ = true; | ||||
|       LOG_INFO("[{}] connected to signal server", client_id); | ||||
|     } else if (SignalStatus::SignalFailed == status) { | ||||
|       render->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalClosed == status) { | ||||
|       render->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalReconnecting == status) { | ||||
|       render->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalServerClosed == status) { | ||||
|       render->signal_connected_ = false; | ||||
|     } | ||||
|   } else { | ||||
|     if (client_id.rfind("C-", 0) != 0) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     std::string remote_id(client_id.begin() + 2, client_id.end()); | ||||
|     if (render->client_properties_.find(remote_id) == | ||||
|         render->client_properties_.end()) { | ||||
|       return; | ||||
|     } | ||||
|     auto props = render->client_properties_.find(remote_id)->second; | ||||
|     props->signal_status_ = status; | ||||
|     if (SignalStatus::SignalConnecting == status) { | ||||
|       props->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalConnected == status) { | ||||
|       props->signal_connected_ = true; | ||||
|       LOG_INFO("[{}] connected to signal server", remote_id); | ||||
|     } else if (SignalStatus::SignalFailed == status) { | ||||
|       props->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalClosed == status) { | ||||
|       props->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalReconnecting == status) { | ||||
|       props->signal_connected_ = false; | ||||
|     } else if (SignalStatus::SignalServerClosed == status) { | ||||
|       props->signal_connected_ = false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id, | ||||
|                                   const size_t user_id_size, void* user_data) { | ||||
|   Render* render = (Render*)user_data; | ||||
|   if (!render) return; | ||||
|  | ||||
|   std::string remote_id(user_id, user_id_size); | ||||
|   auto it = render->client_properties_.find(remote_id); | ||||
|   auto props = (it != render->client_properties_.end()) ? it->second : nullptr; | ||||
|  | ||||
|   if (props) { | ||||
|     render->is_client_mode_ = true; | ||||
|     render->show_connection_status_window_ = true; | ||||
|     props->connection_status_ = status; | ||||
|  | ||||
|     switch (status) { | ||||
|       case ConnectionStatus::Connected: | ||||
|         if (!render->need_to_create_stream_window_ && | ||||
|             !render->client_properties_.empty()) { | ||||
|           render->need_to_create_stream_window_ = true; | ||||
|         } | ||||
|         props->connection_established_ = true; | ||||
|         props->stream_render_rect_ = { | ||||
|             0, (int)render->title_bar_height_, | ||||
|             (int)render->stream_window_width_, | ||||
|             (int)(render->stream_window_height_ - render->title_bar_height_)}; | ||||
|         break; | ||||
|       case ConnectionStatus::Disconnected: | ||||
|       case ConnectionStatus::Failed: | ||||
|       case ConnectionStatus::Closed: | ||||
|         render->password_validating_time_ = 0; | ||||
|         render->start_screen_capturer_ = false; | ||||
|         render->start_mouse_controller_ = false; | ||||
|         render->start_keyboard_capturer_ = false; | ||||
|         render->control_mouse_ = false; | ||||
|         props->connection_established_ = false; | ||||
|         props->mouse_control_button_pressed_ = false; | ||||
|         if (props->dst_buffer_) { | ||||
|           memset(props->dst_buffer_, 0, props->dst_buffer_capacity_); | ||||
|           SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_, | ||||
|                             props->texture_width_); | ||||
|         } | ||||
|         render->CleanSubStreamWindowProperties(props); | ||||
|         break; | ||||
|       case ConnectionStatus::IncorrectPassword: | ||||
|         render->password_validating_ = false; | ||||
|         render->password_validating_time_++; | ||||
|         if (render->connect_button_pressed_) { | ||||
|           render->connect_button_pressed_ = false; | ||||
|           props->connection_established_ = false; | ||||
|           render->connect_button_label_ = | ||||
|               localization::connect[render->localization_language_index_]; | ||||
|         } | ||||
|         break; | ||||
|       case ConnectionStatus::NoSuchTransmissionId: | ||||
|         if (render->connect_button_pressed_) { | ||||
|           props->connection_established_ = false; | ||||
|           render->connect_button_label_ = | ||||
|               localization::connect[render->localization_language_index_]; | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } else { | ||||
|     render->is_client_mode_ = false; | ||||
|     render->show_connection_status_window_ = true; | ||||
|  | ||||
|     switch (status) { | ||||
|       case ConnectionStatus::Connected: | ||||
|         render->need_to_send_host_info_ = true; | ||||
|         render->start_screen_capturer_ = true; | ||||
|         render->start_mouse_controller_ = true; | ||||
|         break; | ||||
|       case ConnectionStatus::Closed: | ||||
|         render->start_screen_capturer_ = false; | ||||
|         render->start_mouse_controller_ = false; | ||||
|         render->start_keyboard_capturer_ = false; | ||||
|         render->need_to_send_host_info_ = false; | ||||
|         if (props) props->connection_established_ = false; | ||||
|         if (render->audio_capture_) { | ||||
|           render->StopSpeakerCapturer(); | ||||
|           render->audio_capture_ = false; | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::NetStatusReport(const char* client_id, size_t client_id_size, | ||||
|                              TraversalMode mode, | ||||
|                              const XNetTrafficStats* net_traffic_stats, | ||||
|                              const char* user_id, const size_t user_id_size, | ||||
|                              void* user_data) { | ||||
|   Render* render = (Render*)user_data; | ||||
|   if (!render) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) { | ||||
|     std::string id, password; | ||||
|     const char* at_pos = strchr(client_id, '@'); | ||||
|     if (at_pos == nullptr) { | ||||
|       id = client_id; | ||||
|       password.clear(); | ||||
|     } else { | ||||
|       id.assign(client_id, at_pos - client_id); | ||||
|       password = at_pos + 1; | ||||
|     } | ||||
|  | ||||
|     memset(&render->client_id_, 0, sizeof(render->client_id_)); | ||||
|     strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1); | ||||
|     render->client_id_[sizeof(render->client_id_) - 1] = '\0'; | ||||
|  | ||||
|     memset(&render->password_saved_, 0, sizeof(render->password_saved_)); | ||||
|     strncpy(render->password_saved_, password.c_str(), | ||||
|             sizeof(render->password_saved_) - 1); | ||||
|     render->password_saved_[sizeof(render->password_saved_) - 1] = '\0'; | ||||
|  | ||||
|     memset(&render->client_id_with_password_, 0, | ||||
|            sizeof(render->client_id_with_password_)); | ||||
|     strncpy(render->client_id_with_password_, client_id, | ||||
|             sizeof(render->client_id_with_password_) - 1); | ||||
|     render->client_id_with_password_[sizeof(render->client_id_with_password_) - | ||||
|                                      1] = '\0'; | ||||
|  | ||||
|     LOG_INFO("Use client id [{}] and save id into cache file", id); | ||||
|     render->SaveSettingsIntoCacheFile(); | ||||
|   } | ||||
|  | ||||
|   std::string remote_id(user_id, user_id_size); | ||||
|   if (render->client_properties_.find(remote_id) == | ||||
|       render->client_properties_.end()) { | ||||
|     return; | ||||
|   } | ||||
|   auto props = render->client_properties_.find(remote_id)->second; | ||||
|   if (props->traversal_mode_ != mode) { | ||||
|     props->traversal_mode_ = mode; | ||||
|     LOG_INFO("Net mode: [{}]", int(props->traversal_mode_)); | ||||
|   } | ||||
|  | ||||
|   if (!net_traffic_stats) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // only display client side net status if connected to itself | ||||
|   if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) { | ||||
|     props->net_traffic_stats_ = *net_traffic_stats; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										326
									
								
								src/gui/toolbars/control_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,326 @@ | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int CountDigits(int number) { | ||||
|   if (number == 0) return 1; | ||||
|   return (int)std::floor(std::log10(std::abs(number))) + 1; | ||||
| } | ||||
|  | ||||
| int BitrateDisplay(int bitrate) { | ||||
|   int num_of_digits = CountDigits(bitrate); | ||||
|   if (num_of_digits <= 3) { | ||||
|     ImGui::Text("%d bps", bitrate); | ||||
|   } else if (num_of_digits > 3 && num_of_digits <= 6) { | ||||
|     ImGui::Text("%d kbps", bitrate / 1000); | ||||
|   } else { | ||||
|     ImGui::Text("%.1f mbps", bitrate / 1000000.0f); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int LossRateDisplay(float loss_rate) { | ||||
|   if (loss_rate < 0.01f) { | ||||
|     ImGui::Text("0%%"); | ||||
|   } else { | ||||
|     ImGui::Text("%.0f%%", loss_rate * 100); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) { | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|   if (props->control_bar_expand_) { | ||||
|     ImGui::SetCursorPosX(props->is_control_bar_in_left_ | ||||
|                              ? (props->control_window_width_ + 5.0f) | ||||
|                              : 38.0f); | ||||
|     // mouse control button | ||||
|     ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||
|  | ||||
|     if (props->is_control_bar_in_left_) { | ||||
|       draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 5.0f, | ||||
|                                 ImGui::GetCursorScreenPos().y - 7.0f), | ||||
|                          ImVec2(ImGui::GetCursorScreenPos().x - 5.0f, | ||||
|                                 ImGui::GetCursorScreenPos().y - 7.0f + | ||||
|                                     props->control_window_height_), | ||||
|                          IM_COL32(178, 178, 178, 255), 1.0f); | ||||
|     } | ||||
|  | ||||
|     std::string display = ICON_FA_DISPLAY; | ||||
|     if (ImGui::Button(display.c_str(), ImVec2(25, 25))) { | ||||
|       ImGui::OpenPopup("display"); | ||||
|     } | ||||
|  | ||||
|     ImVec2 btn_min = ImGui::GetItemRectMin(); | ||||
|     ImVec2 btn_size_actual = ImGui::GetItemRectSize(); | ||||
|  | ||||
|     if (ImGui::BeginPopup("display")) { | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       for (int i = 0; i < props->display_info_list_.size(); i++) { | ||||
|         if (ImGui::Selectable(props->display_info_list_[i].name.c_str())) { | ||||
|           props->selected_display_ = i; | ||||
|  | ||||
|           RemoteAction remote_action; | ||||
|           remote_action.type = ControlType::display_id; | ||||
|           remote_action.d = i; | ||||
|           if (props->connection_status_ == ConnectionStatus::Connected) { | ||||
|             SendDataFrame(props->peer_, (const char*)&remote_action, | ||||
|                           sizeof(remote_action), props->data_label_.c_str()); | ||||
|           } | ||||
|         } | ||||
|         props->display_selectable_hovered_ = ImGui::IsWindowHovered(); | ||||
|       } | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::EndPopup(); | ||||
|     } | ||||
|  | ||||
|     ImGui::SetWindowFontScale(0.6f); | ||||
|     ImVec2 text_size = ImGui::CalcTextSize( | ||||
|         std::to_string(props->selected_display_ + 1).c_str()); | ||||
|     ImVec2 text_pos = | ||||
|         ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.5f, | ||||
|                btn_min.y + (btn_size_actual.y - text_size.y) * 0.5f - 2.0f); | ||||
|     ImGui::GetWindowDrawList()->AddText( | ||||
|         text_pos, IM_COL32(0, 0, 0, 255), | ||||
|         std::to_string(props->selected_display_ + 1).c_str()); | ||||
|     ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|     float disable_mouse_x = ImGui::GetCursorScreenPos().x + 4.0f; | ||||
|     float disable_mouse_y = ImGui::GetCursorScreenPos().y + 4.0f; | ||||
|     std::string mouse = props->mouse_control_button_pressed_ | ||||
|                             ? ICON_FA_COMPUTER_MOUSE | ||||
|                             : ICON_FA_COMPUTER_MOUSE; | ||||
|     if (ImGui::Button(mouse.c_str(), ImVec2(25, 25))) { | ||||
|       if (props->connection_established_) { | ||||
|         start_keyboard_capturer_ = !start_keyboard_capturer_; | ||||
|         props->control_mouse_ = !props->control_mouse_; | ||||
|         props->mouse_control_button_pressed_ = | ||||
|             !props->mouse_control_button_pressed_; | ||||
|         props->mouse_control_button_label_ = | ||||
|             props->mouse_control_button_pressed_ | ||||
|                 ? localization::release_mouse[localization_language_index_] | ||||
|                 : localization::control_mouse[localization_language_index_]; | ||||
|       } | ||||
|     } | ||||
|     if (!props->mouse_control_button_pressed_) { | ||||
|       draw_list->AddLine( | ||||
|           ImVec2(disable_mouse_x, disable_mouse_y), | ||||
|           ImVec2(disable_mouse_x + 16.0f, disable_mouse_y + 14.2f), | ||||
|           IM_COL32(0, 0, 0, 255), 2.0f); | ||||
|       draw_list->AddLine( | ||||
|           ImVec2(disable_mouse_x - 1.2f, disable_mouse_y + 1.2f), | ||||
|           ImVec2(disable_mouse_x + 15.3f, disable_mouse_y + 15.4f), | ||||
|           ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255) | ||||
|                                  : IM_COL32(179, 213, 253, 255), | ||||
|           2.0f); | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|     // audio capture button | ||||
|     float disable_audio_x = ImGui::GetCursorScreenPos().x + 4; | ||||
|     float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f; | ||||
|     // std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH | ||||
|     //                                                   : | ||||
|     //                                                   ICON_FA_VOLUME_XMARK; | ||||
|     std::string audio = props->audio_capture_button_pressed_ | ||||
|                             ? ICON_FA_VOLUME_HIGH | ||||
|                             : ICON_FA_VOLUME_HIGH; | ||||
|     if (ImGui::Button(audio.c_str(), ImVec2(25, 25))) { | ||||
|       if (props->connection_established_) { | ||||
|         props->audio_capture_button_pressed_ = | ||||
|             !props->audio_capture_button_pressed_; | ||||
|         props->audio_capture_button_label_ = | ||||
|             props->audio_capture_button_pressed_ | ||||
|                 ? localization::audio_capture[localization_language_index_] | ||||
|                 : localization::mute[localization_language_index_]; | ||||
|  | ||||
|         RemoteAction remote_action; | ||||
|         remote_action.type = ControlType::audio_capture; | ||||
|         remote_action.a = props->audio_capture_button_pressed_; | ||||
|         SendDataFrame(props->peer_, (const char*)&remote_action, | ||||
|                       sizeof(remote_action), props->data_label_.c_str()); | ||||
|       } | ||||
|     } | ||||
|     if (!props->audio_capture_button_pressed_) { | ||||
|       draw_list->AddLine( | ||||
|           ImVec2(disable_audio_x, disable_audio_y), | ||||
|           ImVec2(disable_audio_x + 16.0f, disable_audio_y + 14.2f), | ||||
|           IM_COL32(0, 0, 0, 255), 2.0f); | ||||
|       draw_list->AddLine( | ||||
|           ImVec2(disable_audio_x - 1.2f, disable_audio_y + 1.2f), | ||||
|           ImVec2(disable_audio_x + 15.3f, disable_audio_y + 15.4f), | ||||
|           ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255) | ||||
|                                  : IM_COL32(179, 213, 253, 255), | ||||
|           2.0f); | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|     // net traffic stats button | ||||
|     bool button_color_style_pushed = false; | ||||
|     if (props->net_traffic_stats_button_pressed_) { | ||||
|       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(66 / 255.0f, 150 / 255.0f, | ||||
|                                                     250 / 255.0f, 1.0f)); | ||||
|       button_color_style_pushed = true; | ||||
|     } | ||||
|     std::string net_traffic_stats = ICON_FA_SIGNAL; | ||||
|     if (ImGui::Button(net_traffic_stats.c_str(), ImVec2(25, 25))) { | ||||
|       props->net_traffic_stats_button_pressed_ = | ||||
|           !props->net_traffic_stats_button_pressed_; | ||||
|       props->control_window_height_is_changing_ = true; | ||||
|       props->net_traffic_stats_button_pressed_time_ = ImGui::GetTime(); | ||||
|       props->net_traffic_stats_button_label_ = | ||||
|           props->net_traffic_stats_button_pressed_ | ||||
|               ? localization::hide_net_traffic_stats | ||||
|                     [localization_language_index_] | ||||
|               : localization::show_net_traffic_stats | ||||
|                     [localization_language_index_]; | ||||
|     } | ||||
|     if (button_color_style_pushed) { | ||||
|       ImGui::PopStyleColor(); | ||||
|       button_color_style_pushed = false; | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|     // fullscreen button | ||||
|     std::string fullscreen = | ||||
|         fullscreen_button_pressed_ ? ICON_FA_COMPRESS : ICON_FA_EXPAND; | ||||
|     if (ImGui::Button(fullscreen.c_str(), ImVec2(25, 25))) { | ||||
|       fullscreen_button_pressed_ = !fullscreen_button_pressed_; | ||||
|       props->fullscreen_button_label_ = | ||||
|           fullscreen_button_pressed_ | ||||
|               ? localization::exit_fullscreen[localization_language_index_] | ||||
|               : localization::fullscreen[localization_language_index_]; | ||||
|  | ||||
|       if (fullscreen_button_pressed_) { | ||||
|         SDL_SetWindowFullscreen(stream_window_, true); | ||||
|       } else { | ||||
|         SDL_SetWindowFullscreen(stream_window_, false); | ||||
|       } | ||||
|       props->reset_control_bar_pos_ = true; | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|     // close button | ||||
|     std::string close_button = ICON_FA_XMARK; | ||||
|     if (ImGui::Button(close_button.c_str(), ImVec2(25, 25))) { | ||||
|       CleanupPeer(props); | ||||
|     } | ||||
|  | ||||
|     ImGui::SameLine(); | ||||
|  | ||||
|     if (!props->is_control_bar_in_left_) { | ||||
|       draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 3.0f, | ||||
|                                 ImGui::GetCursorScreenPos().y - 7.0f), | ||||
|                          ImVec2(ImGui::GetCursorScreenPos().x - 3.0f, | ||||
|                                 ImGui::GetCursorScreenPos().y - 7.0f + | ||||
|                                     props->control_window_height_), | ||||
|                          IM_COL32(178, 178, 178, 255), 1.0f); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ImGui::SetCursorPosX(props->is_control_bar_in_left_ | ||||
|                            ? (props->control_window_width_ * 2 - 20.0f) | ||||
|                            : 5.0f); | ||||
|  | ||||
|   std::string control_bar = | ||||
|       props->control_bar_expand_ | ||||
|           ? (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_LEFT | ||||
|                                             : ICON_FA_ANGLE_RIGHT) | ||||
|           : (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT | ||||
|                                             : ICON_FA_ANGLE_LEFT); | ||||
|   if (ImGui::Button(control_bar.c_str(), ImVec2(15, 25))) { | ||||
|     props->control_bar_expand_ = !props->control_bar_expand_; | ||||
|     props->control_bar_button_pressed_time_ = ImGui::GetTime(); | ||||
|     props->control_window_width_is_changing_ = true; | ||||
|  | ||||
|     if (!props->control_bar_expand_) { | ||||
|       props->control_window_height_ = 40; | ||||
|       props->net_traffic_stats_button_pressed_ = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) { | ||||
|     NetTrafficStats(props); | ||||
|   } | ||||
|  | ||||
|   ImGui::PopStyleVar(); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) { | ||||
|   ImGui::SetCursorPos(ImVec2(props->is_control_bar_in_left_ | ||||
|                                  ? (props->control_window_width_ + 5.0f) | ||||
|                                  : 5.0f, | ||||
|                              40.0f)); | ||||
|  | ||||
|   if (ImGui::BeginTable("NetTrafficStats", 4, ImGuiTableFlags_BordersH, | ||||
|                         ImVec2(props->control_window_max_width_ - 10.0f, | ||||
|                                props->control_window_max_height_ - 60.0f))) { | ||||
|     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); | ||||
|     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); | ||||
|     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); | ||||
|     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); | ||||
|  | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text(" "); | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", localization::in[localization_language_index_].c_str()); | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", localization::out[localization_language_index_].c_str()); | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", | ||||
|                 localization::loss_rate[localization_language_index_].c_str()); | ||||
|  | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", | ||||
|                 localization::video[localization_language_index_].c_str()); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.video_inbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.video_outbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     LossRateDisplay(props->net_traffic_stats_.video_inbound_stats.loss_rate); | ||||
|  | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", | ||||
|                 localization::audio[localization_language_index_].c_str()); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.audio_inbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.audio_outbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     LossRateDisplay(props->net_traffic_stats_.audio_inbound_stats.loss_rate); | ||||
|  | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", localization::data[localization_language_index_].c_str()); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.data_inbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.data_outbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     LossRateDisplay(props->net_traffic_stats_.data_inbound_stats.loss_rate); | ||||
|  | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%s", | ||||
|                 localization::total[localization_language_index_].c_str()); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.total_inbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     BitrateDisplay((int)props->net_traffic_stats_.total_outbound_stats.bitrate); | ||||
|     ImGui::TableNextColumn(); | ||||
|     LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate); | ||||
|  | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("FPS"); | ||||
|     ImGui::TableNextColumn(); | ||||
|     ImGui::Text("%d", props->fps_); | ||||
|  | ||||
|     ImGui::EndTable(); | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/gui/toolbars/status_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| #include "localization.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::StatusBar() { | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   static bool a, b, c, d, e; | ||||
|   ImGui::SetNextWindowPos( | ||||
|       ImVec2(0, main_window_height_default_ - status_bar_height_ - 1), | ||||
|       ImGuiCond_Always); | ||||
|  | ||||
|   ImGui::BeginChild( | ||||
|       "StatusBar", ImVec2(main_window_width_, status_bar_height_ + 1), | ||||
|       ImGuiChildFlags_Border, | ||||
|       ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|  | ||||
|   ImVec2 dot_pos = | ||||
|       ImVec2(13, main_window_height_default_ - status_bar_height_ + 11.0f); | ||||
|   ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||
|   draw_list->AddCircleFilled(dot_pos, 5.0f, | ||||
|                              ImColor(signal_connected_ ? 0.0f : 1.0f, | ||||
|                                      signal_connected_ ? 1.0f : 0.0f, 0.0f), | ||||
|                              100); | ||||
|   draw_list->AddCircle(dot_pos, 6.0f, ImColor(1.0f, 1.0f, 1.0f), 100); | ||||
|  | ||||
|   ImGui::SetWindowFontScale(0.6f); | ||||
|   draw_list->AddText( | ||||
|       ImVec2(25, main_window_height_default_ - status_bar_height_ + 3.0f), | ||||
|       ImColor(0.0f, 0.0f, 0.0f), | ||||
|       signal_connected_ | ||||
|           ? localization::signal_connected[localization_language_index_].c_str() | ||||
|           : localization::signal_disconnected[localization_language_index_] | ||||
|                 .c_str()); | ||||
|   ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|   ImGui::PopStyleColor(); | ||||
|   ImGui::EndChild(); | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										174
									
								
								src/gui/toolbars/title_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,174 @@ | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| #define BUTTON_PADDING 36.0f | ||||
|  | ||||
| int Render::TitleBar(bool main_window) { | ||||
|   ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); | ||||
|   ImGui::SetWindowFontScale(0.8f); | ||||
|   ImGui::BeginChild( | ||||
|       main_window ? "MainTitleBar" : "StreamTitleBar", | ||||
|       ImVec2(main_window ? main_window_width_ : stream_window_width_, | ||||
|              title_bar_height_), | ||||
|       ImGuiChildFlags_Border, | ||||
|       ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration | | ||||
|           ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|   ImGui::SetWindowFontScale(1.0f); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||
|   if (ImGui::BeginMenuBar()) { | ||||
|     ImGui::SetCursorPosX( | ||||
|         (main_window ? main_window_width_ : stream_window_width_) - | ||||
|         (BUTTON_PADDING * 3 - 3)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0.1f)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_HeaderActive, | ||||
|                           ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|     if (main_window) { | ||||
|       float bar_pos_x = ImGui::GetCursorPosX() + 6; | ||||
|       float bar_pos_y = ImGui::GetCursorPosY() + 15; | ||||
|       std::string menu_button = "    ";  // ICON_FA_BARS; | ||||
|       if (ImGui::BeginMenu(menu_button.c_str())) { | ||||
|         ImGui::SetWindowFontScale(0.5f); | ||||
|         if (ImGui::MenuItem( | ||||
|                 localization::settings[localization_language_index_].c_str())) { | ||||
|           show_settings_window_ = true; | ||||
|         } | ||||
|         if (ImGui::MenuItem( | ||||
|                 localization::about[localization_language_index_].c_str())) { | ||||
|           show_about_window_ = true; | ||||
|         } | ||||
|         ImGui::SetWindowFontScale(1.0f); | ||||
|         ImGui::EndMenu(); | ||||
|       } | ||||
|       float menu_bar_line_size = 15.0f; | ||||
|       draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y - 6), | ||||
|                          ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y - 6), | ||||
|                          IM_COL32(0, 0, 0, 255)); | ||||
|       draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y), | ||||
|                          ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y), | ||||
|                          IM_COL32(0, 0, 0, 255)); | ||||
|       draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y + 6), | ||||
|                          ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y + 6), | ||||
|                          IM_COL32(0, 0, 0, 255)); | ||||
|  | ||||
|       { | ||||
|         SettingWindow(); | ||||
|         SelfHostedServerWindow(); | ||||
|         AboutWindow(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ImGui::PopStyleColor(2); | ||||
|  | ||||
|     ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); | ||||
|     ImGui::SetCursorPosX(main_window | ||||
|                              ? (main_window_width_ - BUTTON_PADDING * 2) | ||||
|                              : (stream_window_width_ - BUTTON_PADDING * 3)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ButtonActive, | ||||
|                           ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|  | ||||
|     float minimize_pos_x = ImGui::GetCursorPosX() + 12; | ||||
|     float minimize_pos_y = ImGui::GetCursorPosY() + 15; | ||||
|     std::string window_minimize_button = "##minimize";  // ICON_FA_MINUS; | ||||
|     if (ImGui::Button(window_minimize_button.c_str(), | ||||
|                       ImVec2(BUTTON_PADDING, 30))) { | ||||
|       SDL_MinimizeWindow(main_window ? main_window_ : stream_window_); | ||||
|     } | ||||
|     draw_list->AddLine(ImVec2(minimize_pos_x, minimize_pos_y), | ||||
|                        ImVec2(minimize_pos_x + 12, minimize_pos_y), | ||||
|                        IM_COL32(0, 0, 0, 255)); | ||||
|     ImGui::PopStyleColor(2); | ||||
|  | ||||
|     if (!main_window) { | ||||
|       ImGui::SetCursorPosX(stream_window_width_ - BUTTON_PADDING * 2); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f)); | ||||
|       ImGui::PushStyleColor(ImGuiCol_ButtonActive, | ||||
|                             ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|  | ||||
|       if (window_maximized_) { | ||||
|         float pos_x_top = ImGui::GetCursorPosX() + 11; | ||||
|         float pos_y_top = ImGui::GetCursorPosY() + 11; | ||||
|         float pos_x_bottom = ImGui::GetCursorPosX() + 13; | ||||
|         float pos_y_bottom = ImGui::GetCursorPosY() + 9; | ||||
|         std::string window_restore_button = | ||||
|             "##restore";  // ICON_FA_WINDOW_RESTORE; | ||||
|         if (ImGui::Button(window_restore_button.c_str(), | ||||
|                           ImVec2(BUTTON_PADDING, 30))) { | ||||
|           SDL_RestoreWindow(stream_window_); | ||||
|           window_maximized_ = false; | ||||
|         } | ||||
|         draw_list->AddRect(ImVec2(pos_x_top, pos_y_top), | ||||
|                            ImVec2(pos_x_top + 12, pos_y_top + 12), | ||||
|                            IM_COL32(0, 0, 0, 255)); | ||||
|         draw_list->AddRect(ImVec2(pos_x_bottom, pos_y_bottom), | ||||
|                            ImVec2(pos_x_bottom + 12, pos_y_bottom + 12), | ||||
|                            IM_COL32(0, 0, 0, 255)); | ||||
|         draw_list->AddRectFilled(ImVec2(pos_x_top + 1, pos_y_top + 1), | ||||
|                                  ImVec2(pos_x_top + 11, pos_y_top + 11), | ||||
|                                  IM_COL32(255, 255, 255, 255)); | ||||
|       } else { | ||||
|         float maximize_pos_x = ImGui::GetCursorPosX() + 12; | ||||
|         float maximize_pos_y = ImGui::GetCursorPosY() + 10; | ||||
|         std::string window_maximize_button = | ||||
|             "##maximize";  // ICON_FA_SQUARE_FULL; | ||||
|         if (ImGui::Button(window_maximize_button.c_str(), | ||||
|                           ImVec2(BUTTON_PADDING, 30))) { | ||||
|           SDL_MaximizeWindow(stream_window_); | ||||
|           window_maximized_ = !window_maximized_; | ||||
|         } | ||||
|         draw_list->AddRect(ImVec2(maximize_pos_x, maximize_pos_y), | ||||
|                            ImVec2(maximize_pos_x + 12, maximize_pos_y + 12), | ||||
|                            IM_COL32(0, 0, 0, 255)); | ||||
|       } | ||||
|       ImGui::PopStyleColor(2); | ||||
|     } | ||||
|  | ||||
|     ImGui::SetCursorPosX( | ||||
|         (main_window ? main_window_width_ : stream_window_width_) - | ||||
|         BUTTON_PADDING); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0, 0, 1.0f)); | ||||
|     ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0, 0, 0.5f)); | ||||
|  | ||||
|     float xmark_pos_x = ImGui::GetCursorPosX() + 18; | ||||
|     float xmark_pos_y = ImGui::GetCursorPosY() + 16; | ||||
|     float xmark_size = 12.0f; | ||||
|     std::string close_button = "##xmark";  // ICON_FA_XMARK; | ||||
|     if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) { | ||||
| #if _WIN32 | ||||
|       if (enable_minimize_to_tray_) { | ||||
|         tray_->MinimizeToTray(); | ||||
|       } else { | ||||
| #endif | ||||
|         SDL_Event event; | ||||
|         event.type = SDL_EVENT_QUIT; | ||||
|         SDL_PushEvent(&event); | ||||
| #if _WIN32 | ||||
|       } | ||||
| #endif | ||||
|     } | ||||
|     draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f, | ||||
|                               xmark_pos_y - xmark_size / 2 + 0.75f), | ||||
|                        ImVec2(xmark_pos_x + xmark_size / 2 - 1.5f, | ||||
|                               xmark_pos_y + xmark_size / 2 - 0.5f), | ||||
|                        IM_COL32(0, 0, 0, 255)); | ||||
|     draw_list->AddLine(ImVec2(xmark_pos_x + xmark_size / 2 - 1.75f, | ||||
|                               xmark_pos_y - xmark_size / 2 + 0.75f), | ||||
|                        ImVec2(xmark_pos_x - xmark_size / 2, | ||||
|                               xmark_pos_y + xmark_size / 2 - 1.0f), | ||||
|                        IM_COL32(0, 0, 0, 255)); | ||||
|  | ||||
|     ImGui::PopStyleColor(2); | ||||
|  | ||||
|     ImGui::PopStyleColor(); | ||||
|   } | ||||
|  | ||||
|   ImGui::EndMenuBar(); | ||||
|   ImGui::EndChild(); | ||||
|   ImGui::PopStyleColor(); | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										112
									
								
								src/gui/tray/win_tray.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,112 @@ | ||||
| #include "win_tray.h" | ||||
|  | ||||
| #include <SDL3/SDL.h> | ||||
|  | ||||
| #include "localization.h" | ||||
|  | ||||
| // callback for the message-only window that handles tray icon messages | ||||
| static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam, | ||||
|                                    LPARAM lParam) { | ||||
|   WinTray* tray = | ||||
|       reinterpret_cast<WinTray*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); | ||||
|   if (!tray) { | ||||
|     return DefWindowProc(hwnd, msg, wParam, lParam); | ||||
|   } | ||||
|  | ||||
|   if (msg == WM_TRAY_CALLBACK) { | ||||
|     MSG tmpMsg = {}; | ||||
|     tmpMsg.message = msg; | ||||
|     tmpMsg.wParam = wParam; | ||||
|     tmpMsg.lParam = lParam; | ||||
|     tray->HandleTrayMessage(&tmpMsg); | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   return DefWindowProc(hwnd, msg, wParam, lParam); | ||||
| } | ||||
|  | ||||
| WinTray::WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip, | ||||
|                  int language_index) | ||||
|     : app_hwnd_(app_hwnd), | ||||
|       icon_(icon), | ||||
|       tip_(tooltip), | ||||
|       hwnd_message_only_(nullptr), | ||||
|       language_index_(language_index) { | ||||
|   WNDCLASS wc = {}; | ||||
|   wc.lpfnWndProc = MsgWndProc; | ||||
|   wc.hInstance = GetModuleHandle(nullptr); | ||||
|   wc.lpszClassName = L"TrayMessageWindow"; | ||||
|   RegisterClass(&wc); | ||||
|  | ||||
|   // create a message-only window to receive tray messages | ||||
|   hwnd_message_only_ = | ||||
|       CreateWindowEx(0, wc.lpszClassName, L"TrayMsg", 0, 0, 0, 0, 0, | ||||
|                      HWND_MESSAGE, nullptr, wc.hInstance, nullptr); | ||||
|  | ||||
|   // store pointer to this WinTray instance in window data | ||||
|   SetWindowLongPtr(hwnd_message_only_, GWLP_USERDATA, | ||||
|                    reinterpret_cast<LONG_PTR>(this)); | ||||
|  | ||||
|   // initialize NOTIFYICONDATA structure | ||||
|   ZeroMemory(&nid_, sizeof(nid_)); | ||||
|   nid_.cbSize = sizeof(nid_); | ||||
|   nid_.hWnd = hwnd_message_only_; | ||||
|   nid_.uID = 1; | ||||
|   nid_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; | ||||
|   nid_.uCallbackMessage = WM_TRAY_CALLBACK; | ||||
|   nid_.hIcon = icon_; | ||||
|   wcsncpy_s(nid_.szTip, tip_.c_str(), _TRUNCATE); | ||||
| } | ||||
|  | ||||
| WinTray::~WinTray() { | ||||
|   RemoveTrayIcon(); | ||||
|   if (hwnd_message_only_) DestroyWindow(hwnd_message_only_); | ||||
| } | ||||
|  | ||||
| void WinTray::MinimizeToTray() { | ||||
|   Shell_NotifyIcon(NIM_ADD, &nid_); | ||||
|   // hide application window | ||||
|   ShowWindow(app_hwnd_, SW_HIDE); | ||||
| } | ||||
|  | ||||
| void WinTray::RemoveTrayIcon() { Shell_NotifyIcon(NIM_DELETE, &nid_); } | ||||
|  | ||||
| bool WinTray::HandleTrayMessage(MSG* msg) { | ||||
|   if (!msg || msg->message != WM_TRAY_CALLBACK) return false; | ||||
|  | ||||
|   switch (LOWORD(msg->lParam)) { | ||||
|     case WM_LBUTTONDBLCLK: | ||||
|     case WM_LBUTTONUP: { | ||||
|       ShowWindow(app_hwnd_, SW_SHOW); | ||||
|       SetForegroundWindow(app_hwnd_); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case WM_RBUTTONUP: { | ||||
|       POINT pt; | ||||
|       GetCursorPos(&pt); | ||||
|       HMENU menu = CreatePopupMenu(); | ||||
|       AppendMenuW(menu, MF_STRING, 1001, | ||||
|                   localization::exit_program[language_index_]); | ||||
|  | ||||
|       SetForegroundWindow(hwnd_message_only_); | ||||
|       int cmd = | ||||
|           TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_LEFTALIGN, | ||||
|                          pt.x, pt.y, 0, hwnd_message_only_, nullptr); | ||||
|       DestroyMenu(menu); | ||||
|  | ||||
|       // handle menu command | ||||
|       if (cmd == 1001) { | ||||
|         // exit application | ||||
|         SDL_Event event; | ||||
|         event.type = SDL_EVENT_QUIT; | ||||
|         SDL_PushEvent(&event); | ||||
|       } else if (cmd == 1002) { | ||||
|         ShowWindow(app_hwnd_, SW_SHOW);  // show main window | ||||
|         SetForegroundWindow(app_hwnd_); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/gui/tray/win_tray.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2025-10-22 | ||||
|  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _WIN_TRAY_H_ | ||||
| #define _WIN_TRAY_H_ | ||||
|  | ||||
| #include <Windows.h> | ||||
| #include <shellapi.h> | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #define WM_TRAY_CALLBACK (WM_USER + 1) | ||||
|  | ||||
| class WinTray { | ||||
|  public: | ||||
|   WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip, | ||||
|           int language_index); | ||||
|   ~WinTray(); | ||||
|  | ||||
|   void MinimizeToTray(); | ||||
|   void RemoveTrayIcon(); | ||||
|   bool HandleTrayMessage(MSG* msg); | ||||
|  | ||||
|  private: | ||||
|   HWND app_hwnd_; | ||||
|   HWND hwnd_message_only_; | ||||
|   HICON icon_; | ||||
|   std::wstring tip_; | ||||
|   int language_index_; | ||||
|   NOTIFYICONDATA nid_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										61
									
								
								src/gui/windows/about_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,61 @@ | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::AboutWindow() { | ||||
|   if (show_about_window_) { | ||||
|     const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||
|  | ||||
|     ImGui::SetNextWindowPos(ImVec2( | ||||
|         (viewport->WorkSize.x - viewport->WorkPos.x - about_window_width_) / 2, | ||||
|         (viewport->WorkSize.y - viewport->WorkPos.y - about_window_height_) / | ||||
|             2)); | ||||
|  | ||||
|     ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height_)); | ||||
|  | ||||
|     ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|     ImGui::SetWindowFontScale(0.5f); | ||||
|     ImGui::Begin( | ||||
|         localization::about[localization_language_index_].c_str(), nullptr, | ||||
|         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|             ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); | ||||
|     ImGui::SetWindowFontScale(1.0f); | ||||
|     ImGui::SetWindowFontScale(0.5f); | ||||
|  | ||||
|     std::string version; | ||||
| #ifdef CROSSDESK_VERSION | ||||
|     version = CROSSDESK_VERSION; | ||||
| #else | ||||
|     version = "Unknown"; | ||||
| #endif | ||||
|  | ||||
|     std::string text = localization::version[localization_language_index_] + | ||||
|                        ": CrossDesk v" + version; | ||||
|     ImGui::Text("%s", text.c_str()); | ||||
|     ImGui::Text(""); | ||||
|  | ||||
|     std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved."; | ||||
|     std::string license_text = "Licensed under GNU LGPL v3."; | ||||
|     ImGui::Text("%s", copyright_text.c_str()); | ||||
|     ImGui::Text("%s", license_text.c_str()); | ||||
|  | ||||
|     ImGui::SetCursorPosX(about_window_width_ * 0.42f); | ||||
|     ImGui::SetCursorPosY(about_window_height_ * 0.75f); | ||||
|     // OK | ||||
|     if (ImGui::Button(localization::ok[localization_language_index_].c_str())) { | ||||
|       show_about_window_ = false; | ||||
|     } | ||||
|  | ||||
|     ImGui::SetWindowFontScale(1.0f); | ||||
|     ImGui::SetWindowFontScale(0.5f); | ||||
|     ImGui::End(); | ||||
|     ImGui::SetWindowFontScale(1.0f); | ||||
|     ImGui::PopStyleVar(3); | ||||
|     ImGui::PopStyleColor(); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										171
									
								
								src/gui/windows/connection_status_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,171 @@ | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| bool Render::ConnectionStatusWindow( | ||||
|     std::shared_ptr<SubStreamWindowProperties> &props) { | ||||
|   const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||
|   bool ret_flag = false; | ||||
|   ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                                   connection_status_window_width_) / | ||||
|                                      2, | ||||
|                                  (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                                   connection_status_window_height_) / | ||||
|                                      2)); | ||||
|  | ||||
|   ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_, | ||||
|                                   connection_status_window_height_)); | ||||
|  | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0)); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|  | ||||
|   ImGui::Begin("ConnectionStatusWindow", nullptr, | ||||
|                ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                    ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                    ImGuiWindowFlags_NoSavedSettings); | ||||
|   ImGui::PopStyleVar(2); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   ImGui::SetWindowFontScale(0.5f); | ||||
|   std::string text; | ||||
|  | ||||
|   if (ConnectionStatus::Connecting == props->connection_status_) { | ||||
|     text = localization::p2p_connecting[localization_language_index_]; | ||||
|     ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|     ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|   } else if (ConnectionStatus::Connected == props->connection_status_) { | ||||
|     text = localization::p2p_connected[localization_language_index_]; | ||||
|     ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|     ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|     // ok | ||||
|     if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||
|       show_connection_status_window_ = false; | ||||
|     } | ||||
|   } else if (ConnectionStatus::Disconnected == props->connection_status_) { | ||||
|     text = localization::p2p_disconnected[localization_language_index_]; | ||||
|     ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|     ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|     // ok | ||||
|     if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||
|       show_connection_status_window_ = false; | ||||
|     } | ||||
|   } else if (ConnectionStatus::Failed == props->connection_status_) { | ||||
|     text = localization::p2p_failed[localization_language_index_]; | ||||
|     ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|     ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|     // ok | ||||
|     if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||
|       show_connection_status_window_ = false; | ||||
|     } | ||||
|   } else if (ConnectionStatus::Closed == props->connection_status_) { | ||||
|     text = localization::p2p_closed[localization_language_index_]; | ||||
|     ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|     ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|     // ok | ||||
|     if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||
|       show_connection_status_window_ = false; | ||||
|     } | ||||
|   } else if (ConnectionStatus::IncorrectPassword == props->connection_status_) { | ||||
|     if (!password_validating_) { | ||||
|       if (password_validating_time_ == 1) { | ||||
|         text = localization::input_password[localization_language_index_]; | ||||
|       } else { | ||||
|         text = localization::reinput_password[localization_language_index_]; | ||||
|       } | ||||
|  | ||||
|       auto window_width = ImGui::GetWindowSize().x; | ||||
|       auto window_height = ImGui::GetWindowSize().y; | ||||
|       ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f); | ||||
|       ImGui::SetCursorPosY(window_height * 0.4f); | ||||
|       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2); | ||||
|  | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|  | ||||
|       if (focus_on_input_widget_) { | ||||
|         ImGui::SetKeyboardFocusHere(); | ||||
|         focus_on_input_widget_ = false; | ||||
|       } | ||||
|  | ||||
|       ImGui::InputText("##password", props->remote_password_, | ||||
|                        IM_ARRAYSIZE(props->remote_password_), | ||||
|                        ImGuiInputTextFlags_CharsNoBlank); | ||||
|  | ||||
|       ImGui::SetWindowFontScale(0.4f); | ||||
|  | ||||
|       ImVec2 text_size = ImGui::CalcTextSize( | ||||
|           localization::remember_password[localization_language_index_] | ||||
|               .c_str()); | ||||
|       ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f); | ||||
|       ImGui::Checkbox( | ||||
|           localization::remember_password[localization_language_index_].c_str(), | ||||
|           &(props->remember_password_)); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PopStyleVar(); | ||||
|  | ||||
|       ImGui::SetCursorPosX(window_width * 0.315f); | ||||
|       ImGui::SetCursorPosY(window_height * 0.75f); | ||||
|       // ok | ||||
|       if (ImGui::Button( | ||||
|               localization::ok[localization_language_index_].c_str()) || | ||||
|           ImGui::IsKeyPressed(ImGuiKey_Enter)) { | ||||
|         show_connection_status_window_ = true; | ||||
|         password_validating_ = true; | ||||
|         props->rejoin_ = true; | ||||
|         need_to_rejoin_ = true; | ||||
|         focus_on_input_widget_ = true; | ||||
|       } | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|       // cancel | ||||
|       if (ImGui::Button( | ||||
|               localization::cancel[localization_language_index_].c_str()) || | ||||
|           ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||
|         memset(props->remote_password_, 0, sizeof(props->remote_password_)); | ||||
|         show_connection_status_window_ = false; | ||||
|         focus_on_input_widget_ = true; | ||||
|       } | ||||
|     } else if (password_validating_) { | ||||
|       text = localization::validate_password[localization_language_index_]; | ||||
|       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|     } | ||||
|   } else if (ConnectionStatus::NoSuchTransmissionId == | ||||
|              props->connection_status_) { | ||||
|     text = localization::no_such_id[localization_language_index_]; | ||||
|     ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||
|     ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||
|     // ok | ||||
|     if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||
|         ImGui::IsKeyPressed(ImGuiKey_Enter)) { | ||||
|       show_connection_status_window_ = false; | ||||
|       re_enter_remote_id_ = true; | ||||
|       DestroyPeer(&props->peer_); | ||||
|       ret_flag = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   auto window_width = ImGui::GetWindowSize().x; | ||||
|   auto window_height = ImGui::GetWindowSize().y; | ||||
|   auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
|   ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||
|   ImGui::SetCursorPosY(window_height * 0.2f); | ||||
|   ImGui::Text("%s", text.c_str()); | ||||
|   ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|   ImGui::End(); | ||||
|   ImGui::PopStyleVar(); | ||||
|  | ||||
|   return ret_flag; | ||||
| } | ||||
							
								
								
									
										223
									
								
								src/gui/windows/control_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,223 @@ | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) { | ||||
|   double time_duration = | ||||
|       ImGui::GetTime() - props->control_bar_button_pressed_time_; | ||||
|   if (props->control_window_width_is_changing_) { | ||||
|     if (props->control_bar_expand_) { | ||||
|       props->control_window_width_ = | ||||
|           (float)(props->control_window_min_width_ + | ||||
|                   (props->control_window_max_width_ - | ||||
|                    props->control_window_min_width_) * | ||||
|                       4 * time_duration); | ||||
|     } else { | ||||
|       props->control_window_width_ = | ||||
|           (float)(props->control_window_max_width_ - | ||||
|                   (props->control_window_max_width_ - | ||||
|                    props->control_window_min_width_) * | ||||
|                       4 * time_duration); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   time_duration = | ||||
|       ImGui::GetTime() - props->net_traffic_stats_button_pressed_time_; | ||||
|   if (props->control_window_height_is_changing_) { | ||||
|     if (props->control_bar_expand_ && | ||||
|         props->net_traffic_stats_button_pressed_) { | ||||
|       props->control_window_height_ = | ||||
|           (float)(props->control_window_min_height_ + | ||||
|                   (props->control_window_max_height_ - | ||||
|                    props->control_window_min_height_) * | ||||
|                       4 * time_duration); | ||||
|     } else if (props->control_bar_expand_ && | ||||
|                !props->net_traffic_stats_button_pressed_) { | ||||
|       props->control_window_height_ = | ||||
|           (float)(props->control_window_max_height_ - | ||||
|                   (props->control_window_max_height_ - | ||||
|                    props->control_window_min_height_) * | ||||
|                       4 * time_duration); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 1)); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); | ||||
|  | ||||
|   ImGui::SetNextWindowSize( | ||||
|       ImVec2(props->control_window_width_, props->control_window_height_), | ||||
|       ImGuiCond_Always); | ||||
|  | ||||
|   ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_ + 1), ImGuiCond_Once); | ||||
|  | ||||
|   float pos_x = 0; | ||||
|   float pos_y = 0; | ||||
|   float y_boundary = fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1); | ||||
|  | ||||
|   if (props->reset_control_bar_pos_) { | ||||
|     float new_cursor_pos_x = 0; | ||||
|     float new_cursor_pos_y = 0; | ||||
|  | ||||
|     // set control window pos | ||||
|     if (props->control_window_pos_.y + props->control_window_height_ > | ||||
|         stream_window_height_) { | ||||
|       pos_y = stream_window_height_ - props->control_window_height_; | ||||
|     } else if (props->control_window_pos_.y < y_boundary) { | ||||
|       pos_y = y_boundary; | ||||
|     } else { | ||||
|       pos_y = props->control_window_pos_.y; | ||||
|     } | ||||
|  | ||||
|     if (props->is_control_bar_in_left_) { | ||||
|       pos_x = 0; | ||||
|     } else { | ||||
|       pos_x = stream_window_width_ - props->control_window_width_; | ||||
|     } | ||||
|  | ||||
|     ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always); | ||||
|  | ||||
|     if (0 != props->mouse_diff_control_bar_pos_x_ && | ||||
|         0 != props->mouse_diff_control_bar_pos_y_) { | ||||
|       // set cursor pos | ||||
|       new_cursor_pos_x = pos_x + props->mouse_diff_control_bar_pos_x_; | ||||
|       new_cursor_pos_y = pos_y + props->mouse_diff_control_bar_pos_y_; | ||||
|  | ||||
|       SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x, | ||||
|                             (int)new_cursor_pos_y); | ||||
|     } | ||||
|     props->reset_control_bar_pos_ = false; | ||||
|   } else if (!props->reset_control_bar_pos_ && | ||||
|                  ImGui::IsMouseReleased(ImGuiMouseButton_Left) || | ||||
|              props->control_window_width_is_changing_) { | ||||
|     if (props->control_window_pos_.x <= stream_window_width_ / 2) { | ||||
|       if (props->control_window_pos_.y + props->control_window_height_ > | ||||
|           stream_window_height_) { | ||||
|         pos_y = stream_window_height_ - props->control_window_height_; | ||||
|       } else { | ||||
|         pos_y = props->control_window_pos_.y; | ||||
|       } | ||||
|  | ||||
|       if (props->control_bar_expand_) { | ||||
|         if (props->control_window_width_ >= props->control_window_max_width_) { | ||||
|           props->control_window_width_ = props->control_window_max_width_; | ||||
|           props->control_window_width_is_changing_ = false; | ||||
|         } else { | ||||
|           props->control_window_width_is_changing_ = true; | ||||
|         } | ||||
|       } else { | ||||
|         if (props->control_window_width_ <= props->control_window_min_width_) { | ||||
|           props->control_window_width_ = props->control_window_min_width_; | ||||
|           props->control_window_width_is_changing_ = false; | ||||
|         } else { | ||||
|           props->control_window_width_is_changing_ = true; | ||||
|         } | ||||
|       } | ||||
|       props->is_control_bar_in_left_ = true; | ||||
|     } else if (props->control_window_pos_.x > stream_window_width_ / 2) { | ||||
|       pos_x = 0; | ||||
|       pos_y = | ||||
|           (props->control_window_pos_.y >= y_boundary && | ||||
|            props->control_window_pos_.y <= | ||||
|                stream_window_height_ - props->control_window_height_) | ||||
|               ? props->control_window_pos_.y | ||||
|               : (props->control_window_pos_.y < (fullscreen_button_pressed_ | ||||
|                                                      ? 0 | ||||
|                                                      : (title_bar_height_ + 1)) | ||||
|                      ? (fullscreen_button_pressed_ ? 0 | ||||
|                                                    : (title_bar_height_ + 1)) | ||||
|                      : (stream_window_height_ - props->control_window_height_)); | ||||
|  | ||||
|       if (props->control_bar_expand_) { | ||||
|         if (props->control_window_width_ >= props->control_window_max_width_) { | ||||
|           props->control_window_width_ = props->control_window_max_width_; | ||||
|           props->control_window_width_is_changing_ = false; | ||||
|           pos_x = stream_window_width_ - props->control_window_max_width_; | ||||
|         } else { | ||||
|           props->control_window_width_is_changing_ = true; | ||||
|           pos_x = stream_window_width_ - props->control_window_width_; | ||||
|         } | ||||
|       } else { | ||||
|         if (props->control_window_width_ <= props->control_window_min_width_) { | ||||
|           props->control_window_width_ = props->control_window_min_width_; | ||||
|           props->control_window_width_is_changing_ = false; | ||||
|           pos_x = stream_window_width_ - props->control_window_min_width_; | ||||
|         } else { | ||||
|           props->control_window_width_is_changing_ = true; | ||||
|           pos_x = stream_window_width_ - props->control_window_width_; | ||||
|         } | ||||
|       } | ||||
|       props->is_control_bar_in_left_ = false; | ||||
|     } | ||||
|  | ||||
|     if (props->control_window_pos_.y + props->control_window_height_ > | ||||
|         stream_window_height_) { | ||||
|       pos_y = stream_window_height_ - props->control_window_height_; | ||||
|     } else if (props->control_window_pos_.y < y_boundary) { | ||||
|       pos_y = y_boundary; | ||||
|     } | ||||
|     ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always); | ||||
|   } | ||||
|  | ||||
|   if (props->control_bar_expand_ && props->control_window_height_is_changing_) { | ||||
|     if (props->net_traffic_stats_button_pressed_) { | ||||
|       if (props->control_window_height_ >= props->control_window_max_height_) { | ||||
|         props->control_window_height_ = props->control_window_max_height_; | ||||
|         props->control_window_height_is_changing_ = false; | ||||
|       } else { | ||||
|         props->control_window_height_is_changing_ = true; | ||||
|       } | ||||
|     } else { | ||||
|       if (props->control_window_height_ <= props->control_window_min_height_) { | ||||
|         props->control_window_height_ = props->control_window_min_height_; | ||||
|         props->control_window_height_is_changing_ = false; | ||||
|       } else { | ||||
|         props->control_window_height_is_changing_ = true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   std::string control_window_title = props->remote_id_ + "ControlWindow"; | ||||
|   ImGui::Begin(control_window_title.c_str(), nullptr, | ||||
|                ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | | ||||
|                    ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking); | ||||
|   ImGui::PopStyleVar(); | ||||
|  | ||||
|   props->control_window_pos_ = ImGui::GetWindowPos(); | ||||
|   SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_); | ||||
|   props->mouse_diff_control_bar_pos_x_ = | ||||
|       props->mouse_pos_x_ - props->control_window_pos_.x; | ||||
|   props->mouse_diff_control_bar_pos_y_ = | ||||
|       props->mouse_pos_y_ - props->control_window_pos_.y; | ||||
|  | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   static bool a, b, c, d, e; | ||||
|   ImGui::SetNextWindowPos( | ||||
|       ImVec2(props->is_control_bar_in_left_ | ||||
|                  ? props->control_window_pos_.x - props->control_window_width_ | ||||
|                  : props->control_window_pos_.x, | ||||
|              props->control_window_pos_.y), | ||||
|       ImGuiCond_Always); | ||||
|   ImGui::SetWindowFontScale(0.5f); | ||||
|  | ||||
|   std::string control_child_window_title = | ||||
|       props->remote_id_ + "ControlChildWindow"; | ||||
|   ImGui::BeginChild( | ||||
|       control_child_window_title.c_str(), | ||||
|       ImVec2(props->control_window_width_ * 2, props->control_window_height_), | ||||
|       ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration); | ||||
|   ImGui::SetWindowFontScale(1.0f); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   ControlBar(props); | ||||
|   props->control_bar_hovered_ = ImGui::IsWindowHovered(); | ||||
|  | ||||
|   ImGui::EndChild(); | ||||
|   ImGui::End(); | ||||
|   ImGui::PopStyleVar(4); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										393
									
								
								src/gui/windows/main_settings_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,393 @@ | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::SettingWindow() { | ||||
|   if (show_settings_window_) { | ||||
|     if (settings_window_pos_reset_) { | ||||
|       const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     SETTINGS_WINDOW_WIDTH_CN) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     SETTINGS_WINDOW_HEIGHT_CN) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(SETTINGS_WINDOW_WIDTH_CN, SETTINGS_WINDOW_HEIGHT_CN)); | ||||
|       } else { | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     SETTINGS_WINDOW_WIDTH_EN) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     SETTINGS_WINDOW_HEIGHT_EN) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(SETTINGS_WINDOW_WIDTH_EN, SETTINGS_WINDOW_HEIGHT_EN)); | ||||
|       } | ||||
|  | ||||
|       settings_window_pos_reset_ = false; | ||||
|     } | ||||
|  | ||||
|     // Settings | ||||
|     { | ||||
|       static int settings_items_padding = 30; | ||||
|       int settings_items_offset = 0; | ||||
|  | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|       ImGui::Begin(localization::settings[localization_language_index_].c_str(), | ||||
|                    nullptr, | ||||
|                    ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                        ImGuiWindowFlags_NoSavedSettings); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|       { | ||||
|         const char* language_items[] = { | ||||
|             localization::language_zh[localization_language_index_].c_str(), | ||||
|             localization::language_en[localization_language_index_].c_str()}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", localization::language[localization_language_index_].c_str()); | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::Combo("##language", &language_button_value_, language_items, | ||||
|                      IM_ARRAYSIZE(language_items)); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       if (stream_window_inited_) { | ||||
|         ImGui::BeginDisabled(); | ||||
|       } | ||||
|  | ||||
|       { | ||||
|         const char* video_quality_items[] = { | ||||
|             localization::video_quality_high[localization_language_index_] | ||||
|                 .c_str(), | ||||
|             localization::video_quality_medium[localization_language_index_] | ||||
|                 .c_str(), | ||||
|             localization::video_quality_low[localization_language_index_] | ||||
|                 .c_str()}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::video_quality[localization_language_index_].c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::Combo("##video_quality", &video_quality_button_value_, | ||||
|                      video_quality_items, IM_ARRAYSIZE(video_quality_items)); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         const char* video_frame_rate_items[] = {"30 fps", "60 fps"}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text("%s", | ||||
|                     localization::video_frame_rate[localization_language_index_] | ||||
|                         .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::Combo("##video_frame_rate", &video_frame_rate_button_value_, | ||||
|                      video_frame_rate_items, | ||||
|                      IM_ARRAYSIZE(video_frame_rate_items)); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         const char* video_encode_format_items[] = { | ||||
|             localization::h264[localization_language_index_].c_str(), | ||||
|             localization::av1[localization_language_index_].c_str()}; | ||||
|  | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::video_encode_format[localization_language_index_] | ||||
|                 .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::Combo( | ||||
|             "##video_encode_format", &video_encode_format_button_value_, | ||||
|             video_encode_format_items, IM_ARRAYSIZE(video_encode_format_items)); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text("%s", localization::enable_hardware_video_codec | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::Checkbox("##enable_hardware_video_codec", | ||||
|                         &enable_hardware_video_codec_); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::enable_turn[localization_language_index_].c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::Checkbox("##enable_turn", &enable_turn_); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::enable_srtp[localization_language_index_].c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::Checkbox("##enable_srtp", &enable_srtp_); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 1); | ||||
|  | ||||
|         if (ImGui::Button(localization::self_hosted_server_config | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str())) { | ||||
|           show_self_hosted_server_config_window_ = true; | ||||
|         } | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::Checkbox("##enable_self_hosted_server", | ||||
|                         &enable_self_hosted_server_); | ||||
|       } | ||||
| #if _WIN32 | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 4); | ||||
|  | ||||
|         ImGui::Text("%s", | ||||
|                     localization::minimize_to_tray[localization_language_index_] | ||||
|                         .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::Checkbox("##enable_minimize_to_tray_", | ||||
|                         &enable_minimize_to_tray_); | ||||
|       } | ||||
| #endif | ||||
|       if (stream_window_inited_) { | ||||
|         ImGui::EndDisabled(); | ||||
|       } | ||||
|  | ||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|         ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_CN); | ||||
|       } else { | ||||
|         ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN); | ||||
|       } | ||||
|  | ||||
|       settings_items_offset += settings_items_padding + 10; | ||||
|       ImGui::SetCursorPosY(settings_items_offset); | ||||
|       ImGui::PopStyleVar(); | ||||
|  | ||||
|       // OK | ||||
|       if (ImGui::Button( | ||||
|               localization::ok[localization_language_index_].c_str())) { | ||||
|         show_settings_window_ = false; | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
|  | ||||
|         // Language | ||||
|         if (language_button_value_ == 0) { | ||||
|           config_center_->SetLanguage(ConfigCenter::LANGUAGE::CHINESE); | ||||
|         } else { | ||||
|           config_center_->SetLanguage(ConfigCenter::LANGUAGE::ENGLISH); | ||||
|         } | ||||
|         language_button_value_last_ = language_button_value_; | ||||
|         localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_; | ||||
|         localization_language_index_ = language_button_value_; | ||||
|         LOG_INFO("Set localization language: {}", | ||||
|                  localization_language_index_ == 0 ? "zh" : "en"); | ||||
|  | ||||
|         // Video quality | ||||
|         if (video_quality_button_value_ == 0) { | ||||
|           config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH); | ||||
|         } else if (video_quality_button_value_ == 1) { | ||||
|           config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM); | ||||
|         } else { | ||||
|           config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW); | ||||
|         } | ||||
|         video_quality_button_value_last_ = video_quality_button_value_; | ||||
|  | ||||
|         // Video encode format | ||||
|         if (video_encode_format_button_value_ == 0) { | ||||
|           config_center_->SetVideoEncodeFormat( | ||||
|               ConfigCenter::VIDEO_ENCODE_FORMAT::H264); | ||||
|         } else if (video_encode_format_button_value_ == 1) { | ||||
|           config_center_->SetVideoEncodeFormat( | ||||
|               ConfigCenter::VIDEO_ENCODE_FORMAT::AV1); | ||||
|         } | ||||
|         video_encode_format_button_value_last_ = | ||||
|             video_encode_format_button_value_; | ||||
|  | ||||
|         // Hardware video codec | ||||
|         if (enable_hardware_video_codec_) { | ||||
|           config_center_->SetHardwareVideoCodec(true); | ||||
|         } else { | ||||
|           config_center_->SetHardwareVideoCodec(false); | ||||
|         } | ||||
|         enable_hardware_video_codec_last_ = enable_hardware_video_codec_; | ||||
|  | ||||
|         // TURN mode | ||||
|         if (enable_turn_) { | ||||
|           config_center_->SetTurn(true); | ||||
|         } else { | ||||
|           config_center_->SetTurn(false); | ||||
|         } | ||||
|         enable_turn_last_ = enable_turn_; | ||||
|  | ||||
|         // SRTP | ||||
|         if (enable_srtp_) { | ||||
|           config_center_->SetSrtp(true); | ||||
|         } else { | ||||
|           config_center_->SetSrtp(false); | ||||
|         } | ||||
|         enable_srtp_last_ = enable_srtp_; | ||||
|  | ||||
|         if (enable_self_hosted_server_) { | ||||
|           config_center_->SetSelfHosted(true); | ||||
|         } else { | ||||
|           config_center_->SetSelfHosted(false); | ||||
|         } | ||||
|  | ||||
|         settings_window_pos_reset_ = true; | ||||
|  | ||||
|         // Recreate peer instance | ||||
|         LoadSettingsFromCacheFile(); | ||||
|  | ||||
|         // Recreate peer instance | ||||
|         if (!stream_window_inited_) { | ||||
|           LOG_INFO("Recreate peer instance"); | ||||
|           CleanupPeers(); | ||||
|           CreateConnectionPeer(); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|       // Cancel | ||||
|       if (ImGui::Button( | ||||
|               localization::cancel[localization_language_index_].c_str())) { | ||||
|         show_settings_window_ = false; | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
|  | ||||
|         if (language_button_value_ != language_button_value_last_) { | ||||
|           language_button_value_ = language_button_value_last_; | ||||
|         } | ||||
|  | ||||
|         if (video_quality_button_value_ != video_quality_button_value_last_) { | ||||
|           video_quality_button_value_ = video_quality_button_value_last_; | ||||
|         } | ||||
|  | ||||
|         if (video_encode_format_button_value_ != | ||||
|             video_encode_format_button_value_last_) { | ||||
|           video_encode_format_button_value_ = | ||||
|               video_encode_format_button_value_last_; | ||||
|         } | ||||
|  | ||||
|         if (enable_hardware_video_codec_ != enable_hardware_video_codec_last_) { | ||||
|           enable_hardware_video_codec_ = enable_hardware_video_codec_last_; | ||||
|         } | ||||
|  | ||||
|         if (enable_turn_ != enable_turn_last_) { | ||||
|           enable_turn_ = enable_turn_last_; | ||||
|         } | ||||
|  | ||||
|         settings_window_pos_reset_ = true; | ||||
|       } | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::End(); | ||||
|       ImGui::PopStyleVar(2); | ||||
|       ImGui::PopStyleColor(); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										49
									
								
								src/gui/windows/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,49 @@ | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| int Render::MainWindow() { | ||||
|   ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||
|   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|   ImGui::BeginChild("DeskWindow", | ||||
|                     ImVec2(main_window_width_default_, local_window_height_), | ||||
|                     ImGuiChildFlags_Border, | ||||
|                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||
|                         ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||
|   ImGui::PopStyleVar(); | ||||
|   ImGui::PopStyleColor(); | ||||
|  | ||||
|   LocalWindow(); | ||||
|  | ||||
|   ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||
|   draw_list->AddLine( | ||||
|       ImVec2(main_window_width_default_ / 2, title_bar_height_ + 15.0f), | ||||
|       ImVec2(main_window_width_default_ / 2, title_bar_height_ + 225.0f), | ||||
|       IM_COL32(0, 0, 0, 122), 1.0f); | ||||
|  | ||||
|   RemoteWindow(); | ||||
|   ImGui::EndChild(); | ||||
|  | ||||
|   RecentConnectionsWindow(); | ||||
|   StatusBar(); | ||||
|  | ||||
|   if (show_connection_status_window_) { | ||||
|     for (auto it = client_properties_.begin(); | ||||
|          it != client_properties_.end();) { | ||||
|       auto& props = it->second; | ||||
|       if (focused_remote_id_ == props->remote_id_) { | ||||
|         if (ConnectionStatusWindow(props)) { | ||||
|           it = client_properties_.erase(it); | ||||
|         } else { | ||||
|           ++it; | ||||
|         } | ||||
|       } else { | ||||
|         ++it; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										270
									
								
								src/gui/windows/server_settings_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,270 @@ | ||||
| #include <filesystem> | ||||
| #include <vector> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| #include "layout.h" | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| std::vector<std::string> GetRootEntries() { | ||||
|   std::vector<std::string> roots; | ||||
| #ifdef _WIN32 | ||||
|   DWORD mask = GetLogicalDrives(); | ||||
|   for (char letter = 'A'; letter <= 'Z'; ++letter) { | ||||
|     if (mask & 1) { | ||||
|       roots.push_back(std::string(1, letter) + ":\\"); | ||||
|     } | ||||
|     mask >>= 1; | ||||
|   } | ||||
| #else | ||||
|   roots.push_back("/"); | ||||
| #endif | ||||
|   return roots; | ||||
| } | ||||
|  | ||||
| int Render::ShowSimpleFileBrowser() { | ||||
|   std::string display_text; | ||||
|   if (!selected_file_.empty()) { | ||||
|     display_text = std::filesystem::path(selected_file_).filename().string(); | ||||
|   } else if (selected_current_file_path_ != "Root") { | ||||
|     display_text = | ||||
|         std::filesystem::path(selected_current_file_path_).filename().string(); | ||||
|     if (display_text.empty()) { | ||||
|       display_text = selected_current_file_path_; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (display_text.empty()) { | ||||
|     display_text = | ||||
|         localization::select_a_file[localization_language_index_].c_str(); | ||||
|   } | ||||
|  | ||||
|   if (ImGui::BeginCombo("##select_a_file", display_text.c_str())) { | ||||
|     if (selected_current_file_path_ == "Root" || | ||||
|         !std::filesystem::exists(selected_current_file_path_) || | ||||
|         !std::filesystem::is_directory(selected_current_file_path_)) { | ||||
|       auto roots = GetRootEntries(); | ||||
|       for (const auto& root : roots) { | ||||
|         if (ImGui::Selectable(root.c_str())) { | ||||
|           selected_current_file_path_ = root; | ||||
|           selected_file_.clear(); | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       std::filesystem::path p(selected_current_file_path_); | ||||
|  | ||||
|       if (ImGui::Selectable("..")) { | ||||
|         if (p.has_parent_path() && p != p.root_path()) | ||||
|           selected_current_file_path_ = p.parent_path().string(); | ||||
|         else | ||||
|           selected_current_file_path_ = "Root"; | ||||
|         selected_file_.clear(); | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         for (const auto& entry : | ||||
|              std::filesystem::directory_iterator(selected_current_file_path_)) { | ||||
|           std::string name = entry.path().filename().string(); | ||||
|           if (entry.is_directory()) { | ||||
|             if (ImGui::Selectable(name.c_str())) { | ||||
|               selected_current_file_path_ = entry.path().string(); | ||||
|               selected_file_.clear(); | ||||
|             } | ||||
|           } else { | ||||
|             if (ImGui::Selectable(name.c_str())) { | ||||
|               selected_file_ = entry.path().string(); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } catch (const std::exception& e) { | ||||
|         ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", e.what()); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     ImGui::EndCombo(); | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int Render::SelfHostedServerWindow() { | ||||
|   if (show_self_hosted_server_config_window_) { | ||||
|     if (self_hosted_server_config_window_pos_reset_) { | ||||
|       const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN, | ||||
|                    SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN)); | ||||
|       } else { | ||||
|         ImGui::SetNextWindowPos( | ||||
|             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN) / | ||||
|                        2, | ||||
|                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||
|                     SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN) / | ||||
|                        2)); | ||||
|  | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN, | ||||
|                    SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN)); | ||||
|       } | ||||
|  | ||||
|       self_hosted_server_config_window_pos_reset_ = false; | ||||
|     } | ||||
|  | ||||
|     // Settings | ||||
|     { | ||||
|       static int settings_items_padding = 30; | ||||
|       int settings_items_offset = 0; | ||||
|  | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||
|  | ||||
|       ImGui::Begin(localization::self_hosted_server_settings | ||||
|                        [localization_language_index_] | ||||
|                            .c_str(), | ||||
|                    nullptr, | ||||
|                    ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||
|                        ImGuiWindowFlags_NoSavedSettings); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::Text("%s", localization::self_hosted_server_address | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str()); | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::InputText("##signal_server_ip_tmp_", signal_server_ip_tmp_, | ||||
|                          IM_ARRAYSIZE(signal_server_ip_tmp_), | ||||
|                          ImGuiInputTextFlags_AlwaysOverwrite); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::Text( | ||||
|             "%s", | ||||
|             localization::self_hosted_server_port[localization_language_index_] | ||||
|                 .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); | ||||
|  | ||||
|         ImGui::InputText("##signal_server_port_tmp_", signal_server_port_tmp_, | ||||
|                          IM_ARRAYSIZE(signal_server_port_tmp_)); | ||||
|       } | ||||
|  | ||||
|       ImGui::Separator(); | ||||
|  | ||||
|       { | ||||
|         settings_items_offset += settings_items_padding; | ||||
|         ImGui::SetCursorPosY(settings_items_offset + 2); | ||||
|         ImGui::Text("%s", localization::self_hosted_server_certificate_path | ||||
|                               [localization_language_index_] | ||||
|                                   .c_str()); | ||||
|  | ||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN); | ||||
|         } else { | ||||
|           ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN); | ||||
|         } | ||||
|         ImGui::SetCursorPosY(settings_items_offset); | ||||
|         ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH); | ||||
|  | ||||
|         ShowSimpleFileBrowser(); | ||||
|       } | ||||
|  | ||||
|       if (stream_window_inited_) { | ||||
|         ImGui::EndDisabled(); | ||||
|       } | ||||
|  | ||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||
|         ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN); | ||||
|       } else { | ||||
|         ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN); | ||||
|       } | ||||
|  | ||||
|       settings_items_offset += settings_items_padding + 10; | ||||
|       ImGui::SetCursorPosY(settings_items_offset); | ||||
|       ImGui::PopStyleVar(); | ||||
|  | ||||
|       // OK | ||||
|       if (ImGui::Button( | ||||
|               localization::ok[localization_language_index_].c_str())) { | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
|  | ||||
|         config_center_->SetServerHost(signal_server_ip_tmp_); | ||||
|         config_center_->SetServerPort(atoi(signal_server_port_tmp_)); | ||||
|         config_center_->SetCertFilePath(selected_file_); | ||||
|         strncpy(signal_server_ip_, signal_server_ip_tmp_, | ||||
|                 sizeof(signal_server_ip_) - 1); | ||||
|         signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0'; | ||||
|         strncpy(signal_server_port_, signal_server_port_tmp_, | ||||
|                 sizeof(signal_server_port_) - 1); | ||||
|         signal_server_port_[sizeof(signal_server_port_) - 1] = '\0'; | ||||
|         strncpy(cert_file_path_, selected_file_.c_str(), | ||||
|                 sizeof(cert_file_path_) - 1); | ||||
|         cert_file_path_[sizeof(cert_file_path_) - 1] = '\0'; | ||||
|  | ||||
|         self_hosted_server_config_window_pos_reset_ = true; | ||||
|       } | ||||
|  | ||||
|       ImGui::SameLine(); | ||||
|       // Cancel | ||||
|       if (ImGui::Button( | ||||
|               localization::cancel[localization_language_index_].c_str())) { | ||||
|         show_self_hosted_server_config_window_ = false; | ||||
|         self_hosted_server_config_window_pos_reset_ = true; | ||||
|  | ||||
|         strncpy(signal_server_ip_tmp_, signal_server_ip_, | ||||
|                 sizeof(signal_server_ip_tmp_) - 1); | ||||
|         signal_server_ip_tmp_[sizeof(signal_server_ip_tmp_) - 1] = '\0'; | ||||
|         strncpy(signal_server_port_tmp_, signal_server_port_, | ||||
|                 sizeof(signal_server_port_tmp_) - 1); | ||||
|         signal_server_port_tmp_[sizeof(signal_server_port_tmp_) - 1] = '\0'; | ||||
|         config_center_->SetServerHost(signal_server_ip_tmp_); | ||||
|         config_center_->SetServerPort(atoi(signal_server_port_tmp_)); | ||||
|         selected_file_.clear(); | ||||
|       } | ||||
|  | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|       ImGui::SetWindowFontScale(0.5f); | ||||
|       ImGui::End(); | ||||
|       ImGui::PopStyleVar(2); | ||||
|       ImGui::PopStyleColor(); | ||||
|       ImGui::SetWindowFontScale(1.0f); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										202
									
								
								src/gui/windows/stream_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,202 @@ | ||||
| #include "localization.h" | ||||
| #include "rd_log.h" | ||||
| #include "render.h" | ||||
|  | ||||
| void Render::DrawConnectionStatusText( | ||||
|     std::shared_ptr<SubStreamWindowProperties>& props) { | ||||
|   std::string text; | ||||
|   switch (props->connection_status_) { | ||||
|     case ConnectionStatus::Disconnected: | ||||
|       text = localization::p2p_disconnected[localization_language_index_]; | ||||
|       break; | ||||
|     case ConnectionStatus::Failed: | ||||
|       text = localization::p2p_failed[localization_language_index_]; | ||||
|       break; | ||||
|     case ConnectionStatus::Closed: | ||||
|       text = localization::p2p_closed[localization_language_index_]; | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   if (!text.empty()) { | ||||
|     ImVec2 size = ImGui::GetWindowSize(); | ||||
|     ImVec2 text_size = ImGui::CalcTextSize(text.c_str()); | ||||
|     ImGui::SetCursorPos( | ||||
|         ImVec2((size.x - text_size.x) * 0.5f, | ||||
|                (size.y - text_size.y - title_bar_height_) * 0.5f)); | ||||
|     ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Render::CloseTab(decltype(client_properties_)::iterator& it) { | ||||
|   CleanupPeer(it->second); | ||||
|   it = client_properties_.erase(it); | ||||
|   if (client_properties_.empty()) { | ||||
|     SDL_Event event; | ||||
|     event.type = SDL_EVENT_QUIT; | ||||
|     SDL_PushEvent(&event); | ||||
|   } | ||||
| } | ||||
|  | ||||
| int Render::StreamWindow() { | ||||
|   ImGui::SetNextWindowPos( | ||||
|       ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_), | ||||
|       ImGuiCond_Always); | ||||
|   ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_), | ||||
|                            ImGuiCond_Always); | ||||
|   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||
|   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); | ||||
|   ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); | ||||
|   ImGui::Begin("VideoBg", nullptr, | ||||
|                ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | | ||||
|                    ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||
|                    ImGuiWindowFlags_NoDocking); | ||||
|   ImGui::PopStyleColor(2); | ||||
|   ImGui::PopStyleVar(); | ||||
|  | ||||
|   ImGuiWindowFlags stream_window_flag = | ||||
|       ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration | | ||||
|       ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove; | ||||
|  | ||||
|   if (!fullscreen_button_pressed_) { | ||||
|     ImGui::SetNextWindowPos(ImVec2(20, 0), ImGuiCond_Always); | ||||
|     ImGui::SetNextWindowSize(ImVec2(0, 20), ImGuiCond_Always); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 8.0f)); | ||||
|     ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||
|     ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f)); | ||||
|     ImGui::Begin("TabBar", nullptr, | ||||
|                  ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | | ||||
|                      ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||
|                      ImGuiWindowFlags_NoDocking); | ||||
|     ImGui::PopStyleColor(); | ||||
|     ImGui::PopStyleVar(2); | ||||
|  | ||||
|     if (ImGui::BeginTabBar("StreamTabBar", | ||||
|                            ImGuiTabBarFlags_Reorderable | | ||||
|                                ImGuiTabBarFlags_AutoSelectNewTabs)) { | ||||
|       is_tab_bar_hovered_ = ImGui::IsWindowHovered(); | ||||
|  | ||||
|       for (auto it = client_properties_.begin(); | ||||
|            it != client_properties_.end();) { | ||||
|         auto& props = it->second; | ||||
|         if (!props->tab_opened_) { | ||||
|           CloseTab(it); | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         ImGui::SetWindowFontScale(0.6f); | ||||
|         std::string tab_label = | ||||
|             enable_srtp_ | ||||
|                 ? std::string(ICON_FA_SHIELD_HALVED) + " " + props->remote_id_ | ||||
|                 : props->remote_id_; | ||||
|         if (ImGui::BeginTabItem(tab_label.c_str(), &props->tab_opened_)) { | ||||
|           props->tab_selected_ = true; | ||||
|           ImGui::SetWindowFontScale(1.0f); | ||||
|  | ||||
|           ImGui::SetNextWindowSize( | ||||
|               ImVec2(stream_window_width_, stream_window_height_), | ||||
|               ImGuiCond_Always); | ||||
|           ImGui::SetNextWindowPos( | ||||
|               ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_), | ||||
|               ImGuiCond_Always); | ||||
|           ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||
|           ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||
|           ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f)); | ||||
|           ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag); | ||||
|           ImGui::PopStyleColor(); | ||||
|           ImGui::PopStyleVar(2); | ||||
|  | ||||
|           ImVec2 pos = ImGui::GetWindowPos(); | ||||
|           ImVec2 size = ImGui::GetWindowSize(); | ||||
|           props->render_window_x_ = pos.x; | ||||
|           props->render_window_y_ = pos.y; | ||||
|           props->render_window_width_ = size.x; | ||||
|           props->render_window_height_ = size.y; | ||||
|           UpdateRenderRect(); | ||||
|  | ||||
|           ControlWindow(props); | ||||
|  | ||||
|           focused_remote_id_ = props->remote_id_; | ||||
|  | ||||
|           if (!props->peer_) { | ||||
|             it = client_properties_.erase(it); | ||||
|             if (client_properties_.empty()) { | ||||
|               SDL_Event event; | ||||
|               event.type = SDL_EVENT_QUIT; | ||||
|               SDL_PushEvent(&event); | ||||
|             } | ||||
|           } else { | ||||
|             DrawConnectionStatusText(props); | ||||
|             ++it; | ||||
|           } | ||||
|  | ||||
|           ImGui::End(); | ||||
|           ImGui::EndTabItem(); | ||||
|         } else { | ||||
|           props->tab_selected_ = false; | ||||
|           ImGui::SetWindowFontScale(1.0f); | ||||
|           ++it; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       ImGui::EndTabBar(); | ||||
|     } | ||||
|  | ||||
|     ImGui::End();  // End TabBar | ||||
|   } else { | ||||
|     for (auto it = client_properties_.begin(); | ||||
|          it != client_properties_.end();) { | ||||
|       auto& props = it->second; | ||||
|       if (!props->tab_opened_) { | ||||
|         CloseTab(it); | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       if (props->tab_selected_) { | ||||
|         ImGui::SetNextWindowSize( | ||||
|             ImVec2(stream_window_width_, stream_window_height_), | ||||
|             ImGuiCond_Always); | ||||
|         ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||
|         ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||
|         ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f)); | ||||
|         ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag); | ||||
|         ImGui::PopStyleColor(); | ||||
|         ImGui::PopStyleVar(2); | ||||
|  | ||||
|         ImVec2 pos = ImGui::GetWindowPos(); | ||||
|         ImVec2 size = ImGui::GetWindowSize(); | ||||
|         props->render_window_x_ = pos.x; | ||||
|         props->render_window_y_ = pos.y; | ||||
|         props->render_window_width_ = size.x; | ||||
|         props->render_window_height_ = size.y; | ||||
|         UpdateRenderRect(); | ||||
|  | ||||
|         ControlWindow(props); | ||||
|         ImGui::End(); | ||||
|  | ||||
|         if (!props->peer_) { | ||||
|           fullscreen_button_pressed_ = false; | ||||
|           SDL_SetWindowFullscreen(stream_window_, false); | ||||
|           it = client_properties_.erase(it); | ||||
|           if (client_properties_.empty()) { | ||||
|             SDL_Event event; | ||||
|             event.type = SDL_EVENT_QUIT; | ||||
|             SDL_PushEvent(&event); | ||||
|           } | ||||
|         } else { | ||||
|           DrawConnectionStatusText(props); | ||||
|           ++it; | ||||
|         } | ||||
|       } else { | ||||
|         ++it; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // UpdateRenderRect(); | ||||
|   ImGui::End();  // End VideoBg | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
							
								
								
									
										127
									
								
								src/log/log.h
									
									
									
									
									
								
							
							
						
						| @@ -1,127 +0,0 @@ | ||||
| #ifndef _LOG_H_ | ||||
| #define _LOG_H_ | ||||
|  | ||||
| #include <chrono> | ||||
| #include <iomanip> | ||||
| #include <iostream> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
|  | ||||
| #include "spdlog/common.h" | ||||
| #include "spdlog/logger.h" | ||||
| #include "spdlog/sinks/base_sink.h" | ||||
| #include "spdlog/sinks/rotating_file_sink.h" | ||||
| #include "spdlog/sinks/stdout_color_sinks.h" | ||||
| #include "spdlog/spdlog.h" | ||||
|  | ||||
| using namespace std::chrono; | ||||
|  | ||||
| #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | ||||
|  | ||||
| // SPDLOG_TRACE(...) | ||||
| // SPDLOG_DEBUG(...) | ||||
| // SPDLOG_INFO(...) | ||||
| // SPDLOG_WARN(...) | ||||
| // SPDLOG_ERROR(...) | ||||
| // SPDLOG_CRITICAL(...) | ||||
|  | ||||
| #ifdef SIGNAL_LOGGER | ||||
| constexpr auto LOGGER_NAME = "siganl_server"; | ||||
| #else | ||||
| constexpr auto LOGGER_NAME = "remote_desk"; | ||||
| #endif | ||||
|  | ||||
| #define LOG_INFO(...)                                                         \ | ||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ | ||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ | ||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ | ||||
|     auto localTime = *std::gmtime(&timet);                                    \ | ||||
|     std::stringstream ss;                                                     \ | ||||
|     std::string filename;                                                     \ | ||||
|     ss << LOGGER_NAME;                                                        \ | ||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ | ||||
|     ss >> filename;                                                           \ | ||||
|     std::string path = "logs/" + filename;                                    \ | ||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ | ||||
|         path, 1048576 * 5, 3));                                               \ | ||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ | ||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ | ||||
|     combined_logger->flush_on(spdlog::level::info);                           \ | ||||
|     spdlog::register_logger(combined_logger);                                 \ | ||||
|     SPDLOG_LOGGER_INFO(combined_logger, __VA_ARGS__);                         \ | ||||
|   } else {                                                                    \ | ||||
|     SPDLOG_LOGGER_INFO(spdlog::get(LOGGER_NAME), __VA_ARGS__);                \ | ||||
|   } | ||||
|  | ||||
| #define LOG_WARN(...)                                                         \ | ||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ | ||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ | ||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ | ||||
|     auto localTime = *std::gmtime(&timet);                                    \ | ||||
|     std::stringstream ss;                                                     \ | ||||
|     std::string filename;                                                     \ | ||||
|     ss << LOGGER_NAME;                                                        \ | ||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ | ||||
|     ss >> filename;                                                           \ | ||||
|     std::string path = "logs/" + filename;                                    \ | ||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ | ||||
|         path, 1048576 * 5, 3));                                               \ | ||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ | ||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ | ||||
|     spdlog::register_logger(combined_logger);                                 \ | ||||
|     SPDLOG_LOGGER_WARN(combined_logger, __VA_ARGS__);                         \ | ||||
|   } else {                                                                    \ | ||||
|     SPDLOG_LOGGER_WARN(spdlog::get(LOGGER_NAME), __VA_ARGS__);                \ | ||||
|   } | ||||
|  | ||||
| #define LOG_ERROR(...)                                                        \ | ||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ | ||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ | ||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ | ||||
|     auto localTime = *std::gmtime(&timet);                                    \ | ||||
|     std::stringstream ss;                                                     \ | ||||
|     std::string filename;                                                     \ | ||||
|     ss << LOGGER_NAME;                                                        \ | ||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ | ||||
|     ss >> filename;                                                           \ | ||||
|     std::string path = "logs/" + filename;                                    \ | ||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ | ||||
|         path, 1048576 * 5, 3));                                               \ | ||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ | ||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ | ||||
|     spdlog::register_logger(combined_logger);                                 \ | ||||
|     SPDLOG_LOGGER_ERROR(combined_logger, __VA_ARGS__);                        \ | ||||
|   } else {                                                                    \ | ||||
|     SPDLOG_LOGGER_ERROR(spdlog::get(LOGGER_NAME), __VA_ARGS__);               \ | ||||
|   } | ||||
|  | ||||
| #define LOG_FATAL(...)                                                        \ | ||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ | ||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ | ||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ | ||||
|     auto localTime = *std::gmtime(&timet);                                    \ | ||||
|     std::stringstream ss;                                                     \ | ||||
|     std::string filename;                                                     \ | ||||
|     ss << LOGGER_NAME;                                                        \ | ||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ | ||||
|     ss >> filename;                                                           \ | ||||
|     std::string path = "logs/" + filename;                                    \ | ||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ | ||||
|         path, 1048576 * 5, 3));                                               \ | ||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ | ||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ | ||||
|     spdlog::register_logger(combined_logger);                                 \ | ||||
|     SPDLOG_LOGGER_CRITICAL(combined_logger, __VA_ARGS__);                     \ | ||||
|   } else {                                                                    \ | ||||
|     SPDLOG_LOGGER_CRITICAL(spdlog::get(LOGGER_NAME), __VA_ARGS__);            \ | ||||
|   } | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										62
									
								
								src/log/rd_log.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,62 @@ | ||||
| #include "rd_log.h" | ||||
|  | ||||
| #include <atomic> | ||||
| #include <filesystem> | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| std::string g_log_dir = "logs"; | ||||
| std::once_flag g_logger_once_flag; | ||||
| std::shared_ptr<spdlog::logger> g_logger; | ||||
| std::atomic<bool> g_logger_created{false}; | ||||
|  | ||||
| }  // namespace | ||||
|  | ||||
| void InitLogger(const std::string& log_dir) { | ||||
|   if (g_logger_created.load()) { | ||||
|     LOG_WARN( | ||||
|         "InitLogger called after logger initialized. Ignoring log_dir: {}, " | ||||
|         "using previous log_dir: {}", | ||||
|         log_dir, g_log_dir); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   g_log_dir = log_dir; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<spdlog::logger> get_logger() { | ||||
|   std::call_once(g_logger_once_flag, []() { | ||||
|     g_logger_created.store(true); | ||||
|  | ||||
|     std::error_code ec; | ||||
|     std::filesystem::create_directories(g_log_dir, ec); | ||||
|  | ||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8); | ||||
|     auto now_time = std::chrono::system_clock::to_time_t(now); | ||||
|  | ||||
|     std::tm tm_info; | ||||
| #ifdef _WIN32 | ||||
|     gmtime_s(&tm_info, &now_time); | ||||
| #else | ||||
|     gmtime_r(&now_time, &tm_info); | ||||
| #endif | ||||
|  | ||||
|     std::stringstream ss; | ||||
|     ss << LOGGER_NAME; | ||||
|     ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log"); | ||||
|  | ||||
|     std::string filename = g_log_dir + "/" + ss.str(); | ||||
|  | ||||
|     std::vector<spdlog::sink_ptr> sinks; | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); | ||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>( | ||||
|         filename, 5 * 1024 * 1024, 3)); | ||||
|  | ||||
|     g_logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(), | ||||
|                                                 sinks.end()); | ||||
|     g_logger->flush_on(spdlog::level::info); | ||||
|     spdlog::register_logger(g_logger); | ||||
|   }); | ||||
|  | ||||
|   return g_logger; | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/log/rd_log.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2025-07-21 | ||||
|  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _RD_LOG_H_ | ||||
| #define _RD_LOG_H_ | ||||
|  | ||||
| #include <chrono> | ||||
| #include <iomanip> | ||||
| #include <iostream> | ||||
| #include <memory> | ||||
| #include <mutex> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| #include "spdlog/common.h" | ||||
| #include "spdlog/logger.h" | ||||
| #include "spdlog/sinks/base_sink.h" | ||||
| #include "spdlog/sinks/rotating_file_sink.h" | ||||
| #include "spdlog/sinks/stdout_color_sinks.h" | ||||
| #include "spdlog/spdlog.h" | ||||
|  | ||||
| #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | ||||
|  | ||||
| constexpr auto LOGGER_NAME = "crossdesk"; | ||||
|  | ||||
| void InitLogger(const std::string& log_dir); | ||||
|  | ||||
| std::shared_ptr<spdlog::logger> get_logger(); | ||||
|  | ||||
| #define LOG_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__) | ||||
| #define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__) | ||||
| #define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__) | ||||
| #define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__) | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										91
									
								
								src/path_manager/path_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,91 @@ | ||||
| #include "path_manager.h" | ||||
|  | ||||
| #include <cstdlib> | ||||
|  | ||||
| PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {} | ||||
|  | ||||
| std::filesystem::path PathManager::GetConfigPath() { | ||||
| #ifdef _WIN32 | ||||
|   return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_; | ||||
| #elif __APPLE__ | ||||
|   return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; | ||||
| #else | ||||
|   return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| std::filesystem::path PathManager::GetCachePath() { | ||||
| #ifdef _WIN32 | ||||
|   return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache"; | ||||
| #elif __APPLE__ | ||||
|   return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; | ||||
| #else | ||||
|   return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| std::filesystem::path PathManager::GetLogPath() { | ||||
| #ifdef _WIN32 | ||||
|   return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs"; | ||||
| #elif __APPLE__ | ||||
|   return GetHome() + "/Library/Logs/" + app_name_; | ||||
| #else | ||||
|   return GetCachePath() / app_name_ / "logs"; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| std::filesystem::path PathManager::GetCertPath() { | ||||
| #ifdef _WIN32 | ||||
|   // %APPDATA%\AppName\Certs | ||||
|   return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_ / "certs"; | ||||
| #elif __APPLE__ | ||||
|   // $HOME/Library/Application Support/AppName/certs | ||||
|   return GetHome() + "/Library/Application Support/" + app_name_ + "/certs"; | ||||
| #else | ||||
|   // $XDG_CONFIG_HOME/AppName/certs | ||||
|   return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / | ||||
|          app_name_ / "certs"; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| bool PathManager::CreateDirectories(const std::filesystem::path& p) { | ||||
|   std::error_code ec; | ||||
|   bool created = std::filesystem::create_directories(p, ec); | ||||
|   if (ec) { | ||||
|     return false; | ||||
|   } | ||||
|   return created || std::filesystem::exists(p); | ||||
| } | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) { | ||||
|   PWSTR path = NULL; | ||||
|   if (SUCCEEDED(SHGetKnownFolderPath(id, 0, NULL, &path))) { | ||||
|     std::wstring wpath(path); | ||||
|     CoTaskMemFree(path); | ||||
|     return std::filesystem::path(wpath); | ||||
|   } | ||||
|   return {}; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| std::string PathManager::GetHome() { | ||||
|   if (const char* home = getenv("HOME")) { | ||||
|     return std::string(home); | ||||
|   } | ||||
| #ifdef _WIN32 | ||||
|   char path[MAX_PATH]; | ||||
|   if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path))) | ||||
|     return std::string(path); | ||||
| #endif | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var, | ||||
|                                                    const std::string& def) { | ||||
|   if (const char* val = getenv(env_var)) { | ||||
|     return std::filesystem::path(val); | ||||
|   } | ||||
|  | ||||
|   return std::filesystem::path(def); | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/path_manager/path_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2025-07-16 | ||||
|  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _PATH_MANAGER_H_ | ||||
| #define _PATH_MANAGER_H_ | ||||
|  | ||||
| #include <filesystem> | ||||
| #include <string> | ||||
| #ifdef _WIN32 | ||||
| #include <shlobj.h> | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| class PathManager { | ||||
|  public: | ||||
|   explicit PathManager(const std::string& app_name); | ||||
|  | ||||
|   std::filesystem::path GetConfigPath(); | ||||
|  | ||||
|   std::filesystem::path GetCachePath(); | ||||
|  | ||||
|   std::filesystem::path GetLogPath(); | ||||
|  | ||||
|   std::filesystem::path GetCertPath(); | ||||
|  | ||||
|   bool CreateDirectories(const std::filesystem::path& p); | ||||
|  | ||||
|  private: | ||||
| #ifdef _WIN32 | ||||
|   std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id); | ||||
| #endif | ||||
|  | ||||
|   std::string GetHome(); | ||||
|   std::filesystem::path GetEnvOrDefault(const char* env_var, | ||||
|                                         const std::string& def); | ||||
|  | ||||
|  private: | ||||
|   std::string app_name_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
| @@ -1,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 | ||||
							
								
								
									
										174
									
								
								src/screen_capturer/linux/screen_capturer_x11.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,174 @@ | ||||
| #include "screen_capturer_x11.h" | ||||
|  | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
|  | ||||
| #include "libyuv.h" | ||||
| #include "rd_log.h" | ||||
|  | ||||
| ScreenCapturerX11::ScreenCapturerX11() {} | ||||
|  | ||||
| ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); } | ||||
|  | ||||
| int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) { | ||||
|   display_ = XOpenDisplay(nullptr); | ||||
|   if (!display_) { | ||||
|     LOG_ERROR("Cannot connect to X server"); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   root_ = DefaultRootWindow(display_); | ||||
|   screen_res_ = XRRGetScreenResources(display_, root_); | ||||
|   if (!screen_res_) { | ||||
|     LOG_ERROR("Failed to get screen resources"); | ||||
|     XCloseDisplay(display_); | ||||
|     return 1; | ||||
|   } | ||||
|  | ||||
|   for (int i = 0; i < screen_res_->noutput; ++i) { | ||||
|     RROutput output = screen_res_->outputs[i]; | ||||
|     XRROutputInfo* output_info = | ||||
|         XRRGetOutputInfo(display_, screen_res_, output); | ||||
|  | ||||
|     if (output_info->connection == RR_Connected && output_info->crtc != 0) { | ||||
|       XRRCrtcInfo* crtc_info = | ||||
|           XRRGetCrtcInfo(display_, screen_res_, output_info->crtc); | ||||
|  | ||||
|       display_info_list_.push_back( | ||||
|           DisplayInfo((void*)display_, output_info->name, true, crtc_info->x, | ||||
|                       crtc_info->y, crtc_info->width, crtc_info->height)); | ||||
|  | ||||
|       XRRFreeCrtcInfo(crtc_info); | ||||
|     } | ||||
|  | ||||
|     if (output_info) { | ||||
|       XRRFreeOutputInfo(output_info); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   XWindowAttributes attr; | ||||
|   XGetWindowAttributes(display_, root_, &attr); | ||||
|  | ||||
|   width_ = attr.width; | ||||
|   height_ = attr.height; | ||||
|  | ||||
|   if (width_ % 2 != 0 || height_ % 2 != 0) { | ||||
|     LOG_ERROR("Width and height must be even numbers"); | ||||
|     return -2; | ||||
|   } | ||||
|  | ||||
|   fps_ = fps; | ||||
|   callback_ = cb; | ||||
|  | ||||
|   y_plane_.resize(width_ * height_); | ||||
|   uv_plane_.resize((width_ / 2) * (height_ / 2) * 2); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerX11::Destroy() { | ||||
|   Stop(); | ||||
|  | ||||
|   y_plane_.clear(); | ||||
|   uv_plane_.clear(); | ||||
|  | ||||
|   if (screen_res_) { | ||||
|     XRRFreeScreenResources(screen_res_); | ||||
|     screen_res_ = nullptr; | ||||
|   } | ||||
|  | ||||
|   if (display_) { | ||||
|     XCloseDisplay(display_); | ||||
|     display_ = nullptr; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerX11::Start() { | ||||
|   if (running_) return 0; | ||||
|   running_ = true; | ||||
|   paused_ = false; | ||||
|   thread_ = std::thread([this]() { | ||||
|     while (running_) { | ||||
|       if (!paused_) OnFrame(); | ||||
|     } | ||||
|   }); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerX11::Stop() { | ||||
|   if (!running_) return 0; | ||||
|   running_ = false; | ||||
|   if (thread_.joinable()) thread_.join(); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerX11::Pause(int monitor_index) { | ||||
|   paused_ = true; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerX11::Resume(int monitor_index) { | ||||
|   paused_ = false; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerX11::SwitchTo(int monitor_index) { | ||||
|   monitor_index_ = monitor_index; | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() { | ||||
|   return display_info_list_; | ||||
| } | ||||
|  | ||||
| void ScreenCapturerX11::OnFrame() { | ||||
|   if (!display_) { | ||||
|     LOG_ERROR("Display is not initialized"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (monitor_index_ < 0 || monitor_index_ >= display_info_list_.size()) { | ||||
|     LOG_ERROR("Invalid monitor index: {}", monitor_index_.load()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   left_ = display_info_list_[monitor_index_].left; | ||||
|   top_ = display_info_list_[monitor_index_].top; | ||||
|   width_ = display_info_list_[monitor_index_].width; | ||||
|   height_ = display_info_list_[monitor_index_].height; | ||||
|  | ||||
|   XImage* image = XGetImage(display_, root_, left_, top_, width_, height_, | ||||
|                             AllPlanes, ZPixmap); | ||||
|   if (!image) return; | ||||
|  | ||||
|   bool needs_copy = image->bytes_per_line != width_ * 4; | ||||
|   std::vector<uint8_t> argb_buf; | ||||
|   uint8_t* src_argb = nullptr; | ||||
|  | ||||
|   if (needs_copy) { | ||||
|     argb_buf.resize(width_ * height_ * 4); | ||||
|     for (int y = 0; y < height_; ++y) { | ||||
|       memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line, | ||||
|              width_ * 4); | ||||
|     } | ||||
|     src_argb = argb_buf.data(); | ||||
|   } else { | ||||
|     src_argb = reinterpret_cast<uint8_t*>(image->data); | ||||
|   } | ||||
|  | ||||
|   libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_, | ||||
|                      uv_plane_.data(), width_, width_, height_); | ||||
|  | ||||
|   std::vector<uint8_t> nv12; | ||||
|   nv12.reserve(y_plane_.size() + uv_plane_.size()); | ||||
|   nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end()); | ||||
|   nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end()); | ||||
|  | ||||
|   if (callback_) { | ||||
|     callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_, | ||||
|               display_info_list_[monitor_index_].name.c_str()); | ||||
|   } | ||||
|  | ||||
|   XDestroyImage(image); | ||||
| } | ||||
							
								
								
									
										64
									
								
								src/screen_capturer/linux/screen_capturer_x11.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,64 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2025-05-07 | ||||
|  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _SCREEN_CAPTURER_X11_H_ | ||||
| #define _SCREEN_CAPTURER_X11_H_ | ||||
|  | ||||
| #include <X11/Xlib.h> | ||||
| #include <X11/Xutil.h> | ||||
| #include <X11/extensions/Xrandr.h> | ||||
|  | ||||
| #include <atomic> | ||||
| #include <cstring> | ||||
| #include <functional> | ||||
| #include <iostream> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
|  | ||||
| #include "screen_capturer.h" | ||||
|  | ||||
| class ScreenCapturerX11 : public ScreenCapturer { | ||||
|  public: | ||||
|   ScreenCapturerX11(); | ||||
|   ~ScreenCapturerX11(); | ||||
|  | ||||
|  public: | ||||
|   int Init(const int fps, cb_desktop_data cb) override; | ||||
|   int Destroy() override; | ||||
|   int Start() override; | ||||
|   int Stop() override; | ||||
|  | ||||
|   int Pause(int monitor_index) override; | ||||
|   int Resume(int monitor_index) override; | ||||
|  | ||||
|   int SwitchTo(int monitor_index) override; | ||||
|  | ||||
|   std::vector<DisplayInfo> GetDisplayInfoList() override; | ||||
|  | ||||
|   void OnFrame(); | ||||
|  | ||||
|  private: | ||||
|   Display* display_ = nullptr; | ||||
|   Window root_ = 0; | ||||
|   XRRScreenResources* screen_res_ = nullptr; | ||||
|   int left_ = 0; | ||||
|   int top_ = 0; | ||||
|   int width_ = 0; | ||||
|   int height_ = 0; | ||||
|   std::thread thread_; | ||||
|   std::atomic<bool> running_{false}; | ||||
|   std::atomic<bool> paused_{false}; | ||||
|   std::atomic<int> monitor_index_{0}; | ||||
|   int fps_ = 60; | ||||
|   cb_desktop_data callback_; | ||||
|   std::vector<DisplayInfo> display_info_list_; | ||||
|  | ||||
|   // 缓冲区 | ||||
|   std::vector<uint8_t> y_plane_; | ||||
|   std::vector<uint8_t> uv_plane_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										73
									
								
								src/screen_capturer/macosx/screen_capturer_sck.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,73 @@ | ||||
| #include "screen_capturer_sck.h" | ||||
|  | ||||
| #include "rd_log.h" | ||||
|  | ||||
| ScreenCapturerSck::ScreenCapturerSck() {} | ||||
| ScreenCapturerSck::~ScreenCapturerSck() {} | ||||
|  | ||||
| int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) { | ||||
|   if (cb) { | ||||
|     on_data_ = cb; | ||||
|   } else { | ||||
|     LOG_ERROR("cb is null"); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   screen_capturer_sck_impl_ = CreateScreenCapturerSck(); | ||||
|   screen_capturer_sck_impl_->Init(fps, on_data_); | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSck::Destroy() { | ||||
|   if (screen_capturer_sck_impl_) { | ||||
|     screen_capturer_sck_impl_->Destroy(); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSck::Start() { | ||||
|   screen_capturer_sck_impl_->Start(); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSck::Stop() { | ||||
|   if (screen_capturer_sck_impl_) { | ||||
|     screen_capturer_sck_impl_->Stop(); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSck::Pause(int monitor_index) { | ||||
|   if (screen_capturer_sck_impl_) { | ||||
|     return screen_capturer_sck_impl_->Pause(monitor_index); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSck::Resume(int monitor_index) { | ||||
|   if (screen_capturer_sck_impl_) { | ||||
|     return screen_capturer_sck_impl_->Resume(monitor_index); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSck::SwitchTo(int monitor_index) { | ||||
|   if (screen_capturer_sck_impl_) { | ||||
|     return screen_capturer_sck_impl_->SwitchTo(monitor_index); | ||||
|   } | ||||
|  | ||||
|   return -1; | ||||
| } | ||||
|  | ||||
| std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() { | ||||
|   if (screen_capturer_sck_impl_) { | ||||
|     return screen_capturer_sck_impl_->GetDisplayInfoList(); | ||||
|   } | ||||
|  | ||||
|   return std::vector<DisplayInfo>(); | ||||
| } | ||||
|  | ||||
| void ScreenCapturerSck::OnFrame() {} | ||||
|  | ||||
| void ScreenCapturerSck::CleanUp() {} | ||||
							
								
								
									
										59
									
								
								src/screen_capturer/macosx/screen_capturer_sck.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2024-10-17 | ||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _SCREEN_CAPTURER_SCK_H_ | ||||
| #define _SCREEN_CAPTURER_SCK_H_ | ||||
|  | ||||
| #include <atomic> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| #include <vector> | ||||
|  | ||||
| #include "screen_capturer.h" | ||||
|  | ||||
| class ScreenCapturerSck : public ScreenCapturer { | ||||
|  public: | ||||
|   ScreenCapturerSck(); | ||||
|   ~ScreenCapturerSck(); | ||||
|  | ||||
|  public: | ||||
|   int Init(const int fps, cb_desktop_data cb) override; | ||||
|   int Destroy() override; | ||||
|   int Start() override; | ||||
|   int Stop() override; | ||||
|  | ||||
|   int Pause(int monitor_index) override; | ||||
|   int Resume(int monitor_index) override; | ||||
|  | ||||
|   int SwitchTo(int monitor_index) override; | ||||
|  | ||||
|   std::vector<DisplayInfo> GetDisplayInfoList() override; | ||||
|  | ||||
|   void OnFrame(); | ||||
|  | ||||
|  protected: | ||||
|   void CleanUp(); | ||||
|  | ||||
|  private: | ||||
|   std::unique_ptr<ScreenCapturer> CreateScreenCapturerSck(); | ||||
|  | ||||
|  private: | ||||
|   int _fps; | ||||
|   cb_desktop_data on_data_; | ||||
|   unsigned char* nv12_frame_ = nullptr; | ||||
|   bool inited_ = false; | ||||
|  | ||||
|   // thread | ||||
|   std::thread capture_thread_; | ||||
|   std::atomic_bool running_; | ||||
|  | ||||
|  private: | ||||
|   std::unique_ptr<ScreenCapturer> screen_capturer_sck_impl_; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										488
									
								
								src/screen_capturer/macosx/screen_capturer_sck_impl.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,488 @@ | ||||
| /* | ||||
|  *  Copyright (c) 2024 The WebRTC project authors. All Rights Reserved. | ||||
|  * | ||||
|  *  Use of this source code is governed by a BSD-style license | ||||
|  *  that can be found in the LICENSE file in the root of the source | ||||
|  *  tree. An additional intellectual property rights grant can be found | ||||
|  *  in the file PATENTS.  All contributing project authors may | ||||
|  *  be found in the AUTHORS file in the root of the source tree. | ||||
|  */ | ||||
|  | ||||
| #include "screen_capturer_sck.h" | ||||
|  | ||||
| #include <ApplicationServices/ApplicationServices.h> | ||||
| #include <CoreGraphics/CoreGraphics.h> | ||||
| #include <IOKit/IOKitLib.h> | ||||
| #include <IOKit/graphics/IOGraphicsLib.h> | ||||
| #include <IOSurface/IOSurface.h> | ||||
| #include <ScreenCaptureKit/ScreenCaptureKit.h> | ||||
| #include <atomic> | ||||
| #include <mutex> | ||||
| #include <vector> | ||||
| #include "display_info.h" | ||||
| #include "rd_log.h" | ||||
|  | ||||
| static const int kFullDesktopScreenId = -1; | ||||
| class ScreenCapturerSckImpl; | ||||
|  | ||||
| // The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture | ||||
| // was reported to be broken before macOS 13 - see http://crbug.com/40234870. | ||||
| // Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were | ||||
| // introduced in macOS 14. | ||||
| API_AVAILABLE(macos(14.0)) | ||||
| @interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput> | ||||
|  | ||||
| - (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer; | ||||
|  | ||||
| - (void)onShareableContentCreated:(SCShareableContent *)content; | ||||
|  | ||||
| // Called just before the capturer is destroyed. This avoids a dangling pointer, | ||||
| // and prevents any new calls into a deleted capturer. If any method-call on the | ||||
| // capturer is currently running on a different thread, this blocks until it | ||||
| // completes. | ||||
| - (void)releaseCapturer; | ||||
| @end | ||||
|  | ||||
| class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer { | ||||
|  public: | ||||
|   explicit ScreenCapturerSckImpl(); | ||||
|  | ||||
|   ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete; | ||||
|   ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete; | ||||
|   ~ScreenCapturerSckImpl(); | ||||
|  | ||||
|  public: | ||||
|   int Init(const int fps, cb_desktop_data cb) override; | ||||
|  | ||||
|   int Start() override; | ||||
|  | ||||
|   int SwitchTo(int monitor_index) override; | ||||
|  | ||||
|   int Destroy() override; | ||||
|  | ||||
|   int Stop() override; | ||||
|  | ||||
|   int Pause(int monitor_index) override { return 0; } | ||||
|  | ||||
|   int Resume(int monitor_index) override { return 0; } | ||||
|  | ||||
|   std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; } | ||||
|  | ||||
|  private: | ||||
|   std::vector<DisplayInfo> display_info_list_; | ||||
|   std::map<int, CGDirectDisplayID> display_id_map_; | ||||
|   std::map<CGDirectDisplayID, int> display_id_map_reverse_; | ||||
|   std::map<CGDirectDisplayID, std::string> display_id_name_map_; | ||||
|   unsigned char *nv12_frame_ = nullptr; | ||||
|   int width_ = 0; | ||||
|   int height_ = 0; | ||||
|   int fps_ = 60; | ||||
|  | ||||
|  public: | ||||
|   // Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be | ||||
|   // nil if an error occurred. May run on an arbitrary thread. | ||||
|   void OnShareableContentCreated(SCShareableContent *content); | ||||
|   // Called by SckHelper to notify of a newly captured frame. May run on an arbitrary thread. | ||||
|   // void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment); | ||||
|   void OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, CFDictionaryRef attachment); | ||||
|  | ||||
|  private: | ||||
|   // Called when starting the capturer or the configuration has changed (either from a | ||||
|   // SwitchTo() call, or the screen-resolution has changed). This tells SCK to fetch new | ||||
|   // shareable content, and the completion-handler will either start a new stream, or reconfigure | ||||
|   // the existing stream. Runs on the caller's thread. | ||||
|   void StartOrReconfigureCapturer(); | ||||
|   // Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++ | ||||
|   // object. The helper may outlive this C++ instance, if a completion-handler is passed to | ||||
|   // ScreenCaptureKit APIs and the C++ object is deleted before the handler executes. | ||||
|   SckHelper *__strong helper_; | ||||
|   // Callback for returning captured frames, or errors, to the caller. Only used on the caller's | ||||
|   // thread. | ||||
|   cb_desktop_data _on_data = nullptr; | ||||
|   // Signals that a permanent error occurred. This may be set on any thread, and is read by | ||||
|   // CaptureFrame() which runs on the caller's thread. | ||||
|   std::atomic<bool> permanent_error_ = false; | ||||
|   // Guards some variables that may be accessed on different threads. | ||||
|   std::mutex lock_; | ||||
|   // Provides captured desktop frames. | ||||
|   SCStream *__strong stream_; | ||||
|   // Currently selected display, or 0 if the full desktop is selected. This capturer does not | ||||
|   // support full-desktop capture, and will fall back to the first display. | ||||
|   CGDirectDisplayID current_display_ = 0; | ||||
| }; | ||||
|  | ||||
| std::string GetDisplayName(CGDirectDisplayID display_id) { | ||||
|   io_iterator_t iter; | ||||
|   io_service_t serv = 0, matched_serv = 0; | ||||
|  | ||||
|   CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect"); | ||||
|   if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) { | ||||
|     return ""; | ||||
|   } | ||||
|  | ||||
|   while ((serv = IOIteratorNext(iter)) != 0) { | ||||
|     CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName); | ||||
|     if (info) { | ||||
|       CFNumberRef vendorID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayVendorID)); | ||||
|       CFNumberRef productID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayProductID)); | ||||
|       uint32_t vID = 0, pID = 0; | ||||
|       if (vendorID && productID && CFNumberGetValue(vendorID, kCFNumberIntType, &vID) && | ||||
|           CFNumberGetValue(productID, kCFNumberIntType, &pID) && | ||||
|           vID == CGDisplayVendorNumber(display_id) && pID == CGDisplayModelNumber(display_id)) { | ||||
|         matched_serv = serv; | ||||
|         CFRelease(info); | ||||
|         break; | ||||
|       } | ||||
|       CFRelease(info); | ||||
|     } | ||||
|     IOObjectRelease(serv); | ||||
|   } | ||||
|   IOObjectRelease(iter); | ||||
|  | ||||
|   if (!matched_serv) return ""; | ||||
|  | ||||
|   CFDictionaryRef display_info = | ||||
|       IODisplayCreateInfoDictionary(matched_serv, kIODisplayOnlyPreferredName); | ||||
|   IOObjectRelease(matched_serv); | ||||
|   if (!display_info) return ""; | ||||
|  | ||||
|   CFDictionaryRef product_name_dict = | ||||
|       (CFDictionaryRef)CFDictionaryGetValue(display_info, CFSTR(kDisplayProductName)); | ||||
|   std::string result; | ||||
|   if (product_name_dict) { | ||||
|     CFIndex count = CFDictionaryGetCount(product_name_dict); | ||||
|     if (count > 0) { | ||||
|       std::vector<const void *> keys(count); | ||||
|       std::vector<const void *> values(count); | ||||
|       CFDictionaryGetKeysAndValues(product_name_dict, keys.data(), values.data()); | ||||
|       CFStringRef name_ref = (CFStringRef)values[0]; | ||||
|       if (name_ref) { | ||||
|         CFIndex maxSize = | ||||
|             CFStringGetMaximumSizeForEncoding(CFStringGetLength(name_ref), kCFStringEncodingUTF8) + | ||||
|             1; | ||||
|         std::vector<char> buffer(maxSize); | ||||
|         if (CFStringGetCString(name_ref, buffer.data(), buffer.size(), kCFStringEncodingUTF8)) { | ||||
|           result = buffer.data(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   CFRelease(display_info); | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| ScreenCapturerSckImpl::ScreenCapturerSckImpl() { | ||||
|   helper_ = [[SckHelper alloc] initWithCapturer:this]; | ||||
| } | ||||
|  | ||||
| ScreenCapturerSckImpl::~ScreenCapturerSckImpl() { | ||||
|   display_info_list_.clear(); | ||||
|   display_id_map_.clear(); | ||||
|   display_id_map_reverse_.clear(); | ||||
|   display_id_name_map_.clear(); | ||||
|  | ||||
|   if (nv12_frame_) { | ||||
|     delete[] nv12_frame_; | ||||
|     nv12_frame_ = nullptr; | ||||
|   } | ||||
|  | ||||
|   [stream_ stopCaptureWithCompletionHandler:nil]; | ||||
|   [helper_ releaseCapturer]; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) { | ||||
|   _on_data = cb; | ||||
|  | ||||
|   dispatch_semaphore_t sema = dispatch_semaphore_create(0); | ||||
|   __block SCShareableContent *content = nil; | ||||
|  | ||||
|   [SCShareableContent | ||||
|       getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) { | ||||
|         if (error) { | ||||
|           NSLog(@"Failed to get shareable content: %@", error); | ||||
|         } else { | ||||
|           content = result; | ||||
|         } | ||||
|         dispatch_semaphore_signal(sema); | ||||
|       }]; | ||||
|   dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | ||||
|  | ||||
|   if (!content || content.displays.count == 0) { | ||||
|     LOG_ERROR("Failed to get display info"); | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   CGDirectDisplayID displays[10]; | ||||
|   uint32_t count; | ||||
|   CGGetActiveDisplayList(10, displays, &count); | ||||
|  | ||||
|   int unnamed_count = 1; | ||||
|   for (SCDisplay *display in content.displays) { | ||||
|     CGDirectDisplayID display_id = display.displayID; | ||||
|     CGRect bounds = CGDisplayBounds(display_id); | ||||
|     bool is_primary = CGDisplayIsMain(display_id); | ||||
|  | ||||
|     std::string name; | ||||
|     name = GetDisplayName(display_id); | ||||
|  | ||||
|     if (name.empty()) { | ||||
|       name = "Display " + std::to_string(unnamed_count++); | ||||
|     } | ||||
|  | ||||
|     DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary, | ||||
|                      static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y), | ||||
|                      static_cast<int>(bounds.origin.x + bounds.size.width), | ||||
|                      static_cast<int>(bounds.origin.y + bounds.size.height)); | ||||
|  | ||||
|     display_info_list_.push_back(info); | ||||
|     display_id_map_[display_info_list_.size() - 1] = display_id; | ||||
|     display_id_map_reverse_[display_id] = display_info_list_.size() - 1; | ||||
|     display_id_name_map_[display_id] = name; | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSckImpl::Start() { | ||||
|   StartOrReconfigureCapturer(); | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSckImpl::SwitchTo(int monitor_index) { | ||||
|   if (stream_) { | ||||
|     [stream_ stopCaptureWithCompletionHandler:^(NSError *error) { | ||||
|       std::lock_guard<std::mutex> lock(lock_); | ||||
|       stream_ = nil; | ||||
|       current_display_ = display_id_map_[monitor_index]; | ||||
|       StartOrReconfigureCapturer(); | ||||
|     }]; | ||||
|   } else { | ||||
|     current_display_ = display_id_map_[monitor_index]; | ||||
|     StartOrReconfigureCapturer(); | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSckImpl::Destroy() { | ||||
|   std::lock_guard<std::mutex> lock(lock_); | ||||
|   if (stream_) { | ||||
|     LOG_INFO("Destroying stream"); | ||||
|     [stream_ stopCaptureWithCompletionHandler:nil]; | ||||
|     stream_ = nil; | ||||
|   } | ||||
|   current_display_ = 0; | ||||
|   permanent_error_ = false; | ||||
|   _on_data = nullptr; | ||||
|   [helper_ releaseCapturer]; | ||||
|   helper_ = nil; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| int ScreenCapturerSckImpl::Stop() { | ||||
|   std::lock_guard<std::mutex> lock(lock_); | ||||
|   if (stream_) { | ||||
|     LOG_INFO("Stopping stream"); | ||||
|     [stream_ stopCaptureWithCompletionHandler:nil]; | ||||
|     stream_ = nil; | ||||
|   } | ||||
|   current_display_ = 0; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *content) { | ||||
|   if (!content) { | ||||
|     LOG_ERROR("getShareableContent failed"); | ||||
|     permanent_error_ = true; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!content.displays.count) { | ||||
|     LOG_ERROR("getShareableContent returned no displays"); | ||||
|     permanent_error_ = true; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   SCDisplay *captured_display; | ||||
|   { | ||||
|     std::lock_guard<std::mutex> lock(lock_); | ||||
|     for (SCDisplay *display in content.displays) { | ||||
|       if (current_display_ == display.displayID) { | ||||
|         LOG_WARN("current display: {}, name: {}", current_display_, | ||||
|                  display_id_name_map_[current_display_]); | ||||
|         captured_display = display; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     if (!captured_display) { | ||||
|       captured_display = content.displays.firstObject; | ||||
|       current_display_ = captured_display.displayID; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display | ||||
|                                                     excludingWindows:@[]]; | ||||
|   SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init]; | ||||
|   config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; | ||||
|   config.showsCursor = false; | ||||
|   config.width = filter.contentRect.size.width * filter.pointPixelScale; | ||||
|   config.height = filter.contentRect.size.height * filter.pointPixelScale; | ||||
|   config.captureResolution = SCCaptureResolutionAutomatic; | ||||
|   config.minimumFrameInterval = CMTimeMake(1, fps_); | ||||
|  | ||||
|   std::lock_guard<std::mutex> lock(lock_); | ||||
|  | ||||
|   if (stream_) { | ||||
|     LOG_INFO("Updating stream configuration"); | ||||
|     [stream_ updateContentFilter:filter completionHandler:nil]; | ||||
|     [stream_ updateConfiguration:config completionHandler:nil]; | ||||
|   } else { | ||||
|     stream_ = [[SCStream alloc] initWithFilter:filter configuration:config delegate:helper_]; | ||||
|  | ||||
|     // TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for | ||||
|     // best performance. | ||||
|     NSError *add_stream_output_error; | ||||
|     dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL); | ||||
|     bool add_stream_output_result = [stream_ addStreamOutput:helper_ | ||||
|                                                         type:SCStreamOutputTypeScreen | ||||
|                                           sampleHandlerQueue:queue | ||||
|                                                        error:&add_stream_output_error]; | ||||
|  | ||||
|     if (!add_stream_output_result) { | ||||
|       stream_ = nil; | ||||
|       LOG_ERROR("addStreamOutput failed"); | ||||
|       permanent_error_ = true; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     auto handler = ^(NSError *error) { | ||||
|       if (error) { | ||||
|         // It should be safe to access `this` here, because the C++ destructor | ||||
|         // calls stopCaptureWithCompletionHandler on the stream, which cancels | ||||
|         // this handler. | ||||
|         permanent_error_ = true; | ||||
|         LOG_ERROR("startCaptureWithCompletionHandler failed"); | ||||
|       } else { | ||||
|         LOG_INFO("Capture started"); | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     [stream_ startCaptureWithCompletionHandler:handler]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, | ||||
|                                                CFDictionaryRef attachment) { | ||||
|   size_t width = CVPixelBufferGetWidth(pixelBuffer); | ||||
|   size_t height = CVPixelBufferGetHeight(pixelBuffer); | ||||
|  | ||||
|   CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); | ||||
|   if (status != kCVReturnSuccess) { | ||||
|     LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   size_t required_size = width * height * 3 / 2; | ||||
|   if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) { | ||||
|     delete[] nv12_frame_; | ||||
|     nv12_frame_ = new unsigned char[required_size]; | ||||
|     width_ = width; | ||||
|     height_ = height; | ||||
|   } | ||||
|  | ||||
|   void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); | ||||
|   size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); | ||||
|  | ||||
|   void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); | ||||
|   size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); | ||||
|  | ||||
|   unsigned char *dst_y = nv12_frame_; | ||||
|   for (size_t row = 0; row < height; ++row) { | ||||
|     memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width); | ||||
|   } | ||||
|  | ||||
|   unsigned char *dst_uv = nv12_frame_ + width * height; | ||||
|   for (size_t row = 0; row < height / 2; ++row) { | ||||
|     memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width); | ||||
|   } | ||||
|  | ||||
|   _on_data(nv12_frame_, width * height * 3 / 2, width, height, | ||||
|            display_id_name_map_[current_display_].c_str()); | ||||
|  | ||||
|   CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); | ||||
| } | ||||
|  | ||||
| void ScreenCapturerSckImpl::StartOrReconfigureCapturer() { | ||||
|   // The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_` | ||||
|   // inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is | ||||
|   // deleted before the block is executed. | ||||
|   SckHelper *local_helper = helper_; | ||||
|   auto handler = ^(SCShareableContent *content, NSError *error) { | ||||
|     [local_helper onShareableContentCreated:content]; | ||||
|   }; | ||||
|   [SCShareableContent getShareableContentWithCompletionHandler:handler]; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() { | ||||
|   return std::make_unique<ScreenCapturerSckImpl>(); | ||||
| } | ||||
|  | ||||
| @implementation SckHelper { | ||||
|   // This lock is to prevent the capturer being destroyed while an instance | ||||
|   // method is still running on another thread. | ||||
|   std::mutex _capturer_lock; | ||||
|   ScreenCapturerSckImpl *_capturer; | ||||
| } | ||||
|  | ||||
| - (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer { | ||||
|   self = [super init]; | ||||
|   if (self) { | ||||
|     _capturer = capturer; | ||||
|   } | ||||
|   return self; | ||||
| } | ||||
|  | ||||
| - (void)onShareableContentCreated:(SCShareableContent *)content { | ||||
|   std::lock_guard<std::mutex> lock(_capturer_lock); | ||||
|   if (_capturer) { | ||||
|     _capturer->OnShareableContentCreated(content); | ||||
|   } else { | ||||
|     LOG_ERROR("Invalid capturer"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| - (void)stream:(SCStream *)stream | ||||
|     didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer | ||||
|                    ofType:(SCStreamOutputType)type { | ||||
|   CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); | ||||
|   if (!pixelBuffer) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   CFRetain(pixelBuffer); | ||||
|  | ||||
|   CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false); | ||||
|   if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) { | ||||
|     LOG_ERROR("Discarding frame with no attachments"); | ||||
|     CFRelease(pixelBuffer); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   CFDictionaryRef attachment = | ||||
|       static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0)); | ||||
|  | ||||
|   std::lock_guard<std::mutex> lock(_capturer_lock); | ||||
|   if (_capturer) { | ||||
|     _capturer->OnNewCVPixelBuffer(pixelBuffer, attachment); | ||||
|   } | ||||
|  | ||||
|   CFRelease(pixelBuffer); | ||||
| } | ||||
|  | ||||
| - (void)releaseCapturer { | ||||
|   std::lock_guard<std::mutex> lock(_capturer_lock); | ||||
|   _capturer = nullptr; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										34
									
								
								src/screen_capturer/screen_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-15 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _SCREEN_CAPTURER_H_ | ||||
| #define _SCREEN_CAPTURER_H_ | ||||
|  | ||||
| #include <functional> | ||||
|  | ||||
| #include "display_info.h" | ||||
|  | ||||
| class ScreenCapturer { | ||||
|  public: | ||||
|   typedef std::function<void(unsigned char*, int, int, int, const char*)> | ||||
|       cb_desktop_data; | ||||
|  | ||||
|  public: | ||||
|   virtual ~ScreenCapturer() {} | ||||
|  | ||||
|  public: | ||||
|   virtual int Init(const int fps, cb_desktop_data cb) = 0; | ||||
|   virtual int Destroy() = 0; | ||||
|   virtual int Start() = 0; | ||||
|   virtual int Stop() = 0; | ||||
|   virtual int Pause(int monitor_index) = 0; | ||||
|   virtual int Resume(int monitor_index) = 0; | ||||
|  | ||||
|   virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0; | ||||
|   virtual int SwitchTo(int monitor_index) = 0; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										38
									
								
								src/screen_capturer/screen_capturer_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| /* | ||||
|  * @Author: DI JUNKUN | ||||
|  * @Date: 2023-12-15 | ||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. | ||||
|  */ | ||||
|  | ||||
| #ifndef _SCREEN_CAPTURER_FACTORY_H_ | ||||
| #define _SCREEN_CAPTURER_FACTORY_H_ | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #include "screen_capturer_wgc.h" | ||||
| #elif __linux__ | ||||
| #include "screen_capturer_x11.h" | ||||
| #elif __APPLE__ | ||||
| // #include "screen_capturer_avf.h" | ||||
| #include "screen_capturer_sck.h" | ||||
| #endif | ||||
|  | ||||
| class ScreenCapturerFactory { | ||||
|  public: | ||||
|   virtual ~ScreenCapturerFactory() {} | ||||
|  | ||||
|  public: | ||||
|   ScreenCapturer* Create() { | ||||
| #ifdef _WIN32 | ||||
|     return new ScreenCapturerWgc(); | ||||
| #elif __linux__ | ||||
|     return new ScreenCapturerX11(); | ||||
| #elif __APPLE__ | ||||
|     // return new ScreenCapturerAvf(); | ||||
|     return new ScreenCapturerSck(); | ||||
| #else | ||||
|     return nullptr; | ||||
| #endif | ||||
|   } | ||||
| }; | ||||
|  | ||||
| #endif | ||||