mirror of
				https://github.com/kunkundi/crossdesk.git
				synced 2025-10-29 20:40:12 +08:00 
			
		
		
		
	Compare commits
	
		
			289 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3b00fdef71 | ||
|  | f2664daaec | ||
|  | 1a1bbe3e4d | ||
|  | 4c898c5f07 | ||
|  | 5d704edbc8 | ||
|  | b23038bac6 | ||
|  | 147b9b2ddc | ||
|  | 1327b995f2 | ||
|  | e8d7ec8daf | ||
|  | 3bf68a396f | ||
|  | 29077fad5e | ||
|  | 69367bdadf | ||
|  | f650b5a6ef | ||
|  | 75021b74ef | ||
|  | 205421621b | ||
|  | 4bb5607702 | ||
|  | 78c54136e2 | ||
|  | 8fe8f4fd7e | ||
|  | c13e2613b6 | ||
|  | e52ec6cde2 | ||
|  | ad6a24b230 | ||
|  | ad60815d83 | ||
|  | c47a90bf9c | ||
|  | 3901e26c37 | ||
|  | 41c27139a0 | ||
|  | a31ca21ef4 | ||
|  | 57939ccd00 | ||
|  | fc14f7b9c6 | ||
|  | cfcf6dd9cb | ||
|  | 854c3a72c7 | ||
|  | 8004d25ca3 | ||
|  | b04dfd188a | ||
|  | 383ace2493 | ||
|  | 094204361c | ||
|  | d72c6d9df7 | ||
|  | 05d73ebe9a | ||
|  | a8b5e934b8 | ||
|  | 920677a433 | ||
|  | b86d3d42ee | ||
|  | 818dab764f | ||
|  | 7ea26854af | ||
|  | f34b55b88a | ||
|  | 67168f7735 | ||
|  | 6e69c37056 | ||
|  | 8f5dd21e75 | ||
|  | e2dfeb1186 | ||
|  | 96c7d3174b | ||
|  | 97f6c18296 | ||
|  | 9138ca771f | ||
|  | abc6b17a3b | ||
|  | 8e0524bf60 | ||
|  | 6ebf583a13 | ||
|  | eca4f614c8 | ||
|  | 11358e0b60 | ||
|  | 0a1014dded | ||
|  | a6beb48b1f | ||
|  | 5eff530ab9 | ||
|  | e6e237279c | ||
|  | 6fe46f6181 | ||
|  | 9b5023645c | ||
|  | c2da4ebcb7 | ||
|  | dc05f2405f | ||
|  | e2a469ed65 | ||
|  | e89d385f99 | ||
|  | 0a61dcc2be | ||
|  | 250fd49406 | ||
|  | 93bd5b2660 | ||
|  | d05ff9f905 | ||
|  | f3451a5fa1 | ||
|  | a9084ba98d | ||
|  | 893051f9b3 | ||
|  | dfbd4317b7 | ||
|  | 532ad0eb51 | ||
|  | 184983d857 | ||
|  | c33e4bbe0e | ||
|  | 4b9e86c424 | ||
|  | f7f8ddd925 | ||
|  | 188b1758f2 | ||
|  | 9705374b9a | ||
|  | 22aed6ea53 | ||
|  | 44c5fde086 | ||
|  | 2cd5a9dd76 | ||
|  | fa73447f88 | ||
|  | 536fe17055 | ||
|  | 662cbbc3cc | ||
|  | 69a8503ee1 | ||
|  | b906bfcafd | ||
|  | f891a047d6 | ||
|  | dce32672a6 | ||
|  | 2774991107 | ||
|  | b7ce8b6299 | ||
|  | 700fb2ec14 | ||
|  | 62d14587cd | ||
|  | 824d9243cc | ||
|  | 5351b4da0e | ||
|  | fcd0488624 | ||
|  | 193e905b28 | ||
|  | f48085c69a | ||
|  | cf078be53f | ||
|  | badcd6a05c | ||
|  | d828bd736d | ||
|  | 1e014bdae3 | ||
|  | 334ab182db | ||
|  | f52de76bfc | ||
|  | 6c7d0e8cab | ||
|  | 7229ae856e | ||
|  | 597d760d24 | ||
|  | bcf61e1ada | ||
|  | 08e596714b | ||
|  | bff577ba34 | ||
|  | 4df935b9d2 | ||
|  | 9bb560314b | ||
|  | 1a0c5e8b42 | ||
|  | 011919d0e7 | ||
|  | 418ab7a1d2 | ||
|  | 6a2c9af316 | ||
|  | eaabf478cc | ||
|  | ffe3ca76af | ||
|  | c5a6302220 | ||
|  | 9b2f81690f | ||
|  | 9621e6b570 | ||
|  | ce3ae03bef | ||
|  | e0457213ea | ||
|  | 10cdc440a0 | ||
|  | ddc62c90bb | ||
|  | 9e70d0e8fc | ||
|  | 4533d53ba8 | ||
|  | 0caa243006 | ||
|  | 1e58abdfdd | ||
|  | 370ac08d09 | ||
|  | 31b6b2736c | ||
|  | abd22ab7f1 | ||
|  | 5ab68988aa | ||
|  | ba3edcc02a | ||
|  | 8414a57a5b | ||
|  | 3f777e4662 | ||
|  | 52828183a1 | ||
|  | df7489f8e2 | ||
|  | 4fea7d86e1 | ||
|  | cb17b7c8db | ||
|  | cf7ef89bf2 | ||
|  | 2d2a578800 | ||
|  | 0ba12f3ccf | ||
|  | 5ac603977d | ||
|  | 25d5a80bee | ||
|  | c9d452a025 | ||
|  | 8132d62c02 | ||
|  | ca32ebeefe | ||
|  | 5bf5e9ee25 | ||
|  | bf097008e7 | ||
|  | 59b1208321 | ||
|  | 84194188f8 | ||
|  | a067441fb9 | ||
|  | 6eac8380b6 | ||
|  | 5aed8317ca | ||
|  | 9aed7a19cf | ||
|  | c0154be1aa | ||
|  | f9d024e971 | ||
|  | 526eb4bb31 | ||
|  | f9347cbd49 | ||
|  | b6671bdbe7 | ||
|  | edcf5d408c | ||
|  | 8c8731909e | ||
|  | de721ac6e3 | ||
|  | 963f1da1d8 | ||
|  | 4c6159e4d4 | ||
|  | e3c2e9ec6d | ||
|  | 02022bdcdf | ||
|  | 19ea426efc | ||
|  | 863070a8a7 | ||
|  | 44f9e6a8c9 | ||
|  | 087d5d7e52 | ||
|  | 26fa53f867 | ||
|  | d18af6cbc6 | ||
|  | b5bb62bd22 | ||
|  | 9ed3ab9929 | ||
|  | dca18762e0 | ||
|  | fed7c3b103 | ||
|  | d246b7a04d | ||
|  | a49ca813e0 | ||
|  | 0c688efaee | ||
|  | be3561d46f | ||
|  | c3af40a3f0 | ||
|  | d493b9a131 | ||
|  | 4e4e84ae4d | ||
|  | fea545e5e7 | ||
|  | 9096769a85 | ||
|  | 04ab157ecb | ||
|  | 2331f08283 | ||
|  | 9f8f99f21b | ||
|  | 56dadb6a49 | ||
|  | 59c9ca8d53 | ||
|  | f16a4e8aa2 | ||
|  | 890615e13a | ||
|  | 2f72e3957e | ||
|  | 1292018f51 | ||
|  | 8ae9513104 | ||
|  | c1efe2f4ac | ||
|  | 1210a0b631 | ||
|  | 39863c597e | ||
|  | 8a964f0030 | ||
|  | 74e29f25bf | ||
|  | 1e5bea2b1e | ||
|  | d8297ebb74 | ||
|  | 93d7f71cf2 | ||
|  | 887a217828 | ||
|  | 89b12136e4 | ||
|  | def7025abf | ||
|  | 35af5aab43 | ||
|  | 9ea67df0fd | ||
|  | 72fda8a728 | ||
|  | 070b48d7a7 | ||
|  | 6168009cef | ||
|  | 06a7243ac1 | ||
|  | c8602b0d89 | ||
|  | e3c730fd5f | ||
|  | b252cb6ddd | ||
|  | 8ca1e8e5a1 | ||
|  | a7d45b78c8 | ||
|  | 018231eee4 | ||
|  | 4704d494ec | ||
|  | 65927c2091 | ||
|  | 574b9d10ab | ||
|  | ff510a3b44 | ||
|  | 4d3c864950 | ||
|  | 2cde54cf30 | ||
|  | d1f3d11318 | ||
|  | 436228946b | ||
|  | 2b4083ee10 | ||
|  | e3abb4e3de | ||
|  | 4b7cd1005b | ||
|  | 03ea96096d | ||
|  | 0ea8916426 | ||
|  | 43b36eb893 | ||
|  | 03b6a187b3 | ||
|  | 664412dd4e | ||
|  | b37e08a202 | ||
|  | a05d72ec67 | ||
|  | f77e9fe6a8 | ||
|  | 1f9614e060 | ||
|  | 50d92a763a | ||
|  | ec23656334 | ||
|  | 880c2949c3 | ||
|  | 07f5fe81c8 | ||
|  | 5a992b6589 | ||
|  | 8e03e8e79b | ||
|  | ceb3d9fe20 | ||
|  | 0dc0b87bc4 | ||
|  | 3a4284fece | ||
|  | 502a90f121 | ||
|  | 88cd4aca4a | ||
|  | 3395004f93 | ||
|  | e4c05e1f4d | ||
|  | d17c70c2c8 | ||
|  | 7b42923418 | ||
|  | 5b6bdee25a | ||
|  | 05deb73c29 | ||
|  | 3685acc549 | ||
|  | 8f5a53937a | ||
|  | b9c5db41ab | ||
|  | a99a4230af | ||
|  | f446154747 | ||
|  | 5a1e2c5ed9 | ||
|  | ff6f295fac | ||
|  | 3111b3a641 | ||
|  | 20bb13ce85 | ||
|  | 5aa05f3a13 | ||
|  | c911aa2eb1 | ||
|  | d0cd2fe9ab | ||
|  | 9702805331 | ||
|  | 872152f1be | ||
|  | b822221d7f | ||
|  | 95ad605b36 | ||
|  | af32e25149 | ||
|  | e63b384d1e | ||
|  | 7f25f7426c | ||
|  | eed93ea953 | ||
|  | b5f8e92526 | ||
|  | af04b0571e | ||
|  | 75452a3e76 | ||
|  | 3f717f1df2 | ||
|  | ad6f2c2c70 | ||
|  | 8076e7f662 | ||
|  | be78496992 | ||
|  | a3f745d441 | ||
|  | e693d920d3 | ||
|  | 0f1b89eda9 | ||
|  | 172b8836fd | ||
|  | 71178ffa33 | 
							
								
								
									
										186
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | |||||||
|  | name: Build and Release CrossDesk | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [release] | ||||||
|  |     tags: | ||||||
|  |       - "v*" | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
|  | permissions: | ||||||
|  |   contents: write | ||||||
|  |  | ||||||
|  | env: | ||||||
|  |   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   # Linux | ||||||
|  |   build-linux: | ||||||
|  |     name: Build on Ubuntu | ||||||
|  |     runs-on: ubuntu-22.04 | ||||||
|  |     container: | ||||||
|  |       image: crossdesk/ubuntu22.04:latest | ||||||
|  |       options: --user root | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           submodules: recursive | ||||||
|  |  | ||||||
|  |       - name: Build CrossDesk | ||||||
|  |         env: | ||||||
|  |           CUDA_PATH: /usr/local/cuda | ||||||
|  |           XMAKE_GLOBALDIR: /data | ||||||
|  |         run: | | ||||||
|  |           ls -la $XMAKE_GLOBALDIR | ||||||
|  |           xmake b -vy --root crossdesk | ||||||
|  |  | ||||||
|  |       - name: Decode and save certificate | ||||||
|  |         shell: bash | ||||||
|  |         run: | | ||||||
|  |           mkdir -p certs | ||||||
|  |           echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt | ||||||
|  |  | ||||||
|  |       - name: Package | ||||||
|  |         run: | | ||||||
|  |           chmod +x ./scripts/linux/pkg_x86_64.sh | ||||||
|  |           ./scripts/linux/pkg_x86_64.sh | ||||||
|  |  | ||||||
|  |       - name: Upload artifact | ||||||
|  |         uses: actions/upload-artifact@v4 | ||||||
|  |         with: | ||||||
|  |           name: crossdesk-linux-x86_64 | ||||||
|  |           path: ${{ github.workspace }}/CrossDesk-0.0.1.deb | ||||||
|  |  | ||||||
|  |   # macOS | ||||||
|  |   build-macos: | ||||||
|  |     name: Build on macOS | ||||||
|  |     runs-on: ${{ matrix.runner }} | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         include: | ||||||
|  |           - arch: x86_64 | ||||||
|  |             runner: macos-13 | ||||||
|  |             cache-key: intel | ||||||
|  |             out-dir: ./build/macosx/x86_64/release/crossdesk | ||||||
|  |             artifact-name: crossdesk-macos-x86_64 | ||||||
|  |             package_script: ./scripts/macosx/pkg_x86_64.sh | ||||||
|  |           - arch: arm64 | ||||||
|  |             runner: macos-14 | ||||||
|  |             cache-key: arm | ||||||
|  |             out-dir: ./build/macosx/arm64/release/crossdesk | ||||||
|  |             artifact-name: crossdesk-macos-arm64 | ||||||
|  |             package_script: ./scripts/macosx/pkg_arm64.sh | ||||||
|  |     steps: | ||||||
|  |       - name: Cache xmake dependencies | ||||||
|  |         uses: actions/cache@v4 | ||||||
|  |         with: | ||||||
|  |           path: ~/.xmake/packages | ||||||
|  |           key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}- | ||||||
|  |  | ||||||
|  |       - name: Install xmake | ||||||
|  |         run: brew install xmake | ||||||
|  |  | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Initialize submodules | ||||||
|  |         run: git submodule update --init --recursive | ||||||
|  |  | ||||||
|  |       - name: Build CrossDesk | ||||||
|  |         run: xmake b -vy crossdesk | ||||||
|  |  | ||||||
|  |       - name: Decode and save certificate | ||||||
|  |         shell: bash | ||||||
|  |         run: | | ||||||
|  |           mkdir -p certs | ||||||
|  |           echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt | ||||||
|  |  | ||||||
|  |       - name: Package CrossDesk app | ||||||
|  |         run: | | ||||||
|  |           chmod +x ${{ matrix.package_script }} | ||||||
|  |           ${{ matrix.package_script }} | ||||||
|  |  | ||||||
|  |       - name: Upload build artifacts | ||||||
|  |         uses: actions/upload-artifact@v4 | ||||||
|  |         with: | ||||||
|  |           name: ${{ matrix.artifact-name }} | ||||||
|  |           path: crossdesk-macos-${{ matrix.arch }}-v0.0.1.pkg | ||||||
|  |  | ||||||
|  |       - name: Move files to release dir | ||||||
|  |         run: | | ||||||
|  |           mkdir -p release | ||||||
|  |           cp crossdesk-macos-${{ matrix.arch }}-v0.0.1.pkg release/ | ||||||
|  |  | ||||||
|  |   # Windows | ||||||
|  |   build-windows: | ||||||
|  |     name: Build on Windows | ||||||
|  |     runs-on: windows-2022 | ||||||
|  |     env: | ||||||
|  |       XMAKE_GLOBALDIR: D:\xmake_global | ||||||
|  |     steps: | ||||||
|  |       - name: Cache xmake dependencies | ||||||
|  |         uses: actions/cache@v4 | ||||||
|  |         with: | ||||||
|  |           path: D:\xmake_global\.xmake\packages | ||||||
|  |           key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             ${{ runner.os }}-xmake-deps-intel- | ||||||
|  |  | ||||||
|  |       - name: Install xmake | ||||||
|  |         run: | | ||||||
|  |           Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content | ||||||
|  |           echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | ||||||
|  |           xmake create cuda | ||||||
|  |           Set-Location cuda | ||||||
|  |           xmake g --theme=plain | ||||||
|  |           $cudaPath = "" | ||||||
|  |           $packagesPath = "D:\xmake_global\.xmake\packages" | ||||||
|  |  | ||||||
|  |           if (Test-Path $packagesPath) { | ||||||
|  |               Write-Host "Packages directory exists: $packagesPath" | ||||||
|  |               try { | ||||||
|  |                   $info = xmake require --info "cuda 12.6.3" 2>$null | ||||||
|  |                   if ($null -ne $info -and $info -ne "") { | ||||||
|  |                       $cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim() | ||||||
|  |                   } | ||||||
|  |               } catch {} | ||||||
|  |           } else { | ||||||
|  |               Write-Host "Packages directory not found: $packagesPath" | ||||||
|  |               Write-Host "Installing CUDA package..." | ||||||
|  |               xmake require -vy "cuda 12.6.3" | ||||||
|  |               $info = xmake require --info "cuda 12.6.3" | ||||||
|  |               $cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim() | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV | ||||||
|  |           Write-Host "Resolved CUDA_PATH = $cudaPath" | ||||||
|  |           Pop-Location | ||||||
|  |  | ||||||
|  |       - name: Checkout code | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - name: Initialize submodules | ||||||
|  |         run: git submodule update --init --recursive | ||||||
|  |  | ||||||
|  |       - name: Build CrossDesk | ||||||
|  |         run: xmake b -vy crossdesk | ||||||
|  |  | ||||||
|  |       - name: Decode and save certificate | ||||||
|  |         shell: powershell | ||||||
|  |         run: | | ||||||
|  |           New-Item -ItemType Directory -Force -Path certs | ||||||
|  |           [System.IO.File]::WriteAllBytes('certs\crossdesk.cn_root.crt', [Convert]::FromBase64String('${{ secrets.CROSSDESK_CERT_BASE64 }}')) | ||||||
|  |  | ||||||
|  |       - name: Package | ||||||
|  |         run: | | ||||||
|  |           cd ./scripts/windows | ||||||
|  |           makensis nsis_script.nsi | ||||||
|  |  | ||||||
|  |       - name: Upload artifact | ||||||
|  |         uses: actions/upload-artifact@v4 | ||||||
|  |         with: | ||||||
|  |           name: crossdesk-win-x86_64 | ||||||
|  |           path: ${{ github.workspace }}/scripts/windows/CrossDesk-0.0.1.exe | ||||||
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,3 @@ | |||||||
| [submodule "thirdparty/projectx"] | [submodule "thirdparty/minirtc"] | ||||||
| 	path = thirdparty/projectx | 	path = thirdparty/minirtc | ||||||
| 	url = https://github.com/dijunkun/projectx.git | 	url = https://github.com/kunkundi/minirtc.git | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								Info.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Info.plist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  |     <!-- 应用的Bundle identifier,通常使用反向域名标记 --> | ||||||
|  |     <key>CFBundleIdentifier</key> | ||||||
|  |     <string>com.yourcompany.yourappname</string> | ||||||
|  |  | ||||||
|  |     <!-- 应用的显示名称 --> | ||||||
|  |     <key>CFBundleName</key> | ||||||
|  |     <string>Your App Name</string> | ||||||
|  |  | ||||||
|  |     <!-- 应用的版本号 --> | ||||||
|  |     <key>CFBundleShortVersionString</key> | ||||||
|  |     <string>1.0.0</string> | ||||||
|  |  | ||||||
|  |     <!-- 应用的构建版本号 --> | ||||||
|  |     <key>CFBundleVersion</key> | ||||||
|  |     <string>1</string> | ||||||
|  |  | ||||||
|  |     <!-- 请求麦克风访问权限 --> | ||||||
|  |     <key>NSMicrophoneUsageDescription</key> | ||||||
|  |     <string>App requires access to the microphone for audio recording.</string> | ||||||
|  |  | ||||||
|  |     <!-- 请求相机访问权限 --> | ||||||
|  |     <key>NSCameraUsageDescription</key> | ||||||
|  |     <string>App requires access to the camera for video recording.</string> | ||||||
|  |  | ||||||
|  |     <!-- 请求使用连续相机设备 --> | ||||||
|  |     <key>NSCameraUseContinuityCameraDeviceType</key> | ||||||
|  |     <string>Your usage description here</string> | ||||||
|  |  | ||||||
|  |     <!-- High DPI -->> | ||||||
|  |     <key>NSHighResolutionCapable</key> | ||||||
|  |     <true/> | ||||||
|  |  | ||||||
|  |     <!-- 其他权限和配置可以在这里添加 --> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| # Continuous Desk | # CrossDesk | ||||||
|  |  | ||||||
| #### More than remote desktop | #### More than remote desktop | ||||||
|  |  | ||||||
| @@ -9,9 +9,9 @@ | |||||||
|  |  | ||||||
| # Intro | # Intro | ||||||
|  |  | ||||||
| Continuous Desk is a lightweight cross-platform remote desktop. It allows multiple users to remotely control the same computer at the same time. In addition to desktop image transmission, it also supports end-to-end voice transmission, providing collaboration capabilities on the basis of remote desktop. | CrossDesk is a lightweight cross-platform remote desktop. It allows multiple users to remotely control the same computer at the same time. In addition to desktop image transmission, it also supports end-to-end voice transmission, providing collaboration capabilities on the basis of remote desktop. | ||||||
|  |  | ||||||
| Continuous Desk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)). | CrossDesk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)). | ||||||
|  |  | ||||||
| ## Usage | ## Usage | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,19 +0,0 @@ | |||||||
| [signal server] |  | ||||||
| ip = 150.158.81.30 |  | ||||||
| port = 9099 |  | ||||||
|  |  | ||||||
| [stun server] |  | ||||||
| ip = 150.158.81.30 |  | ||||||
| port = 3478 |  | ||||||
|  |  | ||||||
| [turn server] |  | ||||||
| ip = 150.158.81.30 |  | ||||||
| port = 3478 |  | ||||||
| username = dijunkun |  | ||||||
| password = dijunkunpw |  | ||||||
|  |  | ||||||
| [hardware acceleration] |  | ||||||
| turn_on = false |  | ||||||
|  |  | ||||||
| [av1 encoding] |  | ||||||
| turn_on = true |  | ||||||
							
								
								
									
										2348
									
								
								fonts/OPPOSans_Regular.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2348
									
								
								fonts/OPPOSans_Regular.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5668
									
								
								fonts/fa_regular_400.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5668
									
								
								fonts/fa_regular_400.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										35041
									
								
								fonts/fa_solid_900.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35041
									
								
								fonts/fa_solid_900.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								icon/捕获.PNG
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icon/捕获.PNG
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								icon/捕获.ico
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icon/捕获.ico
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 84 KiB | 
							
								
								
									
										1
									
								
								icons/app.rc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								icons/app.rc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | IDI_ICON1 ICON "app_icon.ico" | ||||||
							
								
								
									
										
											BIN
										
									
								
								icons/app_icon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icons/app_icon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 687 B | 
							
								
								
									
										
											BIN
										
									
								
								icons/crossdesk.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icons/crossdesk.icns
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								icons/crossdesk.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icons/crossdesk.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 687 B | 
							
								
								
									
										
											BIN
										
									
								
								icons/crossdesk.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								icons/crossdesk.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 840 B | 
							
								
								
									
										126
									
								
								scripts/linux/pkg_x86_64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								scripts/linux/pkg_x86_64.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | # 配置变量 | ||||||
|  | APP_NAME="CrossDesk" | ||||||
|  | APP_VERSION="0.0.1" | ||||||
|  | ARCHITECTURE="amd64" | ||||||
|  | MAINTAINER="Junkun Di <junkun.di@hotmail.com>" | ||||||
|  | DESCRIPTION="A simple cross-platform remote desktop client." | ||||||
|  |  | ||||||
|  | # 目录结构 | ||||||
|  | DEB_DIR="$APP_NAME-$APP_VERSION" | ||||||
|  | DEBIAN_DIR="$DEB_DIR/DEBIAN" | ||||||
|  | BIN_DIR="$DEB_DIR/usr/local/bin" | ||||||
|  | CERT_SRC_DIR="$DEB_DIR/opt/$APP_NAME/certs"  # 用于中转安装时分发 | ||||||
|  | ICON_DIR="$DEB_DIR/usr/share/icons/hicolor/256x256/apps" | ||||||
|  | DESKTOP_DIR="$DEB_DIR/usr/share/applications" | ||||||
|  |  | ||||||
|  | # 清理已有的打包文件夹 | ||||||
|  | rm -rf "$DEB_DIR" | ||||||
|  |  | ||||||
|  | # 创建目录结构 | ||||||
|  | mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$ICON_DIR" "$DESKTOP_DIR" | ||||||
|  |  | ||||||
|  | # 复制二进制文件 | ||||||
|  | cp build/linux/x86_64/release/crossdesk "$BIN_DIR" | ||||||
|  |  | ||||||
|  | # 复制证书文件(将来通过 postinst 拷贝到每个用户 XDG_CONFIG_HOME) | ||||||
|  | cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt" | ||||||
|  |  | ||||||
|  | # 复制图标文件 | ||||||
|  | cp icons/crossdesk.png "$ICON_DIR/crossdesk.png" | ||||||
|  |  | ||||||
|  | # 设置可执行权限 | ||||||
|  | chmod +x "$BIN_DIR/crossdesk" | ||||||
|  |  | ||||||
|  | # 创建 control 文件 | ||||||
|  | cat > "$DEBIAN_DIR/control" << EOF | ||||||
|  | Package: $APP_NAME | ||||||
|  | Version: $APP_VERSION | ||||||
|  | Architecture: $ARCHITECTURE | ||||||
|  | Maintainer: $MAINTAINER | ||||||
|  | Description: $DESCRIPTION | ||||||
|  | Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1, | ||||||
|  |  libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, | ||||||
|  |  libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2, | ||||||
|  |  libsndio7.0, libxcb-shm0, libpulse0, nvidia-cuda-toolkit | ||||||
|  | Priority: optional | ||||||
|  | Section: utils | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | # 创建 desktop 文件 | ||||||
|  | cat > "$DESKTOP_DIR/$APP_NAME.desktop" << EOF | ||||||
|  | [Desktop Entry] | ||||||
|  | Version=$APP_VERSION | ||||||
|  | Name=$APP_NAME | ||||||
|  | Comment=$DESCRIPTION | ||||||
|  | Exec=/usr/local/bin/crossdesk | ||||||
|  | Icon=crossdesk | ||||||
|  | Terminal=false | ||||||
|  | Type=Application | ||||||
|  | Categories=Utility; | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | # 创建卸载脚本 postrm | ||||||
|  | cat > "$DEBIAN_DIR/postrm" << EOF | ||||||
|  | #!/bin/bash | ||||||
|  | # post-removal script for $APP_NAME | ||||||
|  |  | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then | ||||||
|  |     rm -f /usr/local/bin/crossdesk | ||||||
|  |     rm -f /usr/share/icons/hicolor/256x256/apps/crossdesk.png | ||||||
|  |     rm -f /usr/share/applications/$APP_NAME.desktop | ||||||
|  |     rm -rf /opt/$APP_NAME | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | chmod +x "$DEBIAN_DIR/postrm" | ||||||
|  |  | ||||||
|  | # 创建安装后脚本 postinst(拷贝证书到每个用户 XDG_CONFIG_HOME) | ||||||
|  | cat > "$DEBIAN_DIR/postinst" << 'EOF' | ||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | CERT_SRC="/opt/CrossDesk/certs" | ||||||
|  | CERT_FILE="crossdesk.cn_root.crt" | ||||||
|  |  | ||||||
|  | # 处理每个普通用户的配置目录 | ||||||
|  | for user_home in /home/*; do | ||||||
|  |     [ -d "$user_home" ] || continue | ||||||
|  |     username=$(basename "$user_home") | ||||||
|  |     config_dir="$user_home/.config/CrossDesk/certs" | ||||||
|  |     target="$config_dir/$CERT_FILE" | ||||||
|  |  | ||||||
|  |     if [ ! -f "$target" ]; then | ||||||
|  |         mkdir -p "$config_dir" | ||||||
|  |         cp "$CERT_SRC/$CERT_FILE" "$target" | ||||||
|  |         chown -R "$username:$username" "$user_home/.config/CrossDesk" | ||||||
|  |         echo "✔ Installed cert for $username at $target" | ||||||
|  |     fi | ||||||
|  | done | ||||||
|  |  | ||||||
|  | # 处理 root 用户(可选) | ||||||
|  | if [ -d "/root" ]; then | ||||||
|  |     config_dir="/root/.config/CrossDesk/certs" | ||||||
|  |     mkdir -p "$config_dir" | ||||||
|  |     cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" | ||||||
|  |     chown -R root:root /root/.config/CrossDesk | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | chmod +x "$DEBIAN_DIR/postinst" | ||||||
|  |  | ||||||
|  | # 构建 .deb 包 | ||||||
|  | dpkg-deb --build "$DEB_DIR" | ||||||
|  |  | ||||||
|  | # 清理构建目录 | ||||||
|  | rm -rf "$DEB_DIR" | ||||||
|  |  | ||||||
|  | echo "✅ Deb package for $APP_NAME created successfully." | ||||||
							
								
								
									
										152
									
								
								scripts/macosx/pkg_arm64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								scripts/macosx/pkg_arm64.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e  # 遇错退出 | ||||||
|  |  | ||||||
|  | # === 配置变量 === | ||||||
|  | APP_NAME="crossdesk" | ||||||
|  | APP_NAME_UPPER="CrossDesk"  # 这个变量用来指定大写的应用名 | ||||||
|  | EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"             # 可执行文件路径 | ||||||
|  | APP_VERSION="0.0.1" | ||||||
|  | PLATFORM="macos" | ||||||
|  | ARCH="arm64" | ||||||
|  | IDENTIFIER="cn.crossdesk.app" | ||||||
|  | ICON_PATH="./icons/crossdesk.icns"         # .icns 图标路径 | ||||||
|  | MACOS_MIN_VERSION="10.12" | ||||||
|  |  | ||||||
|  | CERTS_SOURCE="./certs"                     # 你的证书文件目录,里面放所有需要安装的文件 | ||||||
|  | CERT_NAME="crossdesk.cn_root.crt" | ||||||
|  |  | ||||||
|  | APP_BUNDLE="${APP_NAME_UPPER}.app"          # 使用大写的应用名称 | ||||||
|  | CONTENTS_DIR="${APP_BUNDLE}/Contents" | ||||||
|  | MACOS_DIR="${CONTENTS_DIR}/MacOS" | ||||||
|  | RESOURCES_DIR="${CONTENTS_DIR}/Resources" | ||||||
|  |  | ||||||
|  | PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.pkg"  # 保持安装包名称小写 | ||||||
|  | DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.dmg" | ||||||
|  | VOL_NAME="Install ${APP_NAME_UPPER}" | ||||||
|  |  | ||||||
|  | # === 清理旧文件 === | ||||||
|  | echo "🧹 清理旧文件..." | ||||||
|  | rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp | ||||||
|  |  | ||||||
|  | mkdir -p build_pkg_temp | ||||||
|  |  | ||||||
|  | # === 创建 .app 结构 === | ||||||
|  | echo "📦 创建 ${APP_BUNDLE}..." | ||||||
|  | mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" | ||||||
|  |  | ||||||
|  | echo "🚚 拷贝可执行文件..." | ||||||
|  | cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"  # 拷贝时使用大写的应用名称 | ||||||
|  | chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}" | ||||||
|  |  | ||||||
|  | # === 图标 === | ||||||
|  | if [ -f "${ICON_PATH}" ]; then | ||||||
|  |     cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns" | ||||||
|  |     ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>" | ||||||
|  |     echo "🎨 图标添加完成" | ||||||
|  | else | ||||||
|  |     ICON_KEY="" | ||||||
|  |     echo "⚠️ 未找到图标文件,跳过图标设置" | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # === 生成 Info.plist === | ||||||
|  | echo "📝 生成 Info.plist..." | ||||||
|  | cat > "${CONTENTS_DIR}/Info.plist" <<EOF | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  |     <key>CFBundleName</key> | ||||||
|  |     <string>${APP_NAME_UPPER}</string>  <!-- 使用大写名称 --> | ||||||
|  |     <key>CFBundleDisplayName</key> | ||||||
|  |     <string>${APP_NAME_UPPER}</string>  <!-- 使用大写名称 --> | ||||||
|  |     <key>CFBundleIdentifier</key> | ||||||
|  |     <string>${IDENTIFIER}</string> | ||||||
|  |     <key>CFBundleVersion</key> | ||||||
|  |     <string>${APP_VERSION}</string> | ||||||
|  |     <key>CFBundleShortVersionString</key> | ||||||
|  |     <string>${APP_VERSION}</string> | ||||||
|  |     <key>CFBundleExecutable</key> | ||||||
|  |     <string>${APP_NAME_UPPER}</string>  <!-- 使用大写名称 --> | ||||||
|  |     <key>CFBundlePackageType</key> | ||||||
|  |     <string>APPL</string> | ||||||
|  |     ${ICON_KEY} | ||||||
|  |     <key>LSMinimumSystemVersion</key> | ||||||
|  |     <string>${MACOS_MIN_VERSION}</string> | ||||||
|  |     <key>NSHighResolutionCapable</key> | ||||||
|  |     <true/> | ||||||
|  |     <key>NSCameraUsageDescription</key> | ||||||
|  |     <string>应用需要访问摄像头</string> | ||||||
|  |     <key>NSMicrophoneUsageDescription</key> | ||||||
|  |     <string>应用需要访问麦克风</string> | ||||||
|  |     <key>NSAppleEventsUsageDescription</key> | ||||||
|  |     <string>应用需要发送 Apple 事件</string> | ||||||
|  |     <key>NSScreenCaptureUsageDescription</key> | ||||||
|  |     <string>应用需要录屏权限以捕获屏幕内容</string> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | echo "✅ .app 创建完成" | ||||||
|  |  | ||||||
|  | # === 构建应用组件包 === | ||||||
|  | echo "📦 构建应用组件包..." | ||||||
|  | pkgbuild \ | ||||||
|  |   --identifier "${IDENTIFIER}" \ | ||||||
|  |   --version "${APP_VERSION}" \ | ||||||
|  |   --install-location "/Applications" \ | ||||||
|  |   --component "${APP_BUNDLE}" \ | ||||||
|  |   build_pkg_temp/${APP_NAME}-component.pkg | ||||||
|  |  | ||||||
|  | # === 构建 certs 组件包 === | ||||||
|  | # 先创建脚本目录和脚本文件 | ||||||
|  | mkdir -p scripts | ||||||
|  |  | ||||||
|  | cat > scripts/postinstall <<'EOF' | ||||||
|  | #!/bin/bash | ||||||
|  | USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) | ||||||
|  | HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) | ||||||
|  |  | ||||||
|  | DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" | ||||||
|  |  | ||||||
|  | mkdir -p "$DEST" | ||||||
|  | cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | chmod +x scripts/postinstall | ||||||
|  |  | ||||||
|  | # 构建 certs 组件包,增加 --scripts 参数指定 postinstall | ||||||
|  | pkgbuild \ | ||||||
|  |   --root "${CERTS_SOURCE}" \ | ||||||
|  |   --identifier "${IDENTIFIER}.certs" \ | ||||||
|  |   --version "${APP_VERSION}" \ | ||||||
|  |   --install-location "/Library/Application Support/CrossDesk/certs" \ | ||||||
|  |   --scripts scripts \ | ||||||
|  |   build_pkg_temp/${APP_NAME}-certs.pkg | ||||||
|  |  | ||||||
|  | # === 组合产品包 === | ||||||
|  | echo "🏗️ 组合最终安装包..." | ||||||
|  | productbuild \ | ||||||
|  |   --package build_pkg_temp/${APP_NAME}-component.pkg \ | ||||||
|  |   --package build_pkg_temp/${APP_NAME}-certs.pkg \ | ||||||
|  |   "${PKG_NAME}" | ||||||
|  |  | ||||||
|  | echo "✅ 生成安装包完成:${PKG_NAME}" | ||||||
|  |  | ||||||
|  | # === 可选:打包成 DMG === | ||||||
|  | echo "📦 可选打包成 DMG..." | ||||||
|  | mkdir -p CrossDesk_dmg_temp | ||||||
|  | cp "${PKG_NAME}" CrossDesk_dmg_temp/ | ||||||
|  | ln -s /Applications CrossDesk_dmg_temp/Applications | ||||||
|  |  | ||||||
|  | hdiutil create -volname "${VOL_NAME}" \ | ||||||
|  |   -srcfolder CrossDesk_dmg_temp \ | ||||||
|  |   -ov -format UDZO "${DMG_NAME}" | ||||||
|  |  | ||||||
|  | rm -rf CrossDesk_dmg_temp build_pkg_temp scripts ${APP_BUNDLE} ${DMG_NAME} | ||||||
|  |  | ||||||
|  | echo "🎉 所有打包完成:" | ||||||
|  | echo "   ✔️ 应用:${APP_BUNDLE}" | ||||||
|  | echo "   ✔️ 安装包:${PKG_NAME}" | ||||||
|  | echo "   ✔️ 镜像包(可选):${DMG_NAME}" | ||||||
							
								
								
									
										152
									
								
								scripts/macosx/pkg_x86_64.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								scripts/macosx/pkg_x86_64.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e  # 遇错退出 | ||||||
|  |  | ||||||
|  | # === 配置变量 === | ||||||
|  | APP_NAME="crossdesk" | ||||||
|  | APP_NAME_UPPER="CrossDesk"  # 这个变量用来指定大写的应用名 | ||||||
|  | EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"             # 可执行文件路径 | ||||||
|  | APP_VERSION="0.0.1" | ||||||
|  | PLATFORM="macos" | ||||||
|  | ARCH="x86_64" | ||||||
|  | IDENTIFIER="cn.crossdesk.app" | ||||||
|  | ICON_PATH="icons/crossdesk.icns"         # .icns 图标路径 | ||||||
|  | MACOS_MIN_VERSION="10.12" | ||||||
|  |  | ||||||
|  | CERTS_SOURCE="certs"                     # 你的证书文件目录,里面放所有需要安装的文件 | ||||||
|  | CERT_NAME="crossdesk.cn_root.crt" | ||||||
|  |  | ||||||
|  | APP_BUNDLE="${APP_NAME_UPPER}.app"          # 使用大写的应用名称 | ||||||
|  | CONTENTS_DIR="${APP_BUNDLE}/Contents" | ||||||
|  | MACOS_DIR="${CONTENTS_DIR}/MacOS" | ||||||
|  | RESOURCES_DIR="${CONTENTS_DIR}/Resources" | ||||||
|  |  | ||||||
|  | PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.pkg"  # 保持安装包名称小写 | ||||||
|  | DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.dmg" | ||||||
|  | VOL_NAME="Install ${APP_NAME_UPPER}" | ||||||
|  |  | ||||||
|  | # === 清理旧文件 === | ||||||
|  | echo "🧹 清理旧文件..." | ||||||
|  | rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp | ||||||
|  |  | ||||||
|  | mkdir -p build_pkg_temp | ||||||
|  |  | ||||||
|  | # === 创建 .app 结构 === | ||||||
|  | echo "📦 创建 ${APP_BUNDLE}..." | ||||||
|  | mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" | ||||||
|  |  | ||||||
|  | echo "🚚 拷贝可执行文件..." | ||||||
|  | cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"  # 拷贝时使用大写的应用名称 | ||||||
|  | chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}" | ||||||
|  |  | ||||||
|  | # === 图标 === | ||||||
|  | if [ -f "${ICON_PATH}" ]; then | ||||||
|  |     cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns" | ||||||
|  |     ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>" | ||||||
|  |     echo "🎨 图标添加完成" | ||||||
|  | else | ||||||
|  |     ICON_KEY="" | ||||||
|  |     echo "⚠️ 未找到图标文件,跳过图标设置" | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # === 生成 Info.plist === | ||||||
|  | echo "📝 生成 Info.plist..." | ||||||
|  | cat > "${CONTENTS_DIR}/Info.plist" <<EOF | ||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||
|  | <plist version="1.0"> | ||||||
|  | <dict> | ||||||
|  |     <key>CFBundleName</key> | ||||||
|  |     <string>${APP_NAME_UPPER}</string>  <!-- 使用大写名称 --> | ||||||
|  |     <key>CFBundleDisplayName</key> | ||||||
|  |     <string>${APP_NAME_UPPER}</string>  <!-- 使用大写名称 --> | ||||||
|  |     <key>CFBundleIdentifier</key> | ||||||
|  |     <string>${IDENTIFIER}</string> | ||||||
|  |     <key>CFBundleVersion</key> | ||||||
|  |     <string>${APP_VERSION}</string> | ||||||
|  |     <key>CFBundleShortVersionString</key> | ||||||
|  |     <string>${APP_VERSION}</string> | ||||||
|  |     <key>CFBundleExecutable</key> | ||||||
|  |     <string>${APP_NAME_UPPER}</string>  <!-- 使用大写名称 --> | ||||||
|  |     <key>CFBundlePackageType</key> | ||||||
|  |     <string>APPL</string> | ||||||
|  |     ${ICON_KEY} | ||||||
|  |     <key>LSMinimumSystemVersion</key> | ||||||
|  |     <string>${MACOS_MIN_VERSION}</string> | ||||||
|  |     <key>NSHighResolutionCapable</key> | ||||||
|  |     <true/> | ||||||
|  |     <key>NSCameraUsageDescription</key> | ||||||
|  |     <string>应用需要访问摄像头</string> | ||||||
|  |     <key>NSMicrophoneUsageDescription</key> | ||||||
|  |     <string>应用需要访问麦克风</string> | ||||||
|  |     <key>NSAppleEventsUsageDescription</key> | ||||||
|  |     <string>应用需要发送 Apple 事件</string> | ||||||
|  |     <key>NSScreenCaptureUsageDescription</key> | ||||||
|  |     <string>应用需要录屏权限以捕获屏幕内容</string> | ||||||
|  | </dict> | ||||||
|  | </plist> | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | echo "✅ .app 创建完成" | ||||||
|  |  | ||||||
|  | # === 构建应用组件包 === | ||||||
|  | echo "📦 构建应用组件包..." | ||||||
|  | pkgbuild \ | ||||||
|  |   --identifier "${IDENTIFIER}" \ | ||||||
|  |   --version "${APP_VERSION}" \ | ||||||
|  |   --install-location "/Applications" \ | ||||||
|  |   --component "${APP_BUNDLE}" \ | ||||||
|  |   build_pkg_temp/${APP_NAME}-component.pkg | ||||||
|  |  | ||||||
|  | # === 构建 certs 组件包 === | ||||||
|  | # 先创建脚本目录和脚本文件 | ||||||
|  | mkdir -p scripts | ||||||
|  |  | ||||||
|  | cat > scripts/postinstall <<'EOF' | ||||||
|  | #!/bin/bash | ||||||
|  | USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console ) | ||||||
|  | HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' ) | ||||||
|  |  | ||||||
|  | DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs" | ||||||
|  |  | ||||||
|  | mkdir -p "$DEST" | ||||||
|  | cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/" | ||||||
|  |  | ||||||
|  | exit 0 | ||||||
|  | EOF | ||||||
|  |  | ||||||
|  | chmod +x scripts/postinstall | ||||||
|  |  | ||||||
|  | # 构建 certs 组件包,增加 --scripts 参数指定 postinstall | ||||||
|  | pkgbuild \ | ||||||
|  |   --root "${CERTS_SOURCE}" \ | ||||||
|  |   --identifier "${IDENTIFIER}.certs" \ | ||||||
|  |   --version "${APP_VERSION}" \ | ||||||
|  |   --install-location "/Library/Application Support/CrossDesk/certs" \ | ||||||
|  |   --scripts scripts \ | ||||||
|  |   build_pkg_temp/${APP_NAME}-certs.pkg | ||||||
|  |  | ||||||
|  | # === 组合产品包 === | ||||||
|  | echo "🏗️ 组合最终安装包..." | ||||||
|  | productbuild \ | ||||||
|  |   --package build_pkg_temp/${APP_NAME}-component.pkg \ | ||||||
|  |   --package build_pkg_temp/${APP_NAME}-certs.pkg \ | ||||||
|  |   "${PKG_NAME}" | ||||||
|  |  | ||||||
|  | echo "✅ 生成安装包完成:${PKG_NAME}" | ||||||
|  |  | ||||||
|  | # === 可选:打包成 DMG === | ||||||
|  | echo "📦 可选打包成 DMG..." | ||||||
|  | mkdir -p CrossDesk_dmg_temp | ||||||
|  | cp "${PKG_NAME}" CrossDesk_dmg_temp/ | ||||||
|  | ln -s /Applications CrossDesk_dmg_temp/Applications | ||||||
|  |  | ||||||
|  | hdiutil create -volname "${VOL_NAME}" \ | ||||||
|  |   -srcfolder CrossDesk_dmg_temp \ | ||||||
|  |   -ov -format UDZO "${DMG_NAME}" | ||||||
|  |  | ||||||
|  | rm -rf CrossDesk_dmg_temp build_pkg_temp scripts ${APP_BUNDLE} ${DMG_NAME} | ||||||
|  |  | ||||||
|  | echo "🎉 所有打包完成:" | ||||||
|  | echo "   ✔️ 应用:${APP_BUNDLE}" | ||||||
|  | echo "   ✔️ 安装包:${PKG_NAME}" | ||||||
|  | echo "   ✔️ 镜像包(可选):${DMG_NAME}" | ||||||
							
								
								
									
										102
									
								
								scripts/windows/nsis_script.nsi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								scripts/windows/nsis_script.nsi
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7> | ||||||
|  | !addincludedir "${__FILEDIR__}" | ||||||
|  |  | ||||||
|  | ; <20><>װ<EFBFBD><D7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC><EFBFBD>峣<EFBFBD><E5B3A3> | ||||||
|  | !define PRODUCT_NAME "CrossDesk" | ||||||
|  | !define PRODUCT_VERSION "0.0.1" | ||||||
|  | !define PRODUCT_PUBLISHER "CrossDesk" | ||||||
|  | !define PRODUCT_WEB_SITE "https://www.crossdesk.cn/" | ||||||
|  | !define APP_NAME "CrossDesk" | ||||||
|  | !define UNINSTALL_REG_KEY "CrossDesk" | ||||||
|  |  | ||||||
|  | ; <20><><EFBFBD>ð<EFBFBD>װ<EFBFBD><D7B0>ͼ<EFBFBD><CDBC>·<EFBFBD><C2B7> | ||||||
|  | !define MUI_ICON "${__FILEDIR__}\..\..\icons\crossdesk.ico" | ||||||
|  |  | ||||||
|  | ; <20><><EFBFBD><EFBFBD>֤<EFBFBD><D6A4>·<EFBFBD><C2B7> | ||||||
|  | !define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt" | ||||||
|  |  | ||||||
|  | ; ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD> | ||||||
|  | SetCompressor /FINAL lzma | ||||||
|  |  | ||||||
|  | ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԱȨ<D4B1>ޣ<EFBFBD>д<EFBFBD><D0B4>HKLM<4C><4D>Ҫ<EFBFBD><D2AA> | ||||||
|  | RequestExecutionLevel admin | ||||||
|  |  | ||||||
|  | ; ------ MUI <20>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD>涨<EFBFBD><E6B6A8> ------ | ||||||
|  | !include "MUI.nsh" | ||||||
|  | !define MUI_ABORTWARNING | ||||||
|  | !insertmacro MUI_PAGE_WELCOME | ||||||
|  | !insertmacro MUI_PAGE_DIRECTORY | ||||||
|  | !insertmacro MUI_PAGE_INSTFILES | ||||||
|  | !insertmacro MUI_PAGE_FINISH | ||||||
|  | !insertmacro MUI_LANGUAGE "SimpChinese" | ||||||
|  | !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS | ||||||
|  | ; ------ MUI <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ------ | ||||||
|  |  | ||||||
|  | Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" | ||||||
|  | OutFile "CrossDesk-${PRODUCT_VERSION}.exe" | ||||||
|  | InstallDir "$PROGRAMFILES\CrossDesk" | ||||||
|  | ShowInstDetails show | ||||||
|  |  | ||||||
|  | Section "MainSection" | ||||||
|  |     SetOutPath "$INSTDIR" | ||||||
|  |     SetOverwrite ifnewer | ||||||
|  |  | ||||||
|  |     ; <20><><EFBFBD>ó<EFBFBD><C3B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>·<EFBFBD><C2B7> | ||||||
|  |     File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe" | ||||||
|  | 	 | ||||||
|  | 	; ? <20><><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD>װĿ¼ | ||||||
|  |     File "${MUI_ICON}" | ||||||
|  |  | ||||||
|  |     ; д<><D0B4>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>Ϣ | ||||||
|  |     WriteUninstaller "$INSTDIR\uninstall.exe" | ||||||
|  |  | ||||||
|  |     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}" | ||||||
|  |     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe" | ||||||
|  |     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" | ||||||
|  |     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" | ||||||
|  |     WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}" | ||||||
|  | 	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\crossdesk.ico" | ||||||
|  |     WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1 | ||||||
|  |     WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1 | ||||||
|  | SectionEnd | ||||||
|  |  | ||||||
|  | Section "Cert" | ||||||
|  |     SetOutPath "$APPDATA\CrossDesk\certs" | ||||||
|  |     File /r "${CERT_FILE}" | ||||||
|  | SectionEnd | ||||||
|  |  | ||||||
|  | Section -AdditionalIcons | ||||||
|  |     ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ | ||||||
|  |     CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico" | ||||||
|  |  | ||||||
|  |     ; <20><>ʼ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ | ||||||
|  |     CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico" | ||||||
|  |  | ||||||
|  |     ; <20><>ҳ<EFBFBD><D2B3><EFBFBD>ݷ<EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD>棩 | ||||||
|  |     WriteIniStr "$DESKTOP\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}" | ||||||
|  | SectionEnd | ||||||
|  |  | ||||||
|  | Section "Uninstall" | ||||||
|  |     ; ɾ<><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD>س<EFBFBD><D8B3><EFBFBD> | ||||||
|  |     Delete "$INSTDIR\crossdesk.exe" | ||||||
|  |     Delete "$INSTDIR\uninstall.exe" | ||||||
|  |  | ||||||
|  |     ; <20>ݹ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD><EFBFBD>װĿ¼ | ||||||
|  |     RMDir /r "$INSTDIR" | ||||||
|  |  | ||||||
|  |     ; ɾ<><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϳ<EFBFBD>ʼ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ | ||||||
|  |     Delete "$DESKTOP\${PRODUCT_NAME}.lnk" | ||||||
|  |     Delete "$DESKTOP\${PRODUCT_NAME}.url" | ||||||
|  |     Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk" | ||||||
|  |  | ||||||
|  |     ; ɾ<><C9BE>ע<EFBFBD><D7A2><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD> | ||||||
|  |     DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" | ||||||
|  |  | ||||||
|  |     ; <20>ݹ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD>û<EFBFBD> AppData <20>е<EFBFBD> CrossDesk <20>ļ<EFBFBD><C4BC><EFBFBD> | ||||||
|  |     RMDir /r "$APPDATA\CrossDesk" | ||||||
|  |     RMDir /r "$LOCALAPPDATA\CrossDesk" | ||||||
|  | SectionEnd | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Section -Post | ||||||
|  | SectionEnd | ||||||
							
								
								
									
										44
									
								
								src/common/display_info.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/common/display_info.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2025-05-15 | ||||||
|  |  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _DISPLAY_INFO_H_ | ||||||
|  | #define _DISPLAY_INFO_H_ | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | class DisplayInfo { | ||||||
|  |  public: | ||||||
|  |   DisplayInfo(std::string name, int left, int top, int right, int bottom) | ||||||
|  |       : name(name), left(left), top(top), right(right), bottom(bottom) { | ||||||
|  |     width = right - left; | ||||||
|  |     height = bottom - top; | ||||||
|  |   } | ||||||
|  |   DisplayInfo(void* handle, std::string name, bool is_primary, int left, | ||||||
|  |               int top, int right, int bottom) | ||||||
|  |       : handle(handle), | ||||||
|  |         name(name), | ||||||
|  |         is_primary(is_primary), | ||||||
|  |         left(left), | ||||||
|  |         top(top), | ||||||
|  |         right(right), | ||||||
|  |         bottom(bottom) { | ||||||
|  |     width = right - left; | ||||||
|  |     height = bottom - top; | ||||||
|  |   } | ||||||
|  |   ~DisplayInfo() {} | ||||||
|  |  | ||||||
|  |   void* handle = nullptr; | ||||||
|  |   std::string name = ""; | ||||||
|  |   bool is_primary = false; | ||||||
|  |   int left = 0; | ||||||
|  |   int top = 0; | ||||||
|  |   int right = 0; | ||||||
|  |   int bottom = 0; | ||||||
|  |   int width = 0; | ||||||
|  |   int height = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| #include "platform.h" | #include "platform.h" | ||||||
|  |  | ||||||
| #include "log.h" | #include "rd_log.h" | ||||||
|  |  | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
| #include <Winsock2.h> | #include <Winsock2.h> | ||||||
| @@ -25,13 +25,13 @@ std::string GetMac() { | |||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|   IP_ADAPTER_INFO adapterInfo[16]; |   IP_ADAPTER_INFO adapterInfo[16]; | ||||||
|   DWORD bufferSize = sizeof(adapterInfo); |   DWORD bufferSize = sizeof(adapterInfo); | ||||||
|  |  | ||||||
|   DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize); |   DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize); | ||||||
|   if (result == ERROR_SUCCESS) { |   if (result == ERROR_SUCCESS) { | ||||||
|     PIP_ADAPTER_INFO adapter = adapterInfo; |     PIP_ADAPTER_INFO adapter = adapterInfo; | ||||||
|     while (adapter) { |     while (adapter) { | ||||||
|       for (UINT i = 0; i < adapter->AddressLength; i++) { |       for (UINT i = 0; i < adapter->AddressLength; i++) { | ||||||
|         len += sprintf(mac_addr + len, "%.2X", adapter->Address[i]); |         len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X", | ||||||
|  |                          adapter->Address[i]); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -55,7 +55,8 @@ std::string GetMac() { | |||||||
|         const unsigned char *base = |         const unsigned char *base = | ||||||
|             (const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen]; |             (const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen]; | ||||||
|         for (int i = 0; i < dlAddr->sdl_alen; i++) { |         for (int i = 0; i < dlAddr->sdl_alen; i++) { | ||||||
|           len += sprintf(mac_addr + len, "%.2X", base[i]); |           len += | ||||||
|  |               snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       cursor = cursor->ifa_next; |       cursor = cursor->ifa_next; | ||||||
| @@ -99,3 +100,26 @@ std::string GetMac() { | |||||||
| #endif | #endif | ||||||
|   return mac_addr; |   return mac_addr; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::string GetHostName() { | ||||||
|  |   char hostname[256]; | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   WSADATA wsaData; | ||||||
|  |   if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { | ||||||
|  |     std::cerr << "WSAStartup failed." << std::endl; | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  |   if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) { | ||||||
|  |     LOG_ERROR("gethostname failed: {}", WSAGetLastError()); | ||||||
|  |     WSACleanup(); | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  |   WSACleanup(); | ||||||
|  | #else | ||||||
|  |   if (gethostname(hostname, sizeof(hostname)) == -1) { | ||||||
|  |     LOG_ERROR("gethostname failed"); | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |   return hostname; | ||||||
|  | } | ||||||
| @@ -10,5 +10,6 @@ | |||||||
| #include <iostream> | #include <iostream> | ||||||
|  |  | ||||||
| std::string GetMac(); | std::string GetMac(); | ||||||
|  | std::string GetHostName(); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| @@ -25,6 +25,11 @@ int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) { | |||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int ConfigCenter::SetTurn(bool enable_turn) { | ||||||
|  |   enable_turn_ = enable_turn; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; } | ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; } | ||||||
|  |  | ||||||
| ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() { | ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() { | ||||||
| @@ -36,3 +41,5 @@ ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; } | bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; } | ||||||
|  |  | ||||||
|  | bool ConfigCenter::IsEnableTurn() { return enable_turn_; } | ||||||
| @@ -22,12 +22,14 @@ class ConfigCenter { | |||||||
|   int SetVideoQuality(VIDEO_QUALITY video_quality); |   int SetVideoQuality(VIDEO_QUALITY video_quality); | ||||||
|   int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format); |   int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format); | ||||||
|   int SetHardwareVideoCodec(bool hardware_video_codec); |   int SetHardwareVideoCodec(bool hardware_video_codec); | ||||||
|  |   int SetTurn(bool enable_turn); | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   LANGUAGE GetLanguage(); |   LANGUAGE GetLanguage(); | ||||||
|   VIDEO_QUALITY GetVideoQuality(); |   VIDEO_QUALITY GetVideoQuality(); | ||||||
|   VIDEO_ENCODE_FORMAT GetVideoEncodeFormat(); |   VIDEO_ENCODE_FORMAT GetVideoEncodeFormat(); | ||||||
|   bool IsHardwareVideoCodec(); |   bool IsHardwareVideoCodec(); | ||||||
|  |   bool IsEnableTurn(); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   // Default value should be same with parameters in localization.h |   // Default value should be same with parameters in localization.h | ||||||
| @@ -35,6 +37,7 @@ class ConfigCenter { | |||||||
|   VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM; |   VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM; | ||||||
|   VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1; |   VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1; | ||||||
|   bool hardware_video_codec_ = false; |   bool hardware_video_codec_ = false; | ||||||
|  |   bool enable_turn_ = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| @@ -9,12 +9,31 @@ | |||||||
|  |  | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  |  | ||||||
| typedef enum { mouse = 0, keyboard } ControlType; | #include "display_info.h" | ||||||
| typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag; |  | ||||||
|  | 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 enum { key_down = 0, key_up } KeyFlag; | ||||||
| typedef struct { | typedef struct { | ||||||
|   size_t x; |   float x; | ||||||
|   size_t y; |   float y; | ||||||
|  |   int s; | ||||||
|   MouseFlag flag; |   MouseFlag flag; | ||||||
| } Mouse; | } Mouse; | ||||||
|  |  | ||||||
| @@ -23,22 +42,42 @@ typedef struct { | |||||||
|   KeyFlag flag; |   KeyFlag flag; | ||||||
| } Key; | } 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 { | typedef struct { | ||||||
|   ControlType type; |   ControlType type; | ||||||
|   union { |   union { | ||||||
|     Mouse m; |     Mouse m; | ||||||
|     Key k; |     Key k; | ||||||
|  |     HostInfo i; | ||||||
|  |     bool a; | ||||||
|  |     int d; | ||||||
|   }; |   }; | ||||||
| } RemoteAction; | } RemoteAction; | ||||||
|  |  | ||||||
|  | // int key_code, bool is_down | ||||||
|  | typedef void (*OnKeyAction)(int, bool, void*); | ||||||
|  |  | ||||||
| class DeviceController { | class DeviceController { | ||||||
|  public: |  public: | ||||||
|   virtual ~DeviceController() {} |   virtual ~DeviceController() {} | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual int Init(int screen_width, int screen_height) = 0; |   // virtual int Init(int screen_width, int screen_height); | ||||||
|   virtual int Destroy() = 0; |   // virtual int Destroy(); | ||||||
|   virtual int SendCommand(RemoteAction remote_action) = 0; |   // virtual int SendMouseCommand(RemoteAction remote_action); | ||||||
|  |  | ||||||
|  |   // virtual int Hook(); | ||||||
|  |   // virtual int Unhook(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| @@ -8,6 +8,7 @@ | |||||||
| #define _DEVICE_CONTROLLER_FACTORY_H_ | #define _DEVICE_CONTROLLER_FACTORY_H_ | ||||||
|  |  | ||||||
| #include "device_controller.h" | #include "device_controller.h" | ||||||
|  | #include "keyboard_capturer.h" | ||||||
| #include "mouse_controller.h" | #include "mouse_controller.h" | ||||||
|  |  | ||||||
| class DeviceControllerFactory { | class DeviceControllerFactory { | ||||||
| @@ -23,7 +24,7 @@ class DeviceControllerFactory { | |||||||
|       case Mouse: |       case Mouse: | ||||||
|         return new MouseController(); |         return new MouseController(); | ||||||
|       case Keyboard: |       case Keyboard: | ||||||
|         return nullptr; |         return new KeyboardCapturer(); | ||||||
|       default: |       default: | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | #include "keyboard_capturer.h" | ||||||
|  |  | ||||||
|  | #include "keyboard_converter.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | static OnKeyAction g_on_key_action = nullptr; | ||||||
|  | static void* g_user_ptr = nullptr; | ||||||
|  |  | ||||||
|  | static int KeyboardEventHandler(Display* display, XEvent* event) { | ||||||
|  |   if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) { | ||||||
|  |     KeySym keySym = XKeycodeToKeysym(display, event->xkey.keycode, 0); | ||||||
|  |     int key_code = XKeysymToKeycode(display, keySym); | ||||||
|  |     bool is_key_down = (event->xkey.type == KeyPress); | ||||||
|  |  | ||||||
|  |     if (g_on_key_action) { | ||||||
|  |       g_on_key_action(key_code, is_key_down, g_user_ptr); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) { | ||||||
|  |   display_ = XOpenDisplay(nullptr); | ||||||
|  |   if (!display_) { | ||||||
|  |     LOG_ERROR("Failed to open X display."); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | KeyboardCapturer::~KeyboardCapturer() { | ||||||
|  |   if (display_) { | ||||||
|  |     XCloseDisplay(display_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { | ||||||
|  |   g_on_key_action = on_key_action; | ||||||
|  |   g_user_ptr = user_ptr; | ||||||
|  |  | ||||||
|  |   XSelectInput(display_, DefaultRootWindow(display_), | ||||||
|  |                KeyPressMask | KeyReleaseMask); | ||||||
|  |  | ||||||
|  |   while (running_) { | ||||||
|  |     XEvent event; | ||||||
|  |     XNextEvent(display_, &event); | ||||||
|  |     KeyboardEventHandler(display_, &event); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::Unhook() { | ||||||
|  |   running_ = false; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { | ||||||
|  |   if (!display_) { | ||||||
|  |     LOG_ERROR("Display not initialized."); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) { | ||||||
|  |     int x11_key_code = vkCodeToX11KeySym[key_code]; | ||||||
|  |     KeyCode keycode = XKeysymToKeycode(display_, x11_key_code); | ||||||
|  |     XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime); | ||||||
|  |     XFlush(display_); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/device_controller/keyboard/linux/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-11-22 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _KEYBOARD_CAPTURER_H_ | ||||||
|  | #define _KEYBOARD_CAPTURER_H_ | ||||||
|  |  | ||||||
|  | #include <X11/Xlib.h> | ||||||
|  | #include <X11/extensions/XTest.h> | ||||||
|  | #include <X11/keysym.h> | ||||||
|  |  | ||||||
|  | #include "device_controller.h" | ||||||
|  |  | ||||||
|  | class KeyboardCapturer : public DeviceController { | ||||||
|  |  public: | ||||||
|  |   KeyboardCapturer(); | ||||||
|  |   virtual ~KeyboardCapturer(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual int Hook(OnKeyAction on_key_action, void *user_ptr); | ||||||
|  |   virtual int Unhook(); | ||||||
|  |   virtual int SendKeyboardCommand(int key_code, bool is_down); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   Display *display_; | ||||||
|  |   Window root_; | ||||||
|  |   bool running_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										135
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | #include "keyboard_capturer.h" | ||||||
|  |  | ||||||
|  | #include "keyboard_converter.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | static OnKeyAction g_on_key_action = nullptr; | ||||||
|  | static void *g_user_ptr = nullptr; | ||||||
|  |  | ||||||
|  | CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, | ||||||
|  |                          CGEventRef event, void *userInfo) { | ||||||
|  |   KeyboardCapturer *keyboard_capturer = (KeyboardCapturer *)userInfo; | ||||||
|  |   if (!keyboard_capturer) { | ||||||
|  |     LOG_ERROR("keyboard_capturer is nullptr"); | ||||||
|  |     return event; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int vk_code = 0; | ||||||
|  |  | ||||||
|  |   if (type == kCGEventKeyDown || type == kCGEventKeyUp) { | ||||||
|  |     CGKeyCode key_code = static_cast<CGKeyCode>( | ||||||
|  |         CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)); | ||||||
|  |     if (CGKeyCodeToVkCode.find(key_code) != CGKeyCodeToVkCode.end()) { | ||||||
|  |       g_on_key_action(CGKeyCodeToVkCode[key_code], type == kCGEventKeyDown, | ||||||
|  |                       g_user_ptr); | ||||||
|  |     } | ||||||
|  |   } else if (type == kCGEventFlagsChanged) { | ||||||
|  |     CGEventFlags current_flags = CGEventGetFlags(event); | ||||||
|  |     CGKeyCode key_code = static_cast<CGKeyCode>( | ||||||
|  |         CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)); | ||||||
|  |  | ||||||
|  |     // caps lock | ||||||
|  |     bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0; | ||||||
|  |     if (caps_lock_state != keyboard_capturer->caps_lock_flag_) { | ||||||
|  |       keyboard_capturer->caps_lock_flag_ = caps_lock_state; | ||||||
|  |       if (keyboard_capturer->caps_lock_flag_) { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||||
|  |       } else { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // shift | ||||||
|  |     bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0; | ||||||
|  |     if (shift_state != keyboard_capturer->shift_flag_) { | ||||||
|  |       keyboard_capturer->shift_flag_ = shift_state; | ||||||
|  |       if (keyboard_capturer->shift_flag_) { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||||
|  |       } else { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // control | ||||||
|  |     bool control_state = (current_flags & kCGEventFlagMaskControl) != 0; | ||||||
|  |     if (control_state != keyboard_capturer->control_flag_) { | ||||||
|  |       keyboard_capturer->control_flag_ = control_state; | ||||||
|  |       if (keyboard_capturer->control_flag_) { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||||
|  |       } else { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // option | ||||||
|  |     bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0; | ||||||
|  |     if (option_state != keyboard_capturer->option_flag_) { | ||||||
|  |       keyboard_capturer->option_flag_ = option_state; | ||||||
|  |       if (keyboard_capturer->option_flag_) { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||||
|  |       } else { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // command | ||||||
|  |     bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0; | ||||||
|  |     if (command_state != keyboard_capturer->command_flag_) { | ||||||
|  |       keyboard_capturer->command_flag_ = command_state; | ||||||
|  |       if (keyboard_capturer->command_flag_) { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr); | ||||||
|  |       } else { | ||||||
|  |         g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | KeyboardCapturer::KeyboardCapturer() {} | ||||||
|  |  | ||||||
|  | KeyboardCapturer::~KeyboardCapturer() {} | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::Hook(OnKeyAction on_key_action, void *user_ptr) { | ||||||
|  |   g_on_key_action = on_key_action; | ||||||
|  |   g_user_ptr = user_ptr; | ||||||
|  |  | ||||||
|  |   CGEventMask eventMask = (1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) | | ||||||
|  |                           (1 << kCGEventFlagsChanged); | ||||||
|  |  | ||||||
|  |   event_tap_ = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, | ||||||
|  |                                 kCGEventTapOptionDefault, eventMask, | ||||||
|  |                                 eventCallback, this); | ||||||
|  |  | ||||||
|  |   if (!event_tap_) { | ||||||
|  |     LOG_ERROR("CGEventTapCreate failed"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   run_loop_source_ = | ||||||
|  |       CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event_tap_, 0); | ||||||
|  |  | ||||||
|  |   CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_, | ||||||
|  |                      kCFRunLoopCommonModes); | ||||||
|  |  | ||||||
|  |   CGEventTapEnable(event_tap_, true); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::Unhook() { | ||||||
|  |   CFRelease(run_loop_source_); | ||||||
|  |   CFRelease(event_tap_); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { | ||||||
|  |   if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) { | ||||||
|  |     CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code]; | ||||||
|  |     CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down); | ||||||
|  |     CGEventPost(kCGHIDEventTap, event); | ||||||
|  |     CFRelease(event); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/device_controller/keyboard/mac/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-11-22 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _KEYBOARD_CAPTURER_H_ | ||||||
|  | #define _KEYBOARD_CAPTURER_H_ | ||||||
|  |  | ||||||
|  | #include <ApplicationServices/ApplicationServices.h> | ||||||
|  |  | ||||||
|  | #include "device_controller.h" | ||||||
|  |  | ||||||
|  | class KeyboardCapturer : public DeviceController { | ||||||
|  |  public: | ||||||
|  |   KeyboardCapturer(); | ||||||
|  |   virtual ~KeyboardCapturer(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual int Hook(OnKeyAction on_key_action, void *user_ptr); | ||||||
|  |   virtual int Unhook(); | ||||||
|  |   virtual int SendKeyboardCommand(int key_code, bool is_down); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   CFMachPortRef event_tap_; | ||||||
|  |   CFRunLoopSourceRef run_loop_source_; | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   bool caps_lock_flag_ = false; | ||||||
|  |   bool shift_flag_ = false; | ||||||
|  |   bool control_flag_ = false; | ||||||
|  |   bool option_flag_ = false; | ||||||
|  |   bool command_flag_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										56
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | #include "keyboard_capturer.h" | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | static OnKeyAction g_on_key_action = nullptr; | ||||||
|  | static void* g_user_ptr = nullptr; | ||||||
|  |  | ||||||
|  | LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { | ||||||
|  |   if (nCode == HC_ACTION && g_on_key_action) { | ||||||
|  |     KBDLLHOOKSTRUCT* kbData = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam); | ||||||
|  |  | ||||||
|  |     if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { | ||||||
|  |       g_on_key_action(kbData->vkCode, true, g_user_ptr); | ||||||
|  |     } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { | ||||||
|  |       g_on_key_action(kbData->vkCode, false, g_user_ptr); | ||||||
|  |     } | ||||||
|  |     return 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return CallNextHookEx(NULL, nCode, wParam, lParam); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | KeyboardCapturer::KeyboardCapturer() {} | ||||||
|  |  | ||||||
|  | KeyboardCapturer::~KeyboardCapturer() {} | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { | ||||||
|  |   g_on_key_action = on_key_action; | ||||||
|  |   g_user_ptr = user_ptr; | ||||||
|  |  | ||||||
|  |   keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0); | ||||||
|  |   if (!keyboard_hook_) { | ||||||
|  |     LOG_ERROR("Failed to install keyboard hook"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int KeyboardCapturer::Unhook() { | ||||||
|  |   UnhookWindowsHookEx(keyboard_hook_); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // apply remote keyboard commands to the local machine | ||||||
|  | int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { | ||||||
|  |   INPUT input = {0}; | ||||||
|  |   input.type = INPUT_KEYBOARD; | ||||||
|  |   input.ki.wVk = (WORD)key_code; | ||||||
|  |  | ||||||
|  |   if (!is_down) { | ||||||
|  |     input.ki.dwFlags = KEYEVENTF_KEYUP; | ||||||
|  |   } | ||||||
|  |   SendInput(1, &input, sizeof(INPUT)); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/device_controller/keyboard/windows/keyboard_capturer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-11-22 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _KEYBOARD_CAPTURER_H_ | ||||||
|  | #define _KEYBOARD_CAPTURER_H_ | ||||||
|  |  | ||||||
|  | #include <Windows.h> | ||||||
|  |  | ||||||
|  | #include "device_controller.h" | ||||||
|  |  | ||||||
|  | class KeyboardCapturer : public DeviceController { | ||||||
|  |  public: | ||||||
|  |   KeyboardCapturer(); | ||||||
|  |   virtual ~KeyboardCapturer(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual int Hook(OnKeyAction on_key_action, void *user_ptr); | ||||||
|  |   virtual int Unhook(); | ||||||
|  |   virtual int SendKeyboardCommand(int key_code, bool is_down); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   HHOOK keyboard_hook_ = nullptr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										304
									
								
								src/device_controller/keyboard_converter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								src/device_controller/keyboard_converter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,304 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-11-25 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _KEYBOARD_CONVERTER_H_ | ||||||
|  | #define _KEYBOARD_CONVERTER_H_ | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
|  | // Windows vkCode to macOS CGKeyCode (104 keys) | ||||||
|  | std::map<int, int> vkCodeToCGKeyCode = { | ||||||
|  |     // A-Z | ||||||
|  |     {0x41, 0x00},  // A | ||||||
|  |     {0x42, 0x0B},  // B | ||||||
|  |     {0x43, 0x08},  // C | ||||||
|  |     {0x44, 0x02},  // D | ||||||
|  |     {0x45, 0x0E},  // E | ||||||
|  |     {0x46, 0x03},  // F | ||||||
|  |     {0x47, 0x05},  // G | ||||||
|  |     {0x48, 0x04},  // H | ||||||
|  |     {0x49, 0x22},  // I | ||||||
|  |     {0x4A, 0x26},  // J | ||||||
|  |     {0x4B, 0x28},  // K | ||||||
|  |     {0x4C, 0x25},  // L | ||||||
|  |     {0x4D, 0x2E},  // M | ||||||
|  |     {0x4E, 0x2D},  // N | ||||||
|  |     {0x4F, 0x1F},  // O | ||||||
|  |     {0x50, 0x23},  // P | ||||||
|  |     {0x51, 0x0C},  // Q | ||||||
|  |     {0x52, 0x0F},  // R | ||||||
|  |     {0x53, 0x01},  // S | ||||||
|  |     {0x54, 0x11},  // T | ||||||
|  |     {0x55, 0x20},  // U | ||||||
|  |     {0x56, 0x09},  // V | ||||||
|  |     {0x57, 0x0D},  // W | ||||||
|  |     {0x58, 0x07},  // X | ||||||
|  |     {0x59, 0x10},  // Y | ||||||
|  |     {0x5A, 0x06},  // Z | ||||||
|  |  | ||||||
|  |     // 0-9 | ||||||
|  |     {0x30, 0x1D},  // 0 | ||||||
|  |     {0x31, 0x12},  // 1 | ||||||
|  |     {0x32, 0x13},  // 2 | ||||||
|  |     {0x33, 0x14},  // 3 | ||||||
|  |     {0x34, 0x15},  // 4 | ||||||
|  |     {0x35, 0x17},  // 5 | ||||||
|  |     {0x36, 0x16},  // 6 | ||||||
|  |     {0x37, 0x1A},  // 7 | ||||||
|  |     {0x38, 0x1C},  // 8 | ||||||
|  |     {0x39, 0x19},  // 9 | ||||||
|  |  | ||||||
|  |     // F1-F12 | ||||||
|  |     {0x70, 0x7A},  // F1 | ||||||
|  |     {0x71, 0x78},  // F2 | ||||||
|  |     {0x72, 0x63},  // F3 | ||||||
|  |     {0x73, 0x76},  // F4 | ||||||
|  |     {0x74, 0x60},  // F5 | ||||||
|  |     {0x75, 0x61},  // F6 | ||||||
|  |     {0x76, 0x62},  // F7 | ||||||
|  |     {0x77, 0x64},  // F8 | ||||||
|  |     {0x78, 0x65},  // F9 | ||||||
|  |     {0x79, 0x6D},  // F10 | ||||||
|  |     {0x7A, 0x67},  // F11 | ||||||
|  |     {0x7B, 0x6F},  // F12 | ||||||
|  |  | ||||||
|  |     // control keys | ||||||
|  |     {0x1B, 0x35},  // Escape | ||||||
|  |     {0x0D, 0x24},  // Enter | ||||||
|  |     {0x20, 0x31},  // Space | ||||||
|  |     {0x08, 0x33},  // Backspace | ||||||
|  |     {0x09, 0x30},  // Tab | ||||||
|  |     {0x2C, 0x74},  // Print Screen | ||||||
|  |     {0x2D, 0x72},  // Insert | ||||||
|  |     {0x2E, 0x75},  // Delete | ||||||
|  |     {0x24, 0x73},  // Home | ||||||
|  |     {0x23, 0x77},  // End | ||||||
|  |     {0x21, 0x79},  // Page Up | ||||||
|  |     {0x22, 0x7A},  // Page Down | ||||||
|  |  | ||||||
|  |     // arrow keys | ||||||
|  |     {0x25, 0x7B},  // Left Arrow | ||||||
|  |     {0x27, 0x7C},  // Right Arrow | ||||||
|  |     {0x26, 0x7E},  // Up Arrow | ||||||
|  |     {0x28, 0x7D},  // Down Arrow | ||||||
|  |  | ||||||
|  |     // numpad | ||||||
|  |     {0x60, 0x52},  // Numpad 0 | ||||||
|  |     {0x61, 0x53},  // Numpad 1 | ||||||
|  |     {0x62, 0x54},  // Numpad 2 | ||||||
|  |     {0x63, 0x55},  // Numpad 3 | ||||||
|  |     {0x64, 0x56},  // Numpad 4 | ||||||
|  |     {0x65, 0x57},  // Numpad 5 | ||||||
|  |     {0x66, 0x58},  // Numpad 6 | ||||||
|  |     {0x67, 0x59},  // Numpad 7 | ||||||
|  |     {0x68, 0x5B},  // Numpad 8 | ||||||
|  |     {0x69, 0x5C},  // Numpad 9 | ||||||
|  |     {0x6E, 0x41},  // Numpad . | ||||||
|  |     {0x6F, 0x4B},  // Numpad / | ||||||
|  |     {0x6A, 0x43},  // Numpad * | ||||||
|  |     {0x6D, 0x4E},  // Numpad - | ||||||
|  |     {0x6B, 0x45},  // Numpad + | ||||||
|  |  | ||||||
|  |     // symbol keys | ||||||
|  |     {0xBA, 0x29},  // ; (Semicolon) | ||||||
|  |     {0xDE, 0x27},  // ' (Quote) | ||||||
|  |     {0xC0, 0x32},  // ` (Backtick) | ||||||
|  |     {0xBC, 0x2B},  // , (Comma) | ||||||
|  |     {0xBE, 0x2F},  // . (Period) | ||||||
|  |     {0xBF, 0x2C},  // / (Slash) | ||||||
|  |     {0xDC, 0x2A},  // \ (Backslash) | ||||||
|  |     {0xDB, 0x21},  // [ (Left Bracket) | ||||||
|  |     {0xDD, 0x1E},  // ] (Right Bracket) | ||||||
|  |     {0xBD, 0x1B},  // - (Minus) | ||||||
|  |     {0xBB, 0x18},  // = (Equals) | ||||||
|  |  | ||||||
|  |     // modifier keys | ||||||
|  |     {0x14, 0x39},  // Caps Lock | ||||||
|  |     {0xA0, 0x38},  // Shift (Left) | ||||||
|  |     {0xA1, 0x3C},  // Shift (Right) | ||||||
|  |     {0xA2, 0x3B},  // Ctrl (Left) | ||||||
|  |     {0xA3, 0x3E},  // Ctrl (Right) | ||||||
|  |     {0xA4, 0x3A},  // Alt (Left) | ||||||
|  |     {0xA5, 0x3D},  // Alt (Right) | ||||||
|  |     {0x5B, 0x37},  // Left Command (Windows key) | ||||||
|  |     {0x5C, 0x36},  // Right Command | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // macOS CGKeyCode to Windows vkCode | ||||||
|  | std::map<int, int> CGKeyCodeToVkCode = { | ||||||
|  |     // A-Z | ||||||
|  |     {0x00, 0x41},  // A | ||||||
|  |     {0x0B, 0x42},  // B | ||||||
|  |     {0x08, 0x43},  // C | ||||||
|  |     {0x02, 0x44},  // D | ||||||
|  |     {0x0E, 0x45},  // E | ||||||
|  |     {0x03, 0x46},  // F | ||||||
|  |     {0x05, 0x47},  // G | ||||||
|  |     {0x04, 0x48},  // H | ||||||
|  |     {0x22, 0x49},  // I | ||||||
|  |     {0x26, 0x4A},  // J | ||||||
|  |     {0x28, 0x4B},  // K | ||||||
|  |     {0x25, 0x4C},  // L | ||||||
|  |     {0x2E, 0x4D},  // M | ||||||
|  |     {0x2D, 0x4E},  // N | ||||||
|  |     {0x1F, 0x4F},  // O | ||||||
|  |     {0x23, 0x50},  // P | ||||||
|  |     {0x0C, 0x51},  // Q | ||||||
|  |     {0x0F, 0x52},  // R | ||||||
|  |     {0x01, 0x53},  // S | ||||||
|  |     {0x11, 0x54},  // T | ||||||
|  |     {0x20, 0x55},  // U | ||||||
|  |     {0x09, 0x56},  // V | ||||||
|  |     {0x0D, 0x57},  // W | ||||||
|  |     {0x07, 0x58},  // X | ||||||
|  |     {0x10, 0x59},  // Y | ||||||
|  |     {0x06, 0x5A},  // Z | ||||||
|  |  | ||||||
|  |     // 0-9 | ||||||
|  |     {0x1D, 0x30},  // 0 | ||||||
|  |     {0x12, 0x31},  // 1 | ||||||
|  |     {0x13, 0x32},  // 2 | ||||||
|  |     {0x14, 0x33},  // 3 | ||||||
|  |     {0x15, 0x34},  // 4 | ||||||
|  |     {0x17, 0x35},  // 5 | ||||||
|  |     {0x16, 0x36},  // 6 | ||||||
|  |     {0x1A, 0x37},  // 7 | ||||||
|  |     {0x1C, 0x38},  // 8 | ||||||
|  |     {0x19, 0x39},  // 9 | ||||||
|  |  | ||||||
|  |     // F1-F12 | ||||||
|  |     {0x7A, 0x70},  // F1 | ||||||
|  |     {0x78, 0x71},  // F2 | ||||||
|  |     {0x63, 0x72},  // F3 | ||||||
|  |     {0x76, 0x73},  // F4 | ||||||
|  |     {0x60, 0x74},  // F5 | ||||||
|  |     {0x61, 0x75},  // F6 | ||||||
|  |     {0x62, 0x76},  // F7 | ||||||
|  |     {0x64, 0x77},  // F8 | ||||||
|  |     {0x65, 0x78},  // F9 | ||||||
|  |     {0x6D, 0x79},  // F10 | ||||||
|  |     {0x67, 0x7A},  // F11 | ||||||
|  |     {0x6F, 0x7B},  // F12 | ||||||
|  |  | ||||||
|  |     // control keys | ||||||
|  |     {0x35, 0x1B},  // Escape | ||||||
|  |     {0x24, 0x0D},  // Enter | ||||||
|  |     {0x31, 0x20},  // Space | ||||||
|  |     {0x33, 0x08},  // Backspace | ||||||
|  |     {0x30, 0x09},  // Tab | ||||||
|  |     {0x74, 0x2C},  // Print Screen | ||||||
|  |     {0x72, 0x2D},  // Insert | ||||||
|  |     {0x75, 0x2E},  // Delete | ||||||
|  |     {0x73, 0x24},  // Home | ||||||
|  |     {0x77, 0x23},  // End | ||||||
|  |     {0x79, 0x21},  // Page Up | ||||||
|  |     {0x7A, 0x22},  // Page Down | ||||||
|  |  | ||||||
|  |     // arrow keys | ||||||
|  |     {0x7B, 0x25},  // Left Arrow | ||||||
|  |     {0x7C, 0x27},  // Right Arrow | ||||||
|  |     {0x7E, 0x26},  // Up Arrow | ||||||
|  |     {0x7D, 0x28},  // Down Arrow | ||||||
|  |  | ||||||
|  |     // numpad | ||||||
|  |     {0x52, 0x60},  // Numpad 0 | ||||||
|  |     {0x53, 0x61},  // Numpad 1 | ||||||
|  |     {0x54, 0x62},  // Numpad 2 | ||||||
|  |     {0x55, 0x63},  // Numpad 3 | ||||||
|  |     {0x56, 0x64},  // Numpad 4 | ||||||
|  |     {0x57, 0x65},  // Numpad 5 | ||||||
|  |     {0x58, 0x66},  // Numpad 6 | ||||||
|  |     {0x59, 0x67},  // Numpad 7 | ||||||
|  |     {0x5B, 0x68},  // Numpad 8 | ||||||
|  |     {0x5C, 0x69},  // Numpad 9 | ||||||
|  |     {0x41, 0x6E},  // Numpad . | ||||||
|  |     {0x4B, 0x6F},  // Numpad / | ||||||
|  |     {0x43, 0x6A},  // Numpad * | ||||||
|  |     {0x4E, 0x6D},  // Numpad - | ||||||
|  |     {0x45, 0x6B},  // Numpad + | ||||||
|  |  | ||||||
|  |     // symbol keys | ||||||
|  |     {0x29, 0xBA},  // ; (Semicolon) | ||||||
|  |     {0x27, 0xDE},  // ' (Quote) | ||||||
|  |     {0x32, 0xC0},  // ` (Backtick) | ||||||
|  |     {0x2B, 0xBC},  // , (Comma) | ||||||
|  |     {0x2F, 0xBE},  // . (Period) | ||||||
|  |     {0x2C, 0xBF},  // / (Slash) | ||||||
|  |     {0x2A, 0xDC},  // \ (Backslash) | ||||||
|  |     {0x21, 0xDB},  // [ (Left Bracket) | ||||||
|  |     {0x1E, 0xDD},  // ] (Right Bracket) | ||||||
|  |     {0x1B, 0xBD},  // - (Minus) | ||||||
|  |     {0x18, 0xBB},  // = (Equals) | ||||||
|  |  | ||||||
|  |     // modifier keys | ||||||
|  |     {0x39, 0x14},  // Caps Lock | ||||||
|  |     {0x38, 0xA0},  // Shift (Left) | ||||||
|  |     {0x3C, 0xA1},  // Shift (Right) | ||||||
|  |     {0x3B, 0xA2},  // Control (Left) | ||||||
|  |     {0x3E, 0xA3},  // Control (Right) | ||||||
|  |     {0x3A, 0xA4},  // Alt (Left) | ||||||
|  |     {0x3D, 0xA5},  // Alt (Right) | ||||||
|  |     {0x37, 0x5B},  // Left Command (Windows key) | ||||||
|  |     {0x36, 0x5C},  // Right Command | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Windows vkCode to X11 KeySym | ||||||
|  | std::map<int, int> vkCodeToX11KeySym = { | ||||||
|  |     {0x41, 0x0041}, {0x42, 0x0042}, {0x43, 0x0043}, {0x44, 0x0044}, | ||||||
|  |     {0x45, 0x0045}, {0x46, 0x0046}, {0x47, 0x0047}, {0x48, 0x0048}, | ||||||
|  |     {0x49, 0x0049}, {0x4A, 0x004A}, {0x4B, 0x004B}, {0x4C, 0x004C}, | ||||||
|  |     {0x4D, 0x004D}, {0x4E, 0x004E}, {0x4F, 0x004F}, {0x50, 0x0050}, | ||||||
|  |     {0x51, 0x0051}, {0x52, 0x0052}, {0x53, 0x0053}, {0x54, 0x0054}, | ||||||
|  |     {0x55, 0x0055}, {0x56, 0x0056}, {0x57, 0x0057}, {0x58, 0x0058}, | ||||||
|  |     {0x59, 0x0059}, {0x5A, 0x005A}, {0x30, 0x0030}, {0x31, 0x0031}, | ||||||
|  |     {0x32, 0x0032}, {0x33, 0x0033}, {0x34, 0x0034}, {0x35, 0x0035}, | ||||||
|  |     {0x36, 0x0036}, {0x37, 0x0037}, {0x38, 0x0038}, {0x39, 0x0039}, | ||||||
|  |     {0x1B, 0xFF1B}, {0x0D, 0xFF0D}, {0x20, 0x0020}, {0x08, 0xFF08}, | ||||||
|  |     {0x09, 0xFF09}, {0x25, 0xFF51}, {0x27, 0xFF53}, {0x26, 0xFF52}, | ||||||
|  |     {0x28, 0xFF54}, {0x70, 0xFFBE}, {0x71, 0xFFBF}, {0x72, 0xFFC0}, | ||||||
|  |     {0x73, 0xFFC1}, {0x74, 0xFFC2}, {0x75, 0xFFC3}, {0x76, 0xFFC4}, | ||||||
|  |     {0x77, 0xFFC5}, {0x78, 0xFFC6}, {0x79, 0xFFC7}, {0x7A, 0xFFC8}, | ||||||
|  |     {0x7B, 0xFFC9}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // X11 KeySym to Windows vkCode | ||||||
|  | std::map<int, int> x11KeySymToVkCode = []() { | ||||||
|  |   std::map<int, int> result; | ||||||
|  |   for (const auto& pair : vkCodeToX11KeySym) { | ||||||
|  |     result[pair.second] = pair.first; | ||||||
|  |   } | ||||||
|  |   return result; | ||||||
|  | }(); | ||||||
|  |  | ||||||
|  | // macOS CGKeyCode to X11 KeySym | ||||||
|  | std::map<int, int> cgKeyCodeToX11KeySym = { | ||||||
|  |     {0x00, 0x0041}, {0x0B, 0x0042}, {0x08, 0x0043}, {0x02, 0x0044}, | ||||||
|  |     {0x0E, 0x0045}, {0x03, 0x0046}, {0x05, 0x0047}, {0x04, 0x0048}, | ||||||
|  |     {0x22, 0x0049}, {0x26, 0x004A}, {0x28, 0x004B}, {0x25, 0x004C}, | ||||||
|  |     {0x2E, 0x004D}, {0x2D, 0x004E}, {0x1F, 0x004F}, {0x23, 0x0050}, | ||||||
|  |     {0x0C, 0x0051}, {0x0F, 0x0052}, {0x01, 0x0053}, {0x11, 0x0054}, | ||||||
|  |     {0x20, 0x0055}, {0x09, 0x0056}, {0x0D, 0x0057}, {0x07, 0x0058}, | ||||||
|  |     {0x10, 0x0059}, {0x06, 0x005A}, {0x12, 0x0031}, {0x13, 0x0032}, | ||||||
|  |     {0x14, 0x0033}, {0x15, 0x0034}, {0x17, 0x0035}, {0x16, 0x0036}, | ||||||
|  |     {0x1A, 0x0037}, {0x1C, 0x0038}, {0x19, 0x0039}, {0x1D, 0x0030}, | ||||||
|  |     {0x35, 0xFF1B}, {0x24, 0xFF0D}, {0x31, 0x0020}, {0x33, 0xFF08}, | ||||||
|  |     {0x30, 0xFF09}, {0x7B, 0xFF51}, {0x7C, 0xFF53}, {0x7E, 0xFF52}, | ||||||
|  |     {0x7D, 0xFF54}, {0x7A, 0xFFBE}, {0x78, 0xFFBF}, {0x63, 0xFFC0}, | ||||||
|  |     {0x76, 0xFFC1}, {0x60, 0xFFC2}, {0x61, 0xFFC3}, {0x62, 0xFFC4}, | ||||||
|  |     {0x64, 0xFFC5}, {0x65, 0xFFC6}, {0x6D, 0xFFC7}, {0x67, 0xFFC8}, | ||||||
|  |     {0x6F, 0xFFC9}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // X11 KeySym to macOS CGKeyCode | ||||||
|  | std::map<int, int> x11KeySymToCgKeyCode = []() { | ||||||
|  |   std::map<int, int> result; | ||||||
|  |   for (const auto& pair : cgKeyCodeToX11KeySym) { | ||||||
|  |     result[pair.second] = pair.first; | ||||||
|  |   } | ||||||
|  |   return result; | ||||||
|  | }(); | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -1,127 +1,124 @@ | |||||||
| #include "mouse_controller.h" | #include "mouse_controller.h" | ||||||
|  |  | ||||||
| #include "log.h" | #include <X11/extensions/XTest.h> | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
| MouseController::MouseController() {} | MouseController::MouseController() {} | ||||||
|  |  | ||||||
| MouseController::~MouseController() { | MouseController::~MouseController() { Destroy(); } | ||||||
|   if (uinput_fd_) { |  | ||||||
|     ioctl(uinput_fd_, UI_DEV_DESTROY); |  | ||||||
|     close(uinput_fd_); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MouseController::Init(int screen_width, int screen_height) { | int MouseController::Init(std::vector<DisplayInfo> display_info_list) { | ||||||
|   screen_width_ = screen_width; |   display_info_list_ = display_info_list; | ||||||
|   screen_height_ = screen_height; |   display_ = XOpenDisplay(NULL); | ||||||
|  |   if (!display_) { | ||||||
|   uinput_fd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK); |     LOG_ERROR("Cannot connect to X server"); | ||||||
|   if (uinput_fd_ < 0) { |  | ||||||
|     LOG_ERROR("Cannot open device: /dev/uinput"); |  | ||||||
|     return -1; |     return -1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ioctl(uinput_fd_, UI_SET_EVBIT, EV_KEY); |   root_ = DefaultRootWindow(display_); | ||||||
|   ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_RIGHT); |  | ||||||
|   ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_LEFT); |  | ||||||
|   ioctl(uinput_fd_, UI_SET_EVBIT, EV_ABS); |  | ||||||
|   ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_X); |  | ||||||
|   ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_Y); |  | ||||||
|   ioctl(uinput_fd_, UI_SET_EVBIT, EV_REL); |  | ||||||
|  |  | ||||||
|   struct uinput_user_dev uidev; |   int event_base, error_base, major_version, minor_version; | ||||||
|   memset(&uidev, 0, sizeof(uidev)); |   if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version, | ||||||
|   snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "VirtualMouse"); |                            &minor_version)) { | ||||||
|   uidev.id.bustype = BUS_USB; |     LOG_ERROR("XTest extension not available"); | ||||||
|   uidev.id.version = 1; |     XCloseDisplay(display_); | ||||||
|   uidev.id.vendor = 0x1; |     return -2; | ||||||
|   uidev.id.product = 0x1; |  | ||||||
|   uidev.absmin[ABS_X] = 0; |  | ||||||
|   uidev.absmax[ABS_X] = screen_width_; |  | ||||||
|   uidev.absmin[ABS_Y] = 0; |  | ||||||
|   uidev.absmax[ABS_Y] = screen_height_; |  | ||||||
|  |  | ||||||
|   int res_uidev = write(uinput_fd_, &uidev, sizeof(uidev)); |  | ||||||
|   ioctl(uinput_fd_, UI_DEV_CREATE); |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MouseController::Destroy() { return 0; } |  | ||||||
|  |  | ||||||
| int MouseController::SendCommand(RemoteAction remote_action) { |  | ||||||
|   int mouse_pos_x = remote_action.m.x * screen_width_ / 1280; |  | ||||||
|   int mouse_pos_y = remote_action.m.y * screen_height_ / 720; |  | ||||||
|  |  | ||||||
|   if (remote_action.type == ControlType::mouse) { |  | ||||||
|     struct input_event event; |  | ||||||
|     memset(&event, 0, sizeof(event)); |  | ||||||
|     gettimeofday(&event.time, NULL); |  | ||||||
|  |  | ||||||
|     if (remote_action.m.flag == MouseFlag::left_down) { |  | ||||||
|       SimulateKeyDown(uinput_fd_, BTN_LEFT); |  | ||||||
|     } else if (remote_action.m.flag == MouseFlag::left_up) { |  | ||||||
|       SimulateKeyUp(uinput_fd_, BTN_LEFT); |  | ||||||
|     } else if (remote_action.m.flag == MouseFlag::right_down) { |  | ||||||
|       SimulateKeyDown(uinput_fd_, BTN_RIGHT); |  | ||||||
|     } else if (remote_action.m.flag == MouseFlag::right_up) { |  | ||||||
|       SimulateKeyUp(uinput_fd_, BTN_RIGHT); |  | ||||||
|     } else { |  | ||||||
|       SetMousePosition(uinput_fd_, mouse_pos_x, mouse_pos_y); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MouseController::SimulateKeyDown(int fd, int kval) { | int MouseController::Destroy() { | ||||||
|   int res_ev = 0; |   if (display_) { | ||||||
|   struct input_event event; |     XCloseDisplay(display_); | ||||||
|   memset(&event, 0, sizeof(event)); |     display_ = nullptr; | ||||||
|   gettimeofday(&event.time, 0); |   } | ||||||
|  |   return 0; | ||||||
|   event.type = EV_KEY; |  | ||||||
|   event.value = 1; |  | ||||||
|   event.code = kval; |  | ||||||
|   res_ev = write(fd, &event, sizeof(event)); |  | ||||||
|  |  | ||||||
|   event.type = EV_SYN; |  | ||||||
|   event.value = 0; |  | ||||||
|   event.code = SYN_REPORT; |  | ||||||
|   res_ev = write(fd, &event, sizeof(event)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void MouseController::SimulateKeyUp(int fd, int kval) { | int MouseController::SendMouseCommand(RemoteAction remote_action, | ||||||
|   int res_ev = 0; |                                       int display_index) { | ||||||
|   struct input_event event; |   switch (remote_action.type) { | ||||||
|   memset(&event, 0, sizeof(event)); |     case mouse: | ||||||
|   gettimeofday(&event.time, 0); |       switch (remote_action.m.flag) { | ||||||
|  |         case MouseFlag::move: | ||||||
|  |           SetMousePosition( | ||||||
|  |               static_cast<int>(remote_action.m.x * | ||||||
|  |                                    display_info_list_[display_index].width + | ||||||
|  |                                display_info_list_[display_index].left), | ||||||
|  |               static_cast<int>(remote_action.m.y * | ||||||
|  |                                    display_info_list_[display_index].height + | ||||||
|  |                                display_info_list_[display_index].top)); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::left_down: | ||||||
|  |           XTestFakeButtonEvent(display_, 1, True, CurrentTime); | ||||||
|  |           XFlush(display_); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::left_up: | ||||||
|  |           XTestFakeButtonEvent(display_, 1, False, CurrentTime); | ||||||
|  |           XFlush(display_); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::right_down: | ||||||
|  |           XTestFakeButtonEvent(display_, 3, True, CurrentTime); | ||||||
|  |           XFlush(display_); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::right_up: | ||||||
|  |           XTestFakeButtonEvent(display_, 3, False, CurrentTime); | ||||||
|  |           XFlush(display_); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::middle_down: | ||||||
|  |           XTestFakeButtonEvent(display_, 2, True, CurrentTime); | ||||||
|  |           XFlush(display_); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::middle_up: | ||||||
|  |           XTestFakeButtonEvent(display_, 2, False, CurrentTime); | ||||||
|  |           XFlush(display_); | ||||||
|  |           break; | ||||||
|  |         case MouseFlag::wheel_vertical: { | ||||||
|  |           if (remote_action.m.s > 0) { | ||||||
|  |             SimulateMouseWheel(4, remote_action.m.s); | ||||||
|  |           } else if (remote_action.m.s < 0) { | ||||||
|  |             SimulateMouseWheel(5, -remote_action.m.s); | ||||||
|  |           } | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         case MouseFlag::wheel_horizontal: { | ||||||
|  |           if (remote_action.m.s > 0) { | ||||||
|  |             SimulateMouseWheel(6, remote_action.m.s); | ||||||
|  |           } else if (remote_action.m.s < 0) { | ||||||
|  |             SimulateMouseWheel(7, -remote_action.m.s); | ||||||
|  |           } | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   event.type = EV_KEY; |   return 0; | ||||||
|   event.value = 0; |  | ||||||
|   event.code = kval; |  | ||||||
|   res_ev = write(fd, &event, sizeof(event)); |  | ||||||
|  |  | ||||||
|   event.type = EV_SYN; |  | ||||||
|   event.value = 0; |  | ||||||
|   event.code = SYN_REPORT; |  | ||||||
|   res_ev = write(fd, &event, sizeof(event)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void MouseController::SetMousePosition(int fd, int x, int y) { | void MouseController::SetMousePosition(int x, int y) { | ||||||
|   struct input_event ev[2], ev_sync; |   XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y); | ||||||
|   memset(ev, 0, sizeof(ev)); |   XFlush(display_); | ||||||
|   memset(&ev_sync, 0, sizeof(ev_sync)); | } | ||||||
|  |  | ||||||
|   ev[0].type = EV_ABS; | void MouseController::SimulateKeyDown(int kval) { | ||||||
|   ev[0].code = ABS_X; |   XTestFakeKeyEvent(display_, kval, True, CurrentTime); | ||||||
|   ev[0].value = x; |   XFlush(display_); | ||||||
|   ev[1].type = EV_ABS; | } | ||||||
|   ev[1].code = ABS_Y; |  | ||||||
|   ev[1].value = y; | void MouseController::SimulateKeyUp(int kval) { | ||||||
|   int res_w = write(fd, ev, sizeof(ev)); |   XTestFakeKeyEvent(display_, kval, False, CurrentTime); | ||||||
|  |   XFlush(display_); | ||||||
|   ev_sync.type = EV_SYN; | } | ||||||
|   ev_sync.value = 0; |  | ||||||
|   ev_sync.code = 0; | void MouseController::SimulateMouseWheel(int direction_button, int count) { | ||||||
|   int res_ev_sync = write(fd, &ev_sync, sizeof(ev_sync)); |   for (int i = 0; i < count; ++i) { | ||||||
|  |     XTestFakeButtonEvent(display_, direction_button, True, CurrentTime); | ||||||
|  |     XTestFakeButtonEvent(display_, direction_button, False, CurrentTime); | ||||||
|  |   } | ||||||
|  |   XFlush(display_); | ||||||
| } | } | ||||||
| @@ -1,19 +1,18 @@ | |||||||
| /* | /* | ||||||
|  * @Author: DI JUNKUN |  * @Author: DI JUNKUN | ||||||
|  * @Date: 2023-12-14 |  * @Date: 2025-05-07 | ||||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. |  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #ifndef _MOUSE_CONTROLLER_H_ | #ifndef _MOUSE_CONTROLLER_H_ | ||||||
| #define _MOUSE_CONTROLLER_H_ | #define _MOUSE_CONTROLLER_H_ | ||||||
|  |  | ||||||
| #include <fcntl.h> | #include <X11/Xlib.h> | ||||||
| #include <linux/uinput.h> | #include <X11/Xutil.h> | ||||||
| #include <net/if.h> |  | ||||||
| #include <sys/ioctl.h> |  | ||||||
| #include <sys/socket.h> |  | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| #include "device_controller.h" | #include "device_controller.h" | ||||||
|  |  | ||||||
| class MouseController : public DeviceController { | class MouseController : public DeviceController { | ||||||
| @@ -22,18 +21,19 @@ class MouseController : public DeviceController { | |||||||
|   virtual ~MouseController(); |   virtual ~MouseController(); | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual int Init(int screen_width, int screen_height); |   virtual int Init(std::vector<DisplayInfo> display_info_list); | ||||||
|   virtual int Destroy(); |   virtual int Destroy(); | ||||||
|   virtual int SendCommand(RemoteAction remote_action); |   virtual int SendMouseCommand(RemoteAction remote_action, int display_index); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   void SimulateKeyDown(int fd, int kval); |   void SimulateKeyDown(int kval); | ||||||
|   void SimulateKeyUp(int fd, int kval); |   void SimulateKeyUp(int kval); | ||||||
|   void SetMousePosition(int fd, int x, int y); |   void SetMousePosition(int x, int y); | ||||||
|  |   void SimulateMouseWheel(int direction_button, int count); | ||||||
|  |  | ||||||
|  private: |   Display* display_ = nullptr; | ||||||
|   int uinput_fd_; |   Window root_ = 0; | ||||||
|   struct uinput_user_dev uinput_dev_; |   std::vector<DisplayInfo> display_info_list_; | ||||||
|   int screen_width_ = 0; |   int screen_width_ = 0; | ||||||
|   int screen_height_ = 0; |   int screen_height_ = 0; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -2,48 +2,100 @@ | |||||||
|  |  | ||||||
| #include <ApplicationServices/ApplicationServices.h> | #include <ApplicationServices/ApplicationServices.h> | ||||||
|  |  | ||||||
| #include "log.h" | #include "rd_log.h" | ||||||
|  |  | ||||||
| MouseController::MouseController() {} | MouseController::MouseController() {} | ||||||
|  |  | ||||||
| MouseController::~MouseController() {} | MouseController::~MouseController() {} | ||||||
|  |  | ||||||
| int MouseController::Init(int screen_width, int screen_height) { | int MouseController::Init(std::vector<DisplayInfo> display_info_list) { | ||||||
|   screen_width_ = screen_width; |   display_info_list_ = display_info_list; | ||||||
|   screen_height_ = screen_height; |  | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int MouseController::Destroy() { return 0; } | int MouseController::Destroy() { return 0; } | ||||||
|  |  | ||||||
| int MouseController::SendCommand(RemoteAction remote_action) { | int MouseController::SendMouseCommand(RemoteAction remote_action, | ||||||
|   int mouse_pos_x = remote_action.m.x * screen_width_ / 1280; |                                       int display_index) { | ||||||
|   int mouse_pos_y = remote_action.m.y * screen_height_ / 720; |   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) { |   if (remote_action.type == ControlType::mouse) { | ||||||
|     CGEventRef mouse_event; |     CGEventRef mouse_event = nullptr; | ||||||
|     CGEventType mouse_type; |     CGEventType mouse_type; | ||||||
|  |     CGMouseButton mouse_button; | ||||||
|  |     CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y); | ||||||
|  |  | ||||||
|     if (remote_action.m.flag == MouseFlag::left_down) { |     switch (remote_action.m.flag) { | ||||||
|  |       case MouseFlag::left_down: | ||||||
|         mouse_type = kCGEventLeftMouseDown; |         mouse_type = kCGEventLeftMouseDown; | ||||||
|     } else if (remote_action.m.flag == MouseFlag::left_up) { |         left_dragging_ = true; | ||||||
|  |         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||||
|  |                                               kCGMouseButtonLeft); | ||||||
|  |         break; | ||||||
|  |       case MouseFlag::left_up: | ||||||
|         mouse_type = kCGEventLeftMouseUp; |         mouse_type = kCGEventLeftMouseUp; | ||||||
|     } else if (remote_action.m.flag == MouseFlag::right_down) { |         left_dragging_ = false; | ||||||
|  |         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||||
|  |                                               kCGMouseButtonLeft); | ||||||
|  |         break; | ||||||
|  |       case MouseFlag::right_down: | ||||||
|         mouse_type = kCGEventRightMouseDown; |         mouse_type = kCGEventRightMouseDown; | ||||||
|     } else if (remote_action.m.flag == MouseFlag::right_up) { |         right_dragging_ = true; | ||||||
|  |         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||||
|  |                                               kCGMouseButtonRight); | ||||||
|  |         break; | ||||||
|  |       case MouseFlag::right_up: | ||||||
|         mouse_type = kCGEventRightMouseUp; |         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 { |         } else { | ||||||
|           mouse_type = kCGEventMouseMoved; |           mouse_type = kCGEventMouseMoved; | ||||||
|  |           mouse_button = kCGMouseButtonLeft; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, |         mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point, | ||||||
|                                           CGPointMake(mouse_pos_x, mouse_pos_y), |                                               mouse_button); | ||||||
|                                           kCGMouseButtonLeft); |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (mouse_event) { | ||||||
|       CGEventPost(kCGHIDEventTap, mouse_event); |       CGEventPost(kCGHIDEventTap, mouse_event); | ||||||
|       CFRelease(mouse_event); |       CFRelease(mouse_event); | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
| @@ -7,6 +7,8 @@ | |||||||
| #ifndef _MOUSE_CONTROLLER_H_ | #ifndef _MOUSE_CONTROLLER_H_ | ||||||
| #define _MOUSE_CONTROLLER_H_ | #define _MOUSE_CONTROLLER_H_ | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| #include "device_controller.h" | #include "device_controller.h" | ||||||
|  |  | ||||||
| class MouseController : public DeviceController { | class MouseController : public DeviceController { | ||||||
| @@ -15,13 +17,14 @@ class MouseController : public DeviceController { | |||||||
|   virtual ~MouseController(); |   virtual ~MouseController(); | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual int Init(int screen_width, int screen_height); |   virtual int Init(std::vector<DisplayInfo> display_info_list); | ||||||
|   virtual int Destroy(); |   virtual int Destroy(); | ||||||
|   virtual int SendCommand(RemoteAction remote_action); |   virtual int SendMouseCommand(RemoteAction remote_action, int display_index); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   int screen_width_ = 0; |   std::vector<DisplayInfo> display_info_list_; | ||||||
|   int screen_height_ = 0; |   bool left_dragging_ = false; | ||||||
|  |   bool right_dragging_ = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| @@ -1,39 +1,64 @@ | |||||||
| #include "mouse_controller.h" | #include "mouse_controller.h" | ||||||
|  |  | ||||||
| #include "log.h" | #include "rd_log.h" | ||||||
|  |  | ||||||
| MouseController::MouseController() {} | MouseController::MouseController() {} | ||||||
|  |  | ||||||
| MouseController::~MouseController() {} | MouseController::~MouseController() {} | ||||||
|  |  | ||||||
| int MouseController::Init(int screen_width, int screen_height) { | int MouseController::Init(std::vector<DisplayInfo> display_info_list) { | ||||||
|   screen_width_ = screen_width; |   display_info_list_ = display_info_list; | ||||||
|   screen_height_ = screen_height; |  | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int MouseController::Destroy() { return 0; } | int MouseController::Destroy() { return 0; } | ||||||
|  |  | ||||||
| int MouseController::SendCommand(RemoteAction remote_action) { | int MouseController::SendMouseCommand(RemoteAction remote_action, | ||||||
|  |                                       int display_index) { | ||||||
|   INPUT ip; |   INPUT ip; | ||||||
|  |  | ||||||
|   if (remote_action.type == ControlType::mouse) { |   if (remote_action.type == ControlType::mouse) { | ||||||
|     ip.type = INPUT_MOUSE; |     ip.type = INPUT_MOUSE; | ||||||
|     ip.mi.dx = remote_action.m.x * screen_width_ / 1280; |     ip.mi.dx = | ||||||
|     ip.mi.dy = remote_action.m.y * screen_height_ / 720; |         (LONG)(remote_action.m.x * display_info_list_[display_index].width) + | ||||||
|     if (remote_action.m.flag == MouseFlag::left_down) { |         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; |         ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE; | ||||||
|     } else if (remote_action.m.flag == MouseFlag::left_up) { |         break; | ||||||
|  |       case MouseFlag::left_up: | ||||||
|         ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE; |         ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE; | ||||||
|     } else if (remote_action.m.flag == MouseFlag::right_down) { |         break; | ||||||
|  |       case MouseFlag::right_down: | ||||||
|         ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE; |         ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE; | ||||||
|     } else if (remote_action.m.flag == MouseFlag::right_up) { |         break; | ||||||
|  |       case MouseFlag::right_up: | ||||||
|         ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE; |         ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE; | ||||||
|     } else { |         break; | ||||||
|  |       case MouseFlag::middle_down: | ||||||
|  |         ip.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_ABSOLUTE; | ||||||
|  |         break; | ||||||
|  |       case MouseFlag::middle_up: | ||||||
|  |         ip.mi.dwFlags = MOUSEEVENTF_MIDDLEUP | MOUSEEVENTF_ABSOLUTE; | ||||||
|  |         break; | ||||||
|  |       case MouseFlag::wheel_vertical: | ||||||
|  |         ip.mi.dwFlags = MOUSEEVENTF_WHEEL; | ||||||
|  |         ip.mi.mouseData = remote_action.m.s * 120; | ||||||
|  |         break; | ||||||
|  |       case MouseFlag::wheel_horizontal: | ||||||
|  |         ip.mi.dwFlags = MOUSEEVENTF_HWHEEL; | ||||||
|  |         ip.mi.mouseData = remote_action.m.s * 120; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|         ip.mi.dwFlags = MOUSEEVENTF_MOVE; |         ip.mi.dwFlags = MOUSEEVENTF_MOVE; | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
|     ip.mi.mouseData = 0; |  | ||||||
|     ip.mi.time = 0; |     ip.mi.time = 0; | ||||||
|  |  | ||||||
|     SetCursorPos(ip.mi.dx, ip.mi.dy); |     SetCursorPos(ip.mi.dx, ip.mi.dy); | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ | |||||||
| #ifndef _MOUSE_CONTROLLER_H_ | #ifndef _MOUSE_CONTROLLER_H_ | ||||||
| #define _MOUSE_CONTROLLER_H_ | #define _MOUSE_CONTROLLER_H_ | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| #include "device_controller.h" | #include "device_controller.h" | ||||||
|  |  | ||||||
| class MouseController : public DeviceController { | class MouseController : public DeviceController { | ||||||
| @@ -15,13 +17,12 @@ class MouseController : public DeviceController { | |||||||
|   virtual ~MouseController(); |   virtual ~MouseController(); | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual int Init(int screen_width, int screen_height); |   virtual int Init(std::vector<DisplayInfo> display_info_list); | ||||||
|   virtual int Destroy(); |   virtual int Destroy(); | ||||||
|   virtual int SendCommand(RemoteAction remote_action); |   virtual int SendMouseCommand(RemoteAction remote_action, int display_index); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   int screen_width_ = 0; |   std::vector<DisplayInfo> display_info_list_; | ||||||
|   int screen_height_ = 0; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| @@ -1,19 +1,17 @@ | |||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
| #ifdef REMOTE_DESK_DEBUG | #ifdef DESK_PORT_DEBUG | ||||||
| #pragma comment(linker, "/subsystem:\"console\"") | #pragma comment(linker, "/subsystem:\"console\"") | ||||||
| #else | #else | ||||||
| #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") | #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") | ||||||
| #endif | #endif | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #include "log.h" | #include "rd_log.h" | ||||||
| #include "main_window.h" | #include "render.h" | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { | int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) { | ||||||
|   LOG_INFO("Remote desk"); |   Render render; | ||||||
|   MainWindow main_window; |   render.Run(); | ||||||
|  |  | ||||||
|   main_window.Run(); |  | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
| @@ -1,874 +0,0 @@ | |||||||
| #include <SDL.h> |  | ||||||
| #include <stdio.h> |  | ||||||
| #ifdef _WIN32 |  | ||||||
| #ifdef REMOTE_DESK_DEBUG |  | ||||||
| #pragma comment(linker, "/subsystem:\"console\"") |  | ||||||
| #else |  | ||||||
| #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #include <stdio.h> |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <chrono> |  | ||||||
| #include <cstring> |  | ||||||
| #include <fstream> |  | ||||||
| #include <iostream> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
|  |  | ||||||
| #include "../../thirdparty/projectx/src/interface/x.h" |  | ||||||
| #include "config_center.h" |  | ||||||
| #include "device_controller_factory.h" |  | ||||||
| #include "imgui.h" |  | ||||||
| #include "imgui_impl_sdl2.h" |  | ||||||
| #include "imgui_impl_sdlrenderer2.h" |  | ||||||
| #include "log.h" |  | ||||||
| #include "platform.h" |  | ||||||
| #include "screen_capturer_factory.h" |  | ||||||
|  |  | ||||||
| #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 |  | ||||||
|  |  | ||||||
| #ifdef REMOTE_DESK_DEBUG |  | ||||||
| #define MOUSE_CONTROL 0 |  | ||||||
| #else |  | ||||||
| #define MOUSE_CONTROL 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #define CHINESE_FONT 1 |  | ||||||
|  |  | ||||||
| int screen_w = 1280, screen_h = 720; |  | ||||||
| int window_w = 1280, window_h = 720; |  | ||||||
| const int pixel_w = 1280, pixel_h = 720; |  | ||||||
|  |  | ||||||
| unsigned char dst_buffer[pixel_w * pixel_h * 3 / 2]; |  | ||||||
| unsigned char audio_buffer[960]; |  | ||||||
| SDL_Texture *sdlTexture = nullptr; |  | ||||||
| SDL_Renderer *sdlRenderer = nullptr; |  | ||||||
| SDL_Rect sdlRect; |  | ||||||
| SDL_Window *window; |  | ||||||
| static SDL_AudioDeviceID input_dev; |  | ||||||
| static SDL_AudioDeviceID output_dev; |  | ||||||
|  |  | ||||||
| uint32_t start_time, end_time, elapsed_time; |  | ||||||
| uint32_t frame_count = 0; |  | ||||||
| int fps = 0; |  | ||||||
|  |  | ||||||
| static std::atomic<bool> audio_buffer_fresh{false}; |  | ||||||
| static uint32_t last_ts = 0; |  | ||||||
|  |  | ||||||
| int dst_bufsize; |  | ||||||
| struct SwrContext *swr_ctx; |  | ||||||
|  |  | ||||||
| int ret; |  | ||||||
|  |  | ||||||
| int audio_len = 0; |  | ||||||
|  |  | ||||||
| std::string window_title = "Remote Desk Client"; |  | ||||||
| std::string connection_status_str = "-"; |  | ||||||
| std::string signal_status_str = "-"; |  | ||||||
|  |  | ||||||
| std::atomic<SignalStatus> signal_status{SignalStatus::SignalClosed}; |  | ||||||
| std::atomic<ConnectionStatus> connection_status{ConnectionStatus::Closed}; |  | ||||||
|  |  | ||||||
| // Refresh Event |  | ||||||
| #define REFRESH_EVENT (SDL_USEREVENT + 1) |  | ||||||
| #define QUIT_EVENT (SDL_USEREVENT + 2) |  | ||||||
|  |  | ||||||
| typedef struct { |  | ||||||
|   char password[7]; |  | ||||||
| } CDCache; |  | ||||||
|  |  | ||||||
| int thread_exit = 0; |  | ||||||
| PeerPtr *peer_server = nullptr; |  | ||||||
| // PeerPtr *peer_server = nullptr; |  | ||||||
| bool joined = false; |  | ||||||
| bool received_frame = false; |  | ||||||
| bool menu_hovered = false; |  | ||||||
|  |  | ||||||
| static bool connect_button_pressed = false; |  | ||||||
| static bool fullscreen_button_pressed = false; |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
| static const char *connect_label = u8"连接"; |  | ||||||
| static const char *fullscreen_label = u8"全屏"; |  | ||||||
| #else |  | ||||||
| static const char *connect_label = "Connect"; |  | ||||||
| static const char *fullscreen_label = "FULLSCREEN"; |  | ||||||
| #endif |  | ||||||
| static char input_password[7] = ""; |  | ||||||
| static FILE *cd_cache_file = nullptr; |  | ||||||
| static CDCache cd_cache; |  | ||||||
|  |  | ||||||
| static bool is_create_connection = false; |  | ||||||
| static bool done = false; |  | ||||||
|  |  | ||||||
| ScreenCapturerFactory *screen_capturer_factory = nullptr; |  | ||||||
| ScreenCapturer *screen_capturer = nullptr; |  | ||||||
|  |  | ||||||
| DeviceControllerFactory *device_controller_factory = nullptr; |  | ||||||
| MouseController *mouse_controller = nullptr; |  | ||||||
|  |  | ||||||
| ConfigCenter config_center; |  | ||||||
|  |  | ||||||
| char *nv12_buffer = nullptr; |  | ||||||
|  |  | ||||||
| #ifdef __linux__ |  | ||||||
| std::chrono::_V2::system_clock::time_point last_frame_time_; |  | ||||||
| #else |  | ||||||
| std::chrono::steady_clock::time_point last_frame_time_; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| inline int ProcessMouseKeyEven(SDL_Event &ev) { |  | ||||||
|   float ratio = (float)(1280.0 / window_w); |  | ||||||
|  |  | ||||||
|   RemoteAction remote_action; |  | ||||||
|   remote_action.m.x = (size_t)(ev.button.x * ratio); |  | ||||||
|   remote_action.m.y = (size_t)(ev.button.y * ratio); |  | ||||||
|  |  | ||||||
|   if (SDL_KEYDOWN == ev.type)  // SDL_KEYUP |  | ||||||
|   { |  | ||||||
|     // printf("SDLK_DOWN: %d\n", SDL_KeyCode(ev.key.keysym.sym)); |  | ||||||
|     if (SDLK_DOWN == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_DOWN  \n"); |  | ||||||
|  |  | ||||||
|     } else if (SDLK_UP == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_UP  \n"); |  | ||||||
|  |  | ||||||
|     } else if (SDLK_LEFT == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_LEFT  \n"); |  | ||||||
|  |  | ||||||
|     } else if (SDLK_RIGHT == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_RIGHT  \n"); |  | ||||||
|     } |  | ||||||
|   } else if (SDL_MOUSEBUTTONDOWN == ev.type) { |  | ||||||
|     remote_action.type = ControlType::mouse; |  | ||||||
|     if (SDL_BUTTON_LEFT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::left_down; |  | ||||||
|     } else if (SDL_BUTTON_RIGHT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::right_down; |  | ||||||
|     } |  | ||||||
|     SendData(peer_server, DATA_TYPE::DATA, (const char *)&remote_action, |  | ||||||
|              sizeof(remote_action)); |  | ||||||
|   } else if (SDL_MOUSEBUTTONUP == ev.type) { |  | ||||||
|     remote_action.type = ControlType::mouse; |  | ||||||
|     if (SDL_BUTTON_LEFT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::left_up; |  | ||||||
|     } else if (SDL_BUTTON_RIGHT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::right_up; |  | ||||||
|     } |  | ||||||
|     SendData(peer_server, DATA_TYPE::DATA, (const char *)&remote_action, |  | ||||||
|              sizeof(remote_action)); |  | ||||||
|   } else if (SDL_MOUSEMOTION == ev.type) { |  | ||||||
|     remote_action.type = ControlType::mouse; |  | ||||||
|     remote_action.m.flag = MouseFlag::move; |  | ||||||
|     SendData(peer_server, DATA_TYPE::DATA, (const char *)&remote_action, |  | ||||||
|              sizeof(remote_action)); |  | ||||||
|   } else if (SDL_QUIT == ev.type) { |  | ||||||
|     SDL_Event event; |  | ||||||
|     event.type = SDL_QUIT; |  | ||||||
|     SDL_PushEvent(&event); |  | ||||||
|     printf("SDL_QUIT\n"); |  | ||||||
|     return 0; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) { |  | ||||||
|   if (1) { |  | ||||||
|     if ("Connected" == connection_status_str) { |  | ||||||
|       SendData(peer_server, DATA_TYPE::AUDIO, (const char *)stream, len); |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     memcpy(audio_buffer, stream, len); |  | ||||||
|     audio_len = len; |  | ||||||
|     SDL_Delay(10); |  | ||||||
|     audio_buffer_fresh = true; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) { |  | ||||||
|   if (!audio_buffer_fresh) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   SDL_memset(stream, 0, len); |  | ||||||
|  |  | ||||||
|   if (audio_len == 0) { |  | ||||||
|     return; |  | ||||||
|   } else { |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   len = (len > audio_len ? audio_len : len); |  | ||||||
|   SDL_MixAudioFormat(stream, audio_buffer, AUDIO_S16LSB, len, |  | ||||||
|                      SDL_MIX_MAXVOLUME); |  | ||||||
|   audio_buffer_fresh = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ServerReceiveVideoBuffer(const char *data, size_t size, |  | ||||||
|                               const char *user_id, size_t user_id_size) { |  | ||||||
|   if (joined) { |  | ||||||
|     memcpy(dst_buffer, data, size); |  | ||||||
|  |  | ||||||
|     SDL_Event event; |  | ||||||
|     event.type = REFRESH_EVENT; |  | ||||||
|     SDL_PushEvent(&event); |  | ||||||
|     received_frame = true; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ClientReceiveVideoBuffer(const char *data, size_t size, |  | ||||||
|                               const char *user_id, size_t user_id_size) { |  | ||||||
|   // std::cout << "Receive: [" << user_id << "] " << std::endl; |  | ||||||
|   if (joined) { |  | ||||||
|     memcpy(dst_buffer, data, size); |  | ||||||
|  |  | ||||||
|     SDL_Event event; |  | ||||||
|     event.type = REFRESH_EVENT; |  | ||||||
|     SDL_PushEvent(&event); |  | ||||||
|     received_frame = true; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ServerReceiveAudioBuffer(const char *data, size_t size, |  | ||||||
|                               const char *user_id, size_t user_id_size) { |  | ||||||
|   // memset(audio_buffer, 0, size); |  | ||||||
|   // memcpy(audio_buffer, data, size); |  | ||||||
|   // audio_len = size; |  | ||||||
|   audio_buffer_fresh = true; |  | ||||||
|  |  | ||||||
|   SDL_QueueAudio(output_dev, data, (Uint32)size); |  | ||||||
|   // printf("queue audio\n"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ClientReceiveAudioBuffer(const char *data, size_t size, |  | ||||||
|                               const char *user_id, size_t user_id_size) { |  | ||||||
|   // std::cout << "Client receive audio, size " << size << ", user [" << user_id |  | ||||||
|   //           << "] " << std::endl; |  | ||||||
|   SDL_QueueAudio(output_dev, data, (Uint32)size); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ServerReceiveDataBuffer(const char *data, size_t size, const char *user_id, |  | ||||||
|                              size_t user_id_size) { |  | ||||||
|   std::string user(user_id, user_id_size); |  | ||||||
|  |  | ||||||
|   RemoteAction remote_action; |  | ||||||
|   memcpy(&remote_action, data, sizeof(remote_action)); |  | ||||||
|  |  | ||||||
|   // std::cout << "remote_action: " << remote_action.type << " " |  | ||||||
|   //           << remote_action.m.flag << " " << remote_action.m.x << " " |  | ||||||
|   //           << remote_action.m.y << std::endl; |  | ||||||
| #if MOUSE_CONTROL |  | ||||||
|   if (mouse_controller) { |  | ||||||
|     mouse_controller->SendCommand(remote_action); |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ClientReceiveDataBuffer(const char *data, size_t size, const char *user_id, |  | ||||||
|                              size_t user_id_size) {} |  | ||||||
|  |  | ||||||
| void SignalStatus(SignalStatus status) { |  | ||||||
|   signal_status = status; |  | ||||||
|   if (SignalStatus::SignalConnecting == status) { |  | ||||||
|     signal_status_str = "SignalConnecting"; |  | ||||||
|   } else if (SignalStatus::SignalConnected == status) { |  | ||||||
|     signal_status_str = "SignalConnected"; |  | ||||||
|   } else if (SignalStatus::SignalFailed == status) { |  | ||||||
|     signal_status_str = "SignalFailed"; |  | ||||||
|   } else if (SignalStatus::SignalClosed == status) { |  | ||||||
|     signal_status_str = "SignalClosed"; |  | ||||||
|   } else if (SignalStatus::SignalReconnecting == status) { |  | ||||||
|     signal_status_str = "SignalReconnecting"; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ConnectionStatus(ConnectionStatus status) { |  | ||||||
|   connection_status = status; |  | ||||||
|   if (ConnectionStatus::Connecting == status) { |  | ||||||
|     connection_status_str = "Connecting"; |  | ||||||
|   } else if (ConnectionStatus::Connected == status) { |  | ||||||
|     connection_status_str = "Connected"; |  | ||||||
|     joined = true; |  | ||||||
|   } else if (ConnectionStatus::Disconnected == status) { |  | ||||||
|     connection_status_str = "Disconnected"; |  | ||||||
|   } else if (ConnectionStatus::Failed == status) { |  | ||||||
|     connection_status_str = "Failed"; |  | ||||||
|   } else if (ConnectionStatus::Closed == status) { |  | ||||||
|     connection_status_str = "Closed"; |  | ||||||
|   } else if (ConnectionStatus::IncorrectPassword == status) { |  | ||||||
|     connection_status_str = "Incorrect password"; |  | ||||||
|     if (connect_button_pressed) { |  | ||||||
|       connect_button_pressed = false; |  | ||||||
|       joined = false; |  | ||||||
|       connect_label = connect_button_pressed ? "Disconnect" : "Connect"; |  | ||||||
|     } |  | ||||||
|   } else if (ConnectionStatus::NoSuchTransmissionId == status) { |  | ||||||
|     connection_status_str = "No such transmission id"; |  | ||||||
|     if (connect_button_pressed) { |  | ||||||
|       connect_button_pressed = false; |  | ||||||
|       joined = false; |  | ||||||
|       connect_label = connect_button_pressed ? "Disconnect" : "Connect"; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { |  | ||||||
|   LOG_INFO("Remote desk"); |  | ||||||
|  |  | ||||||
|   last_ts = static_cast<uint32_t>( |  | ||||||
|       std::chrono::duration_cast<std::chrono::milliseconds>( |  | ||||||
|           std::chrono::high_resolution_clock::now().time_since_epoch()) |  | ||||||
|           .count()); |  | ||||||
|  |  | ||||||
|   cd_cache_file = fopen("cache.cd", "r+"); |  | ||||||
|   if (cd_cache_file) { |  | ||||||
|     fseek(cd_cache_file, 0, SEEK_SET); |  | ||||||
|     fread(&cd_cache.password, sizeof(cd_cache.password), 1, cd_cache_file); |  | ||||||
|     fclose(cd_cache_file); |  | ||||||
|     strncpy(input_password, cd_cache.password, sizeof(cd_cache.password)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Setup SDL |  | ||||||
|   if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | |  | ||||||
|                SDL_INIT_GAMECONTROLLER) != 0) { |  | ||||||
|     printf("Error: %s\n", SDL_GetError()); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // From 2.0.18: Enable native IME. |  | ||||||
| #ifdef SDL_HINT_IME_SHOW_UI |  | ||||||
|   SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   // Create window with SDL_Renderer graphics context |  | ||||||
|   SDL_WindowFlags window_flags = |  | ||||||
|       (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); |  | ||||||
|   window = SDL_CreateWindow("Remote Desk", SDL_WINDOWPOS_CENTERED, |  | ||||||
|                             SDL_WINDOWPOS_CENTERED, window_w, window_h, |  | ||||||
|                             window_flags); |  | ||||||
|  |  | ||||||
|   SDL_DisplayMode DM; |  | ||||||
|   SDL_GetCurrentDisplayMode(0, &DM); |  | ||||||
|   screen_w = DM.w; |  | ||||||
|   screen_h = DM.h; |  | ||||||
|  |  | ||||||
|   sdlRenderer = SDL_CreateRenderer( |  | ||||||
|       window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); |  | ||||||
|   if (sdlRenderer == nullptr) { |  | ||||||
|     SDL_Log("Error creating SDL_Renderer!"); |  | ||||||
|     return 0; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Uint32 pixformat = 0; |  | ||||||
|   pixformat = SDL_PIXELFORMAT_NV12; |  | ||||||
|  |  | ||||||
|   sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat, |  | ||||||
|                                  SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h); |  | ||||||
|  |  | ||||||
|   // Audio |  | ||||||
|   SDL_AudioSpec want_in, have_in, want_out, have_out; |  | ||||||
|   SDL_zero(want_in); |  | ||||||
|   want_in.freq = 48000; |  | ||||||
|   want_in.format = AUDIO_S16LSB; |  | ||||||
|   want_in.channels = 1; |  | ||||||
|   want_in.samples = 480; |  | ||||||
|   want_in.callback = SdlCaptureAudioIn; |  | ||||||
|  |  | ||||||
|   input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in, 0); |  | ||||||
|   if (input_dev == 0) { |  | ||||||
|     SDL_Log("Failed to open input: %s", SDL_GetError()); |  | ||||||
|     // return 1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   SDL_zero(want_out); |  | ||||||
|   want_out.freq = 48000; |  | ||||||
|   want_out.format = AUDIO_S16LSB; |  | ||||||
|   want_out.channels = 1; |  | ||||||
|   // want_out.silence = 0; |  | ||||||
|   want_out.samples = 480; |  | ||||||
|   want_out.callback = NULL; |  | ||||||
|  |  | ||||||
|   output_dev = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out, 0); |  | ||||||
|   if (output_dev == 0) { |  | ||||||
|     SDL_Log("Failed to open input: %s", SDL_GetError()); |  | ||||||
|     // return 1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   SDL_PauseAudioDevice(input_dev, 0); |  | ||||||
|   SDL_PauseAudioDevice(output_dev, 0); |  | ||||||
|  |  | ||||||
|   // Setup Dear ImGui context |  | ||||||
|   IMGUI_CHECKVERSION(); |  | ||||||
|   ImGui::CreateContext(); |  | ||||||
|   ImGuiIO &io = ImGui::GetIO(); |  | ||||||
|  |  | ||||||
|   io.ConfigFlags |= |  | ||||||
|       ImGuiConfigFlags_NavEnableKeyboard;  // Enable Keyboard Controls |  | ||||||
|   io.ConfigFlags |= |  | ||||||
|       ImGuiConfigFlags_NavEnableGamepad;  // Enable Gamepad Controls |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
|   // Load Fonts |  | ||||||
| #ifdef _WIN32 |  | ||||||
|   std::string default_font_path = "c:/windows/fonts/simhei.ttf"; |  | ||||||
|   std::ifstream font_path_f(default_font_path.c_str()); |  | ||||||
|   std::string font_path = |  | ||||||
|       font_path_f.good() ? "c:/windows/fonts/simhei.ttf" : ""; |  | ||||||
|   if (!font_path.empty()) { |  | ||||||
|     io.Fonts->AddFontFromFileTTF(font_path.c_str(), 13.0f, NULL, |  | ||||||
|                                  io.Fonts->GetGlyphRangesChineseFull()); |  | ||||||
|   } |  | ||||||
| #elif __APPLE__ |  | ||||||
|   std::string default_font_path = "/System/Library/Fonts/PingFang.ttc"; |  | ||||||
|   std::ifstream font_path_f(default_font_path.c_str()); |  | ||||||
|   std::string font_path = |  | ||||||
|       font_path_f.good() ? "/System/Library/Fonts/PingFang.ttc" : ""; |  | ||||||
|   if (!font_path.empty()) { |  | ||||||
|     io.Fonts->AddFontFromFileTTF(font_path.c_str(), 13.0f, NULL, |  | ||||||
|                                  io.Fonts->GetGlyphRangesChineseFull()); |  | ||||||
|   } |  | ||||||
| #elif __linux__ |  | ||||||
|   io.Fonts->AddFontFromFileTTF("c:/windows/fonts/msyh.ttc", 13.0f, NULL, |  | ||||||
|                                io.Fonts->GetGlyphRangesChineseFull()); |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   // Setup Dear ImGui style |  | ||||||
|   // ImGui::StyleColorsDark(); |  | ||||||
|   ImGui::StyleColorsLight(); |  | ||||||
|  |  | ||||||
|   // Setup Platform/Renderer backends |  | ||||||
|   ImGui_ImplSDL2_InitForSDLRenderer(window, sdlRenderer); |  | ||||||
|   ImGui_ImplSDLRenderer2_Init(sdlRenderer); |  | ||||||
|  |  | ||||||
|   // Our state |  | ||||||
|   bool show_demo_window = true; |  | ||||||
|   bool show_another_window = false; |  | ||||||
|   ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); |  | ||||||
|  |  | ||||||
|   std::string mac_addr_str = GetMac(); |  | ||||||
|  |  | ||||||
|   std::thread rtc_thread( |  | ||||||
|       [](int screen_width, int screen_height) { |  | ||||||
|         std::string default_cfg_path = "../../../../config/config.ini"; |  | ||||||
|         std::ifstream f(default_cfg_path.c_str()); |  | ||||||
|  |  | ||||||
|         std::string mac_addr_str = GetMac(); |  | ||||||
|  |  | ||||||
|         Params server_params; |  | ||||||
|         server_params.cfg_path = |  | ||||||
|             f.good() ? "../../../../config/config.ini" : "config.ini"; |  | ||||||
|         server_params.on_receive_video_buffer = ServerReceiveVideoBuffer; |  | ||||||
|         server_params.on_receive_audio_buffer = ServerReceiveAudioBuffer; |  | ||||||
|         server_params.on_receive_data_buffer = ServerReceiveDataBuffer; |  | ||||||
|         server_params.on_signal_status = SignalStatus; |  | ||||||
|         server_params.on_connection_status = ConnectionStatus; |  | ||||||
|  |  | ||||||
|         std::string transmission_id = "000001"; |  | ||||||
|  |  | ||||||
|         peer_server = CreatePeer(&server_params); |  | ||||||
|         LOG_INFO("Create peer_server"); |  | ||||||
|         std::string server_user_id = "S-" + mac_addr_str; |  | ||||||
|         Init(peer_server, server_user_id.c_str()); |  | ||||||
|         LOG_INFO("peer_server init finish"); |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|           while (SignalStatus::SignalConnected != signal_status && !done) { |  | ||||||
|             std::this_thread::sleep_for(std::chrono::seconds(1)); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           if (done) { |  | ||||||
|             return; |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           std::string user_id = "S-" + mac_addr_str; |  | ||||||
|           is_create_connection = |  | ||||||
|               CreateConnection(peer_server, mac_addr_str.c_str(), |  | ||||||
|                                input_password) |  | ||||||
|                   ? false |  | ||||||
|                   : true; |  | ||||||
|  |  | ||||||
|           nv12_buffer = new char[NV12_BUFFER_SIZE]; |  | ||||||
|  |  | ||||||
|           // Screen capture |  | ||||||
|           screen_capturer_factory = new ScreenCapturerFactory(); |  | ||||||
|           screen_capturer = (ScreenCapturer *)screen_capturer_factory->Create(); |  | ||||||
|  |  | ||||||
|           last_frame_time_ = std::chrono::high_resolution_clock::now(); |  | ||||||
|           ScreenCapturer::RECORD_DESKTOP_RECT rect; |  | ||||||
|           rect.left = 0; |  | ||||||
|           rect.top = 0; |  | ||||||
|           rect.right = screen_w; |  | ||||||
|           rect.bottom = screen_h; |  | ||||||
|  |  | ||||||
|           int screen_capturer_init_ret = screen_capturer->Init( |  | ||||||
|               rect, 60, |  | ||||||
|               [](unsigned char *data, int size, int width, int height) -> void { |  | ||||||
|                 auto now_time = std::chrono::high_resolution_clock::now(); |  | ||||||
|                 std::chrono::duration<double> duration = |  | ||||||
|                     now_time - last_frame_time_; |  | ||||||
|                 auto tc = duration.count() * 1000; |  | ||||||
|  |  | ||||||
|                 if (tc >= 0) { |  | ||||||
|                   SendData(peer_server, DATA_TYPE::VIDEO, (const char *)data, |  | ||||||
|                            NV12_BUFFER_SIZE); |  | ||||||
|                   last_frame_time_ = now_time; |  | ||||||
|                 } |  | ||||||
|               }); |  | ||||||
|  |  | ||||||
|           if (0 == screen_capturer_init_ret) { |  | ||||||
|             screen_capturer->Start(); |  | ||||||
|           } else { |  | ||||||
|             screen_capturer->Destroy(); |  | ||||||
|             screen_capturer = nullptr; |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           // Mouse control |  | ||||||
|           device_controller_factory = new DeviceControllerFactory(); |  | ||||||
|           mouse_controller = |  | ||||||
|               (MouseController *)device_controller_factory->Create( |  | ||||||
|                   DeviceControllerFactory::Device::Mouse); |  | ||||||
|           int mouse_controller_init_ret = |  | ||||||
|               mouse_controller->Init(screen_w, screen_h); |  | ||||||
|           if (0 != mouse_controller_init_ret) { |  | ||||||
|             mouse_controller->Destroy(); |  | ||||||
|             mouse_controller = nullptr; |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|       screen_w, screen_h); |  | ||||||
|  |  | ||||||
|   // Main loop |  | ||||||
|   while (!done) { |  | ||||||
|     // Start the Dear ImGui frame |  | ||||||
|     ImGui_ImplSDLRenderer2_NewFrame(); |  | ||||||
|     ImGui_ImplSDL2_NewFrame(); |  | ||||||
|     ImGui::NewFrame(); |  | ||||||
|  |  | ||||||
|     if (joined && !menu_hovered) { |  | ||||||
|       ImGui::SetMouseCursor(ImGuiMouseCursor_None); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     { |  | ||||||
|       static float f = 0.0f; |  | ||||||
|       static int counter = 0; |  | ||||||
|  |  | ||||||
|       const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); |  | ||||||
|       ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Once); |  | ||||||
|  |  | ||||||
|       // ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
|       ImGui::SetNextWindowSize(ImVec2(160, 210)); |  | ||||||
| #else |  | ||||||
|       ImGui::SetNextWindowSize(ImVec2(180, 210)); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
|       if (!joined) { |  | ||||||
|         ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); |  | ||||||
|         ImGui::Begin(u8"菜单", nullptr, |  | ||||||
|                      ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | |  | ||||||
|                          ImGuiWindowFlags_NoMove); |  | ||||||
|       } else { |  | ||||||
|         ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); |  | ||||||
|         ImGui::Begin(u8"菜单", nullptr, ImGuiWindowFlags_None); |  | ||||||
|       } |  | ||||||
| #else |  | ||||||
|       if (!joined) { |  | ||||||
|         ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); |  | ||||||
|         ImGui::Begin("Menu", nullptr, |  | ||||||
|                      ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | |  | ||||||
|                          ImGuiWindowFlags_NoMove); |  | ||||||
|       } else { |  | ||||||
|         ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); |  | ||||||
|         ImGui::Begin("Menu", nullptr, ImGuiWindowFlags_None); |  | ||||||
|       } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|       { |  | ||||||
|         menu_hovered = ImGui::IsWindowHovered(); |  | ||||||
| #if CHINESE_FONT |  | ||||||
|         ImGui::Text(u8"本机ID:"); |  | ||||||
| #else |  | ||||||
|         ImGui::Text("LOCAL ID:"); |  | ||||||
| #endif |  | ||||||
|         ImGui::SameLine(); |  | ||||||
|         ImGui::SetNextItemWidth(90); |  | ||||||
| #if CHINESE_FONT |  | ||||||
|         ImGui::SetCursorPosX(60.0f); |  | ||||||
| #else |  | ||||||
|         ImGui::SetCursorPosX(80.0f); |  | ||||||
| #endif |  | ||||||
|         ImGui::InputText( |  | ||||||
|             "##local_id", (char *)mac_addr_str.c_str(), |  | ||||||
|             mac_addr_str.length() + 1, |  | ||||||
|             ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_ReadOnly); |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
|         ImGui::Text(u8"密码:"); |  | ||||||
| #else |  | ||||||
|         ImGui::Text("PASSWORD:"); |  | ||||||
| #endif |  | ||||||
|         ImGui::SameLine(); |  | ||||||
|         char input_password_tmp[7] = ""; |  | ||||||
|         std::string input_password_str = "123456"; |  | ||||||
|         strncpy(input_password_tmp, input_password, sizeof(input_password)); |  | ||||||
|         ImGui::SetNextItemWidth(90); |  | ||||||
| #if CHINESE_FONT |  | ||||||
|         ImGui::SetCursorPosX(60.0f); |  | ||||||
|         ImGui::InputTextWithHint("##server_pwd", u8"最长6个字符", |  | ||||||
|                                  input_password, IM_ARRAYSIZE(input_password), |  | ||||||
|                                  ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
| #else |  | ||||||
|         ImGui::SetCursorPosX(80.0f); |  | ||||||
|         ImGui::InputTextWithHint("##server_pwd", "max 6 chars", input_password, |  | ||||||
|                                  IM_ARRAYSIZE(input_password), |  | ||||||
|                                  ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
| #endif |  | ||||||
|         if (strcmp(input_password_tmp, input_password)) { |  | ||||||
|           cd_cache_file = fopen("cache.cd", "w+"); |  | ||||||
|           if (cd_cache_file) { |  | ||||||
|             fseek(cd_cache_file, 0, SEEK_SET); |  | ||||||
|             strncpy(cd_cache.password, input_password, sizeof(input_password)); |  | ||||||
|             fwrite(&cd_cache.password, sizeof(cd_cache.password), 1, |  | ||||||
|                    cd_cache_file); |  | ||||||
|             fclose(cd_cache_file); |  | ||||||
|           } |  | ||||||
|           LeaveConnection(peer_server); |  | ||||||
|           CreateConnection(peer_server, mac_addr_str.c_str(), input_password); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|         ImGui::Separator(); |  | ||||||
|  |  | ||||||
|         ImGui::Spacing(); |  | ||||||
|         { |  | ||||||
|           { |  | ||||||
|             static char remote_id[20] = ""; |  | ||||||
| #if CHINESE_FONT |  | ||||||
|             ImGui::Text(u8"远端ID:"); |  | ||||||
| #else |  | ||||||
|             ImGui::Text("REMOTE ID:"); |  | ||||||
| #endif |  | ||||||
|             ImGui::SameLine(); |  | ||||||
|             ImGui::SetNextItemWidth(90); |  | ||||||
| #if CHINESE_FONT |  | ||||||
|             ImGui::SetCursorPosX(60.0f); |  | ||||||
| #else |  | ||||||
|             ImGui::SetCursorPosX(80.0f); |  | ||||||
| #endif |  | ||||||
|             ImGui::InputTextWithHint("##remote_id", mac_addr_str.c_str(), |  | ||||||
|                                      remote_id, IM_ARRAYSIZE(remote_id), |  | ||||||
|                                      ImGuiInputTextFlags_CharsUppercase | |  | ||||||
|                                          ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
|  |  | ||||||
|             ImGui::Spacing(); |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
|             ImGui::Text(u8"密码:"); |  | ||||||
| #else |  | ||||||
|             ImGui::Text("PASSWORD:"); |  | ||||||
| #endif |  | ||||||
|             ImGui::SameLine(); |  | ||||||
|             ImGui::SetNextItemWidth(90); |  | ||||||
|             static char client_password[20] = ""; |  | ||||||
| #if CHINESE_FONT |  | ||||||
|             ImGui::SetCursorPosX(60.0f); |  | ||||||
|             ImGui::InputTextWithHint("##client_pwd", u8"最长6个字符", |  | ||||||
|                                      client_password, |  | ||||||
|                                      IM_ARRAYSIZE(client_password), |  | ||||||
|                                      ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
| #else |  | ||||||
|             ImGui::SetCursorPosX(80.0f); |  | ||||||
|             ImGui::InputTextWithHint("##client_pwd", "max 6 chars", |  | ||||||
|                                      client_password, |  | ||||||
|                                      IM_ARRAYSIZE(client_password), |  | ||||||
|                                      ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|             ImGui::Spacing(); |  | ||||||
|             ImGui::Separator(); |  | ||||||
|             ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|             if (ImGui::Button(connect_label)) { |  | ||||||
|               int ret = -1; |  | ||||||
|               if ("SignalConnected" == signal_status_str) { |  | ||||||
| #if CHINESE_FONT |  | ||||||
|                 if (strcmp(connect_label, u8"连接") == 0 && !joined) { |  | ||||||
| #else |  | ||||||
|                 if (strcmp(connect_label, "Connect") == 0 && !joined) { |  | ||||||
| #endif |  | ||||||
|                   std::string user_id = "C-" + mac_addr_str; |  | ||||||
|                   ret = JoinConnection(peer_server, remote_id, client_password); |  | ||||||
|                   if (0 == ret) { |  | ||||||
|                     // joined = true; |  | ||||||
|                   } |  | ||||||
| #if CHINESE_FONT |  | ||||||
|                 } else if (strcmp(connect_label, u8"断开连接") == 0 && joined) { |  | ||||||
| #else |  | ||||||
|                 } else if (strcmp(connect_label, "Disconnect") == 0 && joined) { |  | ||||||
| #endif |  | ||||||
|                   ret = LeaveConnection(peer_server); |  | ||||||
|                   CreateConnection(peer_server, mac_addr_str.c_str(), |  | ||||||
|                                    input_password); |  | ||||||
|                   memset(audio_buffer, 0, 960); |  | ||||||
|                   if (0 == ret) { |  | ||||||
|                     joined = false; |  | ||||||
|                     received_frame = false; |  | ||||||
|                   } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (0 == ret) { |  | ||||||
|                   connect_button_pressed = !connect_button_pressed; |  | ||||||
| #if CHINESE_FONT |  | ||||||
|                   connect_label = |  | ||||||
|                       connect_button_pressed ? u8"断开连接" : u8"连接"; |  | ||||||
| #else |  | ||||||
|                   connect_label = |  | ||||||
|                       connect_button_pressed ? "Disconnect" : "Connect"; |  | ||||||
| #endif |  | ||||||
|                 } |  | ||||||
|               } |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|       ImGui::Separator(); |  | ||||||
|  |  | ||||||
|       ImGui::Spacing(); |  | ||||||
|  |  | ||||||
| #if CHINESE_FONT |  | ||||||
|       if (ImGui::Button(fullscreen_label)) { |  | ||||||
|         if (strcmp(fullscreen_label, u8"全屏") == 0) { |  | ||||||
| #else |  | ||||||
|       if (ImGui::Button(fullscreen_label)) { |  | ||||||
|         if (strcmp(fullscreen_label, "FULLSCREEN") == 0) { |  | ||||||
| #endif |  | ||||||
|           SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); |  | ||||||
|         } else { |  | ||||||
|           SDL_SetWindowFullscreen(window, SDL_FALSE); |  | ||||||
|         } |  | ||||||
|         fullscreen_button_pressed = !fullscreen_button_pressed; |  | ||||||
| #if CHINESE_FONT |  | ||||||
|         fullscreen_label = fullscreen_button_pressed ? u8"退出全屏" : u8"全屏"; |  | ||||||
| #else |  | ||||||
|         fullscreen_label = |  | ||||||
|             fullscreen_button_pressed ? "EXIT FULLSCREEN" : "FULLSCREEN"; |  | ||||||
| #endif |  | ||||||
|       } |  | ||||||
|       ImGui::End(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Rendering |  | ||||||
|     ImGui::Render(); |  | ||||||
|     SDL_RenderSetScale(sdlRenderer, io.DisplayFramebufferScale.x, |  | ||||||
|                        io.DisplayFramebufferScale.y); |  | ||||||
|  |  | ||||||
|     SDL_Event event; |  | ||||||
|     while (SDL_PollEvent(&event)) { |  | ||||||
|       ImGui_ImplSDL2_ProcessEvent(&event); |  | ||||||
|       if (event.type == SDL_QUIT) { |  | ||||||
|         done = true; |  | ||||||
|       } else if (event.type == SDL_WINDOWEVENT && |  | ||||||
|                  event.window.event == SDL_WINDOWEVENT_RESIZED) { |  | ||||||
|         // SDL_GetWindowSize(window, &window_w, &window_h); |  | ||||||
|  |  | ||||||
|         int window_w_last = window_w; |  | ||||||
|         int window_h_last = window_h; |  | ||||||
|  |  | ||||||
|         SDL_GetWindowSize(window, &window_w, &window_h); |  | ||||||
|  |  | ||||||
|         int w_change_ratio = abs(window_w - window_w_last) / 16; |  | ||||||
|         int h_change_ratio = abs(window_h - window_h_last) / 9; |  | ||||||
|  |  | ||||||
|         if (w_change_ratio > h_change_ratio) { |  | ||||||
|           window_h = window_w * 9 / 16; |  | ||||||
|         } else { |  | ||||||
|           window_w = window_h * 16 / 9; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SDL_SetWindowSize(window, window_w, window_h); |  | ||||||
|       } else if (event.type == SDL_WINDOWEVENT && |  | ||||||
|                  event.window.event == SDL_WINDOWEVENT_CLOSE && |  | ||||||
|                  event.window.windowID == SDL_GetWindowID(window)) { |  | ||||||
|         done = true; |  | ||||||
|       } else if (event.type == REFRESH_EVENT) { |  | ||||||
|         sdlRect.x = 0; |  | ||||||
|         sdlRect.y = 0; |  | ||||||
|         sdlRect.w = window_w; |  | ||||||
|         sdlRect.h = window_h; |  | ||||||
|  |  | ||||||
|         SDL_UpdateTexture(sdlTexture, NULL, dst_buffer, pixel_w); |  | ||||||
|       } else { |  | ||||||
|         if (joined) { |  | ||||||
|           ProcessMouseKeyEven(event); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SDL_RenderClear(sdlRenderer); |  | ||||||
|     SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect); |  | ||||||
|  |  | ||||||
|     if (!joined || !received_frame) { |  | ||||||
|       SDL_RenderClear(sdlRenderer); |  | ||||||
|       SDL_SetRenderDrawColor( |  | ||||||
|           sdlRenderer, (Uint8)(clear_color.x * 0), (Uint8)(clear_color.y * 0), |  | ||||||
|           (Uint8)(clear_color.z * 0), (Uint8)(clear_color.w * 0)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); |  | ||||||
|     SDL_RenderPresent(sdlRenderer); |  | ||||||
|  |  | ||||||
|     frame_count++; |  | ||||||
|     end_time = SDL_GetTicks(); |  | ||||||
|     elapsed_time = end_time - start_time; |  | ||||||
|     if (elapsed_time >= 1000) { |  | ||||||
|       fps = frame_count / (elapsed_time / 1000); |  | ||||||
|       frame_count = 0; |  | ||||||
|       window_title = "Remote Desk Client FPS [" + std::to_string(fps) + |  | ||||||
|                      "] status [" + signal_status_str + "|" + |  | ||||||
|                      connection_status_str + "]"; |  | ||||||
|       // For MacOS, UI frameworks can only be called from the main thread |  | ||||||
|       SDL_SetWindowTitle(window, window_title.c_str()); |  | ||||||
|       start_time = end_time; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Cleanup |  | ||||||
|  |  | ||||||
|   if (is_create_connection) { |  | ||||||
|     LeaveConnection(peer_server); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   rtc_thread.join(); |  | ||||||
|   SDL_CloseAudioDevice(output_dev); |  | ||||||
|   SDL_CloseAudioDevice(input_dev); |  | ||||||
|  |  | ||||||
|   if (screen_capturer) { |  | ||||||
|     screen_capturer->Destroy(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (mouse_controller) { |  | ||||||
|     mouse_controller->Destroy(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   ImGui_ImplSDLRenderer2_Shutdown(); |  | ||||||
|   ImGui_ImplSDL2_Shutdown(); |  | ||||||
|   ImGui::DestroyContext(); |  | ||||||
|  |  | ||||||
|   SDL_DestroyRenderer(sdlRenderer); |  | ||||||
|   SDL_DestroyWindow(window); |  | ||||||
|  |  | ||||||
|   SDL_CloseAudio(); |  | ||||||
|   SDL_Quit(); |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| /* | /* | ||||||
|  * @Author: DI JUNKUN |  * @Author: DI JUNKUN | ||||||
|  * @Date: 2024-05-29 |  * @Date: 2024-05-29 | ||||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
| @@ -10,38 +10,133 @@ | |||||||
| #include <vector> | #include <vector> | ||||||
| namespace localization { | namespace localization { | ||||||
|  |  | ||||||
| static std::vector<std::string> menu = {u8"菜单", "Menu"}; | static std::vector<std::string> local_desktop = { | ||||||
| static std::vector<std::string> local_id = {u8"本机ID:", "Local ID:"}; |     reinterpret_cast<const char*>(u8"本桌面"), "Local Desktop"}; | ||||||
| static std::vector<std::string> password = {u8"密码:", "Password:"}; | static std::vector<std::string> local_id = { | ||||||
| static std::vector<std::string> max_password_len = {u8"最大6个字符", |     reinterpret_cast<const char*>(u8"本机ID"), "Local ID"}; | ||||||
|                                                     "Max 6 chars"}; | static std::vector<std::string> local_id_copied_to_clipboard = { | ||||||
| static std::vector<std::string> remote_id = {u8"对端ID:", "Remote ID:"}; |     reinterpret_cast<const char*>(u8"已复制到剪贴板"), "Copied to clipboard"}; | ||||||
| static std::vector<std::string> connect = {u8"连接", "Connect"}; | static std::vector<std::string> password = { | ||||||
| static std::vector<std::string> disconnect = {u8"断开连接", "Disconnect"}; |     reinterpret_cast<const char*>(u8"密码"), "Password"}; | ||||||
| static std::vector<std::string> fullscreen = {u8"全屏", "Fullscreen"}; | static std::vector<std::string> max_password_len = { | ||||||
| static std::vector<std::string> exit_fullscreen = {u8"退出全屏", |     reinterpret_cast<const char*>(u8"最大6个字符"), "Max 6 chars"}; | ||||||
|                                                    "Exit fullscreen"}; |  | ||||||
| static std::vector<std::string> control_mouse = {u8"控制鼠标", "Mouse Control"}; | static std::vector<std::string> remote_desktop = { | ||||||
| static std::vector<std::string> release_mouse = {u8"释放鼠标", "Release Mouse"}; |     reinterpret_cast<const char*>(u8"控制远程桌面"), "Control Remote Desktop"}; | ||||||
| static std::vector<std::string> settings = {u8"设置", "Settings"}; | static std::vector<std::string> remote_id = { | ||||||
| static std::vector<std::string> language = {u8"语言:", "Language:"}; |     reinterpret_cast<const char*>(u8"对端ID"), "Remote ID"}; | ||||||
| static std::vector<std::string> language_zh = {u8"中文", "Chinese"}; | static std::vector<std::string> connect = { | ||||||
| static std::vector<std::string> language_en = {u8"英文", "English"}; |     reinterpret_cast<const char*>(u8"连接"), "Connect"}; | ||||||
| static std::vector<std::string> video_quality = {u8"视频质量:", | static std::vector<std::string> recent_connections = { | ||||||
|                                                  "Video Quality:"}; |     reinterpret_cast<const char*>(u8"近期连接"), "Recent Connections"}; | ||||||
| static std::vector<std::string> video_quality_high = {u8"高", "High"}; | static std::vector<std::string> disconnect = { | ||||||
| static std::vector<std::string> video_quality_medium = {u8"中", "Medium"}; |     reinterpret_cast<const char*>(u8"断开连接"), "Disconnect"}; | ||||||
| static std::vector<std::string> video_quality_low = {u8"低", "Low"}; | static std::vector<std::string> fullscreen = { | ||||||
| static std::vector<std::string> video_encode_format = {u8"视频编码格式:", |     reinterpret_cast<const char*>(u8"全屏"), " Fullscreen"}; | ||||||
|                                                        "Video Encode Format:"}; | static std::vector<std::string> show_net_traffic_stats = { | ||||||
| static std::vector<std::string> av1 = {u8"AV1", "AV1"}; |     reinterpret_cast<const char*>(u8"显示流量统计"), "Show Net Traffic Stats"}; | ||||||
| static std::vector<std::string> h264 = {u8"H.264", "H.264"}; | static std::vector<std::string> hide_net_traffic_stats = { | ||||||
|  |     reinterpret_cast<const char*>(u8"隐藏流量统计"), "Hide Net Traffic Stats"}; | ||||||
|  | static std::vector<std::string> video = { | ||||||
|  |     reinterpret_cast<const char*>(u8"视频"), "Video"}; | ||||||
|  | static std::vector<std::string> audio = { | ||||||
|  |     reinterpret_cast<const char*>(u8"音频"), "Audio"}; | ||||||
|  | static std::vector<std::string> data = {reinterpret_cast<const char*>(u8"数据"), | ||||||
|  |                                         "Data"}; | ||||||
|  | static std::vector<std::string> total = { | ||||||
|  |     reinterpret_cast<const char*>(u8"总计"), "Total"}; | ||||||
|  | static std::vector<std::string> in = {reinterpret_cast<const char*>(u8"输入"), | ||||||
|  |                                       "In"}; | ||||||
|  | static std::vector<std::string> out = {reinterpret_cast<const char*>(u8"输出"), | ||||||
|  |                                        "Out"}; | ||||||
|  | static std::vector<std::string> loss_rate = { | ||||||
|  |     reinterpret_cast<const char*>(u8"丢包率"), "Loss Rate"}; | ||||||
|  | static std::vector<std::string> exit_fullscreen = { | ||||||
|  |     reinterpret_cast<const char*>(u8"退出全屏"), "Exit fullscreen"}; | ||||||
|  | static std::vector<std::string> control_mouse = { | ||||||
|  |     reinterpret_cast<const char*>(u8"控制"), "Control"}; | ||||||
|  | static std::vector<std::string> release_mouse = { | ||||||
|  |     reinterpret_cast<const char*>(u8"释放"), "Release"}; | ||||||
|  | static std::vector<std::string> audio_capture = { | ||||||
|  |     reinterpret_cast<const char*>(u8"声音"), "Audio"}; | ||||||
|  | static std::vector<std::string> mute = { | ||||||
|  |     reinterpret_cast<const char*>(u8" 静音"), " Mute"}; | ||||||
|  | static std::vector<std::string> settings = { | ||||||
|  |     reinterpret_cast<const char*>(u8"设置"), "Settings"}; | ||||||
|  | static std::vector<std::string> language = { | ||||||
|  |     reinterpret_cast<const char*>(u8"语言:"), "Language:"}; | ||||||
|  | static std::vector<std::string> language_zh = { | ||||||
|  |     reinterpret_cast<const char*>(u8"中文"), "Chinese"}; | ||||||
|  | static std::vector<std::string> language_en = { | ||||||
|  |     reinterpret_cast<const char*>(u8"英文"), "English"}; | ||||||
|  | static std::vector<std::string> video_quality = { | ||||||
|  |     reinterpret_cast<const char*>(u8"视频质量:"), "Video Quality:"}; | ||||||
|  | static std::vector<std::string> video_quality_high = { | ||||||
|  |     reinterpret_cast<const char*>(u8"高"), "High"}; | ||||||
|  | static std::vector<std::string> video_quality_medium = { | ||||||
|  |     reinterpret_cast<const char*>(u8"中"), "Medium"}; | ||||||
|  | static std::vector<std::string> video_quality_low = { | ||||||
|  |     reinterpret_cast<const char*>(u8"低"), "Low"}; | ||||||
|  | static std::vector<std::string> video_encode_format = { | ||||||
|  |     reinterpret_cast<const char*>(u8"视频编码格式:"), "Video Encode Format:"}; | ||||||
|  | static std::vector<std::string> av1 = {reinterpret_cast<const char*>(u8"AV1"), | ||||||
|  |                                        "AV1"}; | ||||||
|  | static std::vector<std::string> h264 = { | ||||||
|  |     reinterpret_cast<const char*>(u8"H.264"), "H.264"}; | ||||||
| static std::vector<std::string> enable_hardware_video_codec = { | static std::vector<std::string> enable_hardware_video_codec = { | ||||||
|     u8"启用硬件编解码器:", "Enable Hardware Video Codec:"}; |     reinterpret_cast<const char*>(u8"启用硬件编解码器:"), | ||||||
|  |     "Enable Hardware Video Codec:"}; | ||||||
|  | static std::vector<std::string> enable_turn = { | ||||||
|  |     reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"}; | ||||||
|  |  | ||||||
| static std::vector<std::string> ok = {u8"确认", "OK"}; | static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"), | ||||||
| static std::vector<std::string> cancel = {u8"取消", "Cancel"}; |                                       "OK"}; | ||||||
|  | static std::vector<std::string> cancel = { | ||||||
|  |     reinterpret_cast<const char*>(u8"取消"), "Cancel"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> new_password = { | ||||||
|  |     reinterpret_cast<const char*>(u8"请输入六位密码:"), | ||||||
|  |     "Please input a six-char password:"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> input_password = { | ||||||
|  |     reinterpret_cast<const char*>(u8"请输入密码:"), "Please input password:"}; | ||||||
|  | static std::vector<std::string> validate_password = { | ||||||
|  |     reinterpret_cast<const char*>(u8"验证密码中..."), "Validate password ..."}; | ||||||
|  | static std::vector<std::string> reinput_password = { | ||||||
|  |     reinterpret_cast<const char*>(u8"请重新输入密码"), | ||||||
|  |     "Please input password again"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> remember_password = { | ||||||
|  |     reinterpret_cast<const char*>(u8"记住密码"), "Remember password"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> signal_connected = { | ||||||
|  |     reinterpret_cast<const char*>(u8"已连接服务器"), "Connected"}; | ||||||
|  | static std::vector<std::string> signal_disconnected = { | ||||||
|  |     reinterpret_cast<const char*>(u8"未连接服务器"), "Disconnected"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> p2p_connected = { | ||||||
|  |     reinterpret_cast<const char*>(u8"对等连接已建立"), "P2P Connected"}; | ||||||
|  | static std::vector<std::string> p2p_disconnected = { | ||||||
|  |     reinterpret_cast<const char*>(u8"对等连接已断开"), "P2P Disconnected"}; | ||||||
|  | static std::vector<std::string> p2p_connecting = { | ||||||
|  |     reinterpret_cast<const char*>(u8"正在建立对等连接..."), | ||||||
|  |     "P2P Connecting ..."}; | ||||||
|  | static std::vector<std::string> p2p_failed = { | ||||||
|  |     reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"}; | ||||||
|  | static std::vector<std::string> p2p_closed = { | ||||||
|  |     reinterpret_cast<const char*>(u8"对等连接已关闭"), "P2P closed"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> no_such_id = { | ||||||
|  |     reinterpret_cast<const char*>(u8"无此ID"), "No such ID"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> about = { | ||||||
|  |     reinterpret_cast<const char*>(u8"关于"), "About"}; | ||||||
|  | static std::vector<std::string> version = { | ||||||
|  |     reinterpret_cast<const char*>(u8"版本"), "Version"}; | ||||||
|  |  | ||||||
|  | static std::vector<std::string> confirm_delete_connection = { | ||||||
|  |     reinterpret_cast<const char*>(u8"确认删除此连接"), | ||||||
|  |     "Confirm to delete this connection"}; | ||||||
| }  // namespace localization | }  // namespace localization | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
							
								
								
									
										127
									
								
								src/log/log.h
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								src/log/log.h
									
									
									
									
									
								
							| @@ -1,127 +0,0 @@ | |||||||
| #ifndef _LOG_H_ |  | ||||||
| #define _LOG_H_ |  | ||||||
|  |  | ||||||
| #include <chrono> |  | ||||||
| #include <iomanip> |  | ||||||
| #include <iostream> |  | ||||||
| #include <sstream> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| #include "spdlog/common.h" |  | ||||||
| #include "spdlog/logger.h" |  | ||||||
| #include "spdlog/sinks/base_sink.h" |  | ||||||
| #include "spdlog/sinks/rotating_file_sink.h" |  | ||||||
| #include "spdlog/sinks/stdout_color_sinks.h" |  | ||||||
| #include "spdlog/spdlog.h" |  | ||||||
|  |  | ||||||
| using namespace std::chrono; |  | ||||||
|  |  | ||||||
| #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO |  | ||||||
|  |  | ||||||
| // SPDLOG_TRACE(...) |  | ||||||
| // SPDLOG_DEBUG(...) |  | ||||||
| // SPDLOG_INFO(...) |  | ||||||
| // SPDLOG_WARN(...) |  | ||||||
| // SPDLOG_ERROR(...) |  | ||||||
| // SPDLOG_CRITICAL(...) |  | ||||||
|  |  | ||||||
| #ifdef SIGNAL_LOGGER |  | ||||||
| constexpr auto LOGGER_NAME = "siganl_server"; |  | ||||||
| #else |  | ||||||
| constexpr auto LOGGER_NAME = "remote_desk"; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #define LOG_INFO(...)                                                         \ |  | ||||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ |  | ||||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ |  | ||||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ |  | ||||||
|     auto localTime = *std::gmtime(&timet);                                    \ |  | ||||||
|     std::stringstream ss;                                                     \ |  | ||||||
|     std::string filename;                                                     \ |  | ||||||
|     ss << LOGGER_NAME;                                                        \ |  | ||||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ |  | ||||||
|     ss >> filename;                                                           \ |  | ||||||
|     std::string path = "logs/" + filename;                                    \ |  | ||||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ |  | ||||||
|         path, 1048576 * 5, 3));                                               \ |  | ||||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ |  | ||||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ |  | ||||||
|     combined_logger->flush_on(spdlog::level::info);                           \ |  | ||||||
|     spdlog::register_logger(combined_logger);                                 \ |  | ||||||
|     SPDLOG_LOGGER_INFO(combined_logger, __VA_ARGS__);                         \ |  | ||||||
|   } else {                                                                    \ |  | ||||||
|     SPDLOG_LOGGER_INFO(spdlog::get(LOGGER_NAME), __VA_ARGS__);                \ |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #define LOG_WARN(...)                                                         \ |  | ||||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ |  | ||||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ |  | ||||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ |  | ||||||
|     auto localTime = *std::gmtime(&timet);                                    \ |  | ||||||
|     std::stringstream ss;                                                     \ |  | ||||||
|     std::string filename;                                                     \ |  | ||||||
|     ss << LOGGER_NAME;                                                        \ |  | ||||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ |  | ||||||
|     ss >> filename;                                                           \ |  | ||||||
|     std::string path = "logs/" + filename;                                    \ |  | ||||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ |  | ||||||
|         path, 1048576 * 5, 3));                                               \ |  | ||||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ |  | ||||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ |  | ||||||
|     spdlog::register_logger(combined_logger);                                 \ |  | ||||||
|     SPDLOG_LOGGER_WARN(combined_logger, __VA_ARGS__);                         \ |  | ||||||
|   } else {                                                                    \ |  | ||||||
|     SPDLOG_LOGGER_WARN(spdlog::get(LOGGER_NAME), __VA_ARGS__);                \ |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #define LOG_ERROR(...)                                                        \ |  | ||||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ |  | ||||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ |  | ||||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ |  | ||||||
|     auto localTime = *std::gmtime(&timet);                                    \ |  | ||||||
|     std::stringstream ss;                                                     \ |  | ||||||
|     std::string filename;                                                     \ |  | ||||||
|     ss << LOGGER_NAME;                                                        \ |  | ||||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ |  | ||||||
|     ss >> filename;                                                           \ |  | ||||||
|     std::string path = "logs/" + filename;                                    \ |  | ||||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ |  | ||||||
|         path, 1048576 * 5, 3));                                               \ |  | ||||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ |  | ||||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ |  | ||||||
|     spdlog::register_logger(combined_logger);                                 \ |  | ||||||
|     SPDLOG_LOGGER_ERROR(combined_logger, __VA_ARGS__);                        \ |  | ||||||
|   } else {                                                                    \ |  | ||||||
|     SPDLOG_LOGGER_ERROR(spdlog::get(LOGGER_NAME), __VA_ARGS__);               \ |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #define LOG_FATAL(...)                                                        \ |  | ||||||
|   if (nullptr == spdlog::get(LOGGER_NAME)) {                                  \ |  | ||||||
|     auto now = std::chrono::system_clock::now() + std::chrono::hours(8);      \ |  | ||||||
|     auto timet = std::chrono::system_clock::to_time_t(now);                   \ |  | ||||||
|     auto localTime = *std::gmtime(&timet);                                    \ |  | ||||||
|     std::stringstream ss;                                                     \ |  | ||||||
|     std::string filename;                                                     \ |  | ||||||
|     ss << LOGGER_NAME;                                                        \ |  | ||||||
|     ss << std::put_time(&localTime, "-%Y%m%d-%H%M%S.log");                    \ |  | ||||||
|     ss >> filename;                                                           \ |  | ||||||
|     std::string path = "logs/" + filename;                                    \ |  | ||||||
|     std::vector<spdlog::sink_ptr> sinks;                                      \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); \ |  | ||||||
|     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(   \ |  | ||||||
|         path, 1048576 * 5, 3));                                               \ |  | ||||||
|     auto combined_logger = std::make_shared<spdlog::logger>(                  \ |  | ||||||
|         LOGGER_NAME, begin(sinks), end(sinks));                               \ |  | ||||||
|     spdlog::register_logger(combined_logger);                                 \ |  | ||||||
|     SPDLOG_LOGGER_CRITICAL(combined_logger, __VA_ARGS__);                     \ |  | ||||||
|   } else {                                                                    \ |  | ||||||
|     SPDLOG_LOGGER_CRITICAL(spdlog::get(LOGGER_NAME), __VA_ARGS__);            \ |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
							
								
								
									
										62
									
								
								src/log/rd_log.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/log/rd_log.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <filesystem> | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | std::string g_log_dir = "logs"; | ||||||
|  | std::once_flag g_logger_once_flag; | ||||||
|  | std::shared_ptr<spdlog::logger> g_logger; | ||||||
|  | std::atomic<bool> g_logger_created{false}; | ||||||
|  |  | ||||||
|  | }  // namespace | ||||||
|  |  | ||||||
|  | void InitLogger(const std::string& log_dir) { | ||||||
|  |   if (g_logger_created.load()) { | ||||||
|  |     LOG_WARN( | ||||||
|  |         "InitLogger called after logger initialized. Ignoring log_dir: {}, " | ||||||
|  |         "using previous log_dir: {}", | ||||||
|  |         log_dir, g_log_dir); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   g_log_dir = log_dir; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::shared_ptr<spdlog::logger> get_logger() { | ||||||
|  |   std::call_once(g_logger_once_flag, []() { | ||||||
|  |     g_logger_created.store(true); | ||||||
|  |  | ||||||
|  |     std::error_code ec; | ||||||
|  |     std::filesystem::create_directories(g_log_dir, ec); | ||||||
|  |  | ||||||
|  |     auto now = std::chrono::system_clock::now() + std::chrono::hours(8); | ||||||
|  |     auto now_time = std::chrono::system_clock::to_time_t(now); | ||||||
|  |  | ||||||
|  |     std::tm tm_info; | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     gmtime_s(&tm_info, &now_time); | ||||||
|  | #else | ||||||
|  |     gmtime_r(&now_time, &tm_info); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     std::stringstream ss; | ||||||
|  |     ss << LOGGER_NAME; | ||||||
|  |     ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log"); | ||||||
|  |  | ||||||
|  |     std::string filename = g_log_dir + "/" + ss.str(); | ||||||
|  |  | ||||||
|  |     std::vector<spdlog::sink_ptr> sinks; | ||||||
|  |     sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>()); | ||||||
|  |     sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>( | ||||||
|  |         filename, 5 * 1024 * 1024, 3)); | ||||||
|  |  | ||||||
|  |     g_logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(), | ||||||
|  |                                                 sinks.end()); | ||||||
|  |     g_logger->flush_on(spdlog::level::info); | ||||||
|  |     spdlog::register_logger(g_logger); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   return g_logger; | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								src/log/rd_log.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/log/rd_log.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2025-07-21 | ||||||
|  |  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _RD_LOG_H_ | ||||||
|  | #define _RD_LOG_H_ | ||||||
|  |  | ||||||
|  | #include <chrono> | ||||||
|  | #include <iomanip> | ||||||
|  | #include <iostream> | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <sstream> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "spdlog/common.h" | ||||||
|  | #include "spdlog/logger.h" | ||||||
|  | #include "spdlog/sinks/base_sink.h" | ||||||
|  | #include "spdlog/sinks/rotating_file_sink.h" | ||||||
|  | #include "spdlog/sinks/stdout_color_sinks.h" | ||||||
|  | #include "spdlog/spdlog.h" | ||||||
|  |  | ||||||
|  | #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | ||||||
|  |  | ||||||
|  | constexpr auto LOGGER_NAME = "crossdesk"; | ||||||
|  |  | ||||||
|  | void InitLogger(const std::string& log_dir); | ||||||
|  |  | ||||||
|  | std::shared_ptr<spdlog::logger> get_logger(); | ||||||
|  |  | ||||||
|  | #define LOG_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__) | ||||||
|  | #define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__) | ||||||
|  | #define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__) | ||||||
|  | #define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__) | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -1,81 +0,0 @@ | |||||||
| /* |  | ||||||
|  * @Author: DI JUNKUN |  | ||||||
|  * @Date: 2024-06-14 |  | ||||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #ifndef _LAYOUT_STYLE_H_ |  | ||||||
| #define _LAYOUT_STYLE_H_ |  | ||||||
|  |  | ||||||
| #ifdef _WIN32 |  | ||||||
| #define MENU_WINDOW_WIDTH_CN 160 |  | ||||||
| #define MENU_WINDOW_HEIGHT_CN 245 |  | ||||||
| #define MENU_WINDOW_WIDTH_EN 190 |  | ||||||
| #define MENU_WINDOW_HEIGHT_EN 245 |  | ||||||
| #define IPUT_WINDOW_WIDTH 86 |  | ||||||
| #define INPUT_WINDOW_PADDING_CN 66 |  | ||||||
| #define INPUT_WINDOW_PADDING_EN 96 |  | ||||||
| #define SETTINGS_WINDOW_WIDTH_CN 181 |  | ||||||
| #define SETTINGS_WINDOW_WIDTH_EN 228 |  | ||||||
| #define SETTINGS_WINDOW_HEIGHT_CN 190 |  | ||||||
| #define SETTINGS_WINDOW_HEIGHT_EN 190 |  | ||||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_CN 100 |  | ||||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_EN 147 |  | ||||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 100 |  | ||||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 147 |  | ||||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 100 |  | ||||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 147 |  | ||||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 154 |  | ||||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 201 |  | ||||||
| #define SETTINGS_SELECT_WINDOW_WIDTH 73 |  | ||||||
| #define SETTINGS_OK_BUTTON_PADDING_CN 55 |  | ||||||
| #define SETTINGS_OK_BUTTON_PADDING_EN 78 |  | ||||||
| #elif __linux__ |  | ||||||
| #define MENU_WINDOW_WIDTH_CN 160 |  | ||||||
| #define MENU_WINDOW_HEIGHT_CN 245 |  | ||||||
| #define MENU_WINDOW_WIDTH_EN 190 |  | ||||||
| #define MENU_WINDOW_HEIGHT_EN 245 |  | ||||||
| #define IPUT_WINDOW_WIDTH 90 |  | ||||||
| #define INPUT_WINDOW_PADDING_CN 60 |  | ||||||
| #define INPUT_WINDOW_PADDING_EN 80 |  | ||||||
| #define SETTINGS_WINDOW_WIDTH_CN 188 |  | ||||||
| #define SETTINGS_WINDOW_WIDTH_EN 228 |  | ||||||
| #define SETTINGS_WINDOW_HEIGHT_CN 190 |  | ||||||
| #define SETTINGS_WINDOW_HEIGHT_EN 190 |  | ||||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_CN 100 |  | ||||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_EN 140 |  | ||||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 100 |  | ||||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 140 |  | ||||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 100 |  | ||||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 140 |  | ||||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 161 |  | ||||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 201 |  | ||||||
| #define SETTINGS_SELECT_WINDOW_WIDTH 60 |  | ||||||
| #define SETTINGS_OK_BUTTON_PADDING_CN 60 |  | ||||||
| #define SETTINGS_OK_BUTTON_PADDING_EN 80 |  | ||||||
| #elif __APPLE__ |  | ||||||
| #define MENU_WINDOW_WIDTH_CN 148 |  | ||||||
| #define MENU_WINDOW_HEIGHT_CN 244 |  | ||||||
| #define MENU_WINDOW_WIDTH_EN 148 |  | ||||||
| #define MENU_WINDOW_HEIGHT_EN 244 |  | ||||||
| #define IPUT_WINDOW_WIDTH 77 |  | ||||||
| #define INPUT_WINDOW_PADDING_CN 63 |  | ||||||
| #define INPUT_WINDOW_PADDING_EN 63 |  | ||||||
| #define SETTINGS_WINDOW_WIDTH_CN 160 |  | ||||||
| #define SETTINGS_WINDOW_WIDTH_EN 220 |  | ||||||
| #define SETTINGS_WINDOW_HEIGHT_CN 190 |  | ||||||
| #define SETTINGS_WINDOW_HEIGHT_EN 190 |  | ||||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_CN 90 |  | ||||||
| #define LANGUAGE_SELECT_WINDOW_PADDING_EN 150 |  | ||||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 90 |  | ||||||
| #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 150 |  | ||||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 90 |  | ||||||
| #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 150 |  | ||||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECK_WINDOW_PADDING_CN 133 |  | ||||||
| #define ENABLE_HARDWARE_VIDEO_CODEC_CHECK_WINDOW_PADDING_EN 193 |  | ||||||
| #define SETTINGS_SELECT_WINDOW_WIDTH 62 |  | ||||||
| #define SETTINGS_OK_BUTTON_PADDING_CN 50 |  | ||||||
| #define SETTINGS_OK_BUTTON_PADDING_EN 80 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
| @@ -1,888 +0,0 @@ | |||||||
| #include "main_window.h" |  | ||||||
|  |  | ||||||
| #include <fstream> |  | ||||||
| #include <iostream> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| #include "device_controller_factory.h" |  | ||||||
| #include "layout_style.h" |  | ||||||
| #include "localization.h" |  | ||||||
| #include "log.h" |  | ||||||
| #include "platform.h" |  | ||||||
| #include "screen_capturer_factory.h" |  | ||||||
|  |  | ||||||
| // Refresh Event |  | ||||||
| #define REFRESH_EVENT (SDL_USEREVENT + 1) |  | ||||||
| #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 |  | ||||||
|  |  | ||||||
| MainWindow::MainWindow() {} |  | ||||||
|  |  | ||||||
| MainWindow::~MainWindow() {} |  | ||||||
|  |  | ||||||
| int MainWindow::SaveSettingsIntoCacheFile() { |  | ||||||
|   cd_cache_file_ = fopen("cache.cd", "w+"); |  | ||||||
|   if (!cd_cache_file_) { |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fseek(cd_cache_file_, 0, SEEK_SET); |  | ||||||
|   strncpy(cd_cache_.password, input_password_, sizeof(input_password_)); |  | ||||||
|   memcpy(&cd_cache_.language, &language_button_value_, |  | ||||||
|          sizeof(language_button_value_)); |  | ||||||
|   memcpy(&cd_cache_.video_quality, &video_quality_button_value_, |  | ||||||
|          sizeof(video_quality_button_value_)); |  | ||||||
|   memcpy(&cd_cache_.video_encode_format, &video_encode_format_button_value_, |  | ||||||
|          sizeof(video_encode_format_button_value_)); |  | ||||||
|   memcpy(&cd_cache_.enable_hardware_video_codec, &enable_hardware_video_codec_, |  | ||||||
|          sizeof(enable_hardware_video_codec_)); |  | ||||||
|   fwrite(&cd_cache_, sizeof(cd_cache_), 1, cd_cache_file_); |  | ||||||
|   fclose(cd_cache_file_); |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::LoadSettingsIntoCacheFile() { |  | ||||||
|   cd_cache_file_ = fopen("cache.cd", "r+"); |  | ||||||
|   if (!cd_cache_file_) { |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fseek(cd_cache_file_, 0, SEEK_SET); |  | ||||||
|   fread(&cd_cache_, sizeof(cd_cache_), 1, cd_cache_file_); |  | ||||||
|   fclose(cd_cache_file_); |  | ||||||
|   strncpy(input_password_, cd_cache_.password, sizeof(cd_cache_.password)); |  | ||||||
|   language_button_value_ = cd_cache_.language; |  | ||||||
|   video_quality_button_value_ = cd_cache_.video_quality; |  | ||||||
|   video_encode_format_button_value_ = cd_cache_.video_encode_format; |  | ||||||
|   enable_hardware_video_codec_ = cd_cache_.enable_hardware_video_codec; |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::StartScreenCapture() { |  | ||||||
|   screen_capturer_ = (ScreenCapturer *)screen_capturer_factory_->Create(); |  | ||||||
|   ScreenCapturer::RECORD_DESKTOP_RECT rect; |  | ||||||
|   rect.left = 0; |  | ||||||
|   rect.top = 0; |  | ||||||
|   rect.right = screen_width_; |  | ||||||
|   rect.bottom = screen_height_; |  | ||||||
|   last_frame_time_ = std::chrono::high_resolution_clock::now(); |  | ||||||
|  |  | ||||||
|   int screen_capturer_init_ret = screen_capturer_->Init( |  | ||||||
|       rect, 60, |  | ||||||
|       [this](unsigned char *data, int size, int width, int height) -> void { |  | ||||||
|         auto now_time = std::chrono::high_resolution_clock::now(); |  | ||||||
|         std::chrono::duration<double> duration = now_time - last_frame_time_; |  | ||||||
|         auto tc = duration.count() * 1000; |  | ||||||
|  |  | ||||||
|         if (tc >= 0) { |  | ||||||
|           SendData(peer_, DATA_TYPE::VIDEO, (const char *)data, |  | ||||||
|                    NV12_BUFFER_SIZE); |  | ||||||
|           last_frame_time_ = now_time; |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|   if (0 == screen_capturer_init_ret) { |  | ||||||
|     screen_capturer_->Start(); |  | ||||||
|   } else { |  | ||||||
|     screen_capturer_->Destroy(); |  | ||||||
|     delete screen_capturer_; |  | ||||||
|     screen_capturer_ = nullptr; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::StopScreenCapture() { |  | ||||||
|   if (screen_capturer_) { |  | ||||||
|     LOG_INFO("Destroy screen capturer") |  | ||||||
|     screen_capturer_->Destroy(); |  | ||||||
|     delete screen_capturer_; |  | ||||||
|     screen_capturer_ = nullptr; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::StartMouseControl() { |  | ||||||
|   device_controller_factory_ = new DeviceControllerFactory(); |  | ||||||
|   mouse_controller_ = (MouseController *)device_controller_factory_->Create( |  | ||||||
|       DeviceControllerFactory::Device::Mouse); |  | ||||||
|   int mouse_controller_init_ret = |  | ||||||
|       mouse_controller_->Init(screen_width_, screen_height_); |  | ||||||
|   if (0 != mouse_controller_init_ret) { |  | ||||||
|     LOG_INFO("Destroy mouse controller") |  | ||||||
|     mouse_controller_->Destroy(); |  | ||||||
|     mouse_controller_ = nullptr; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::StopMouseControl() { |  | ||||||
|   if (mouse_controller_) { |  | ||||||
|     mouse_controller_->Destroy(); |  | ||||||
|     delete mouse_controller_; |  | ||||||
|     mouse_controller_ = nullptr; |  | ||||||
|   } |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::CreateConnectionPeer() { |  | ||||||
|   mac_addr_str_ = GetMac(); |  | ||||||
|  |  | ||||||
|   params_.use_cfg_file = false; |  | ||||||
|   params_.signal_server_ip = "150.158.81.30"; |  | ||||||
|   params_.signal_server_port = 9099; |  | ||||||
|   params_.stun_server_ip = "150.158.81.30"; |  | ||||||
|   params_.stun_server_port = 3478; |  | ||||||
|   params_.turn_server_ip = "150.158.81.30"; |  | ||||||
|   params_.turn_server_port = 3478; |  | ||||||
|   params_.turn_server_username = "dijunkun"; |  | ||||||
|   params_.turn_server_password = "dijunkunpw"; |  | ||||||
|   params_.hardware_acceleration = config_center_.IsHardwareVideoCodec(); |  | ||||||
|   params_.av1_encoding = config_center_.GetVideoEncodeFormat() == |  | ||||||
|                                  ConfigCenter::VIDEO_ENCODE_FORMAT::AV1 |  | ||||||
|                              ? true |  | ||||||
|                              : false; |  | ||||||
|   params_.on_receive_video_buffer = OnReceiveVideoBufferCb; |  | ||||||
|   params_.on_receive_audio_buffer = OnReceiveAudioBufferCb; |  | ||||||
|   params_.on_receive_data_buffer = OnReceiveDataBufferCb; |  | ||||||
|   params_.on_signal_status = OnSignalStatusCb; |  | ||||||
|   params_.on_connection_status = OnConnectionStatusCb; |  | ||||||
|   params_.user_data = this; |  | ||||||
|  |  | ||||||
|   peer_ = CreatePeer(¶ms_); |  | ||||||
|   if (peer_) { |  | ||||||
|     LOG_INFO("Create peer instance successful"); |  | ||||||
|     local_id_ = mac_addr_str_; |  | ||||||
|     Init(peer_, local_id_.c_str()); |  | ||||||
|     LOG_INFO("Peer init finish"); |  | ||||||
|   } else { |  | ||||||
|     LOG_INFO("Create peer instance failed"); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int MainWindow::Run() { |  | ||||||
|   LoadSettingsIntoCacheFile(); |  | ||||||
|  |  | ||||||
|   localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_; |  | ||||||
|   localization_language_index_ = language_button_value_; |  | ||||||
|  |  | ||||||
|   // Setup SDL |  | ||||||
|   if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | |  | ||||||
|                SDL_INIT_GAMECONTROLLER) != 0) { |  | ||||||
|     printf("Error: %s\n", SDL_GetError()); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // From 2.0.18: Enable native IME. |  | ||||||
| #ifdef SDL_HINT_IME_SHOW_UI |  | ||||||
|   SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   // Create main window with SDL_Renderer graphics context |  | ||||||
|   SDL_WindowFlags window_flags = |  | ||||||
|       (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); |  | ||||||
|   main_window_ = SDL_CreateWindow("Remote Desk", SDL_WINDOWPOS_CENTERED, |  | ||||||
|                                   SDL_WINDOWPOS_CENTERED, main_window_width_, |  | ||||||
|                                   main_window_height_, window_flags); |  | ||||||
|  |  | ||||||
|   SDL_DisplayMode DM; |  | ||||||
|   SDL_GetCurrentDisplayMode(0, &DM); |  | ||||||
|   screen_width_ = DM.w; |  | ||||||
|   screen_height_ = DM.h; |  | ||||||
|  |  | ||||||
|   sdl_renderer_ = SDL_CreateRenderer( |  | ||||||
|       main_window_, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); |  | ||||||
|   if (sdl_renderer_ == nullptr) { |  | ||||||
|     SDL_Log("Error creating SDL_Renderer!"); |  | ||||||
|     return 0; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   pixformat_ = SDL_PIXELFORMAT_NV12; |  | ||||||
|  |  | ||||||
|   sdl_texture_ = |  | ||||||
|       SDL_CreateTexture(sdl_renderer_, pixformat_, SDL_TEXTUREACCESS_STREAMING, |  | ||||||
|                         texture_width_, texture_height_); |  | ||||||
|  |  | ||||||
|   // Setup Dear ImGui context |  | ||||||
|   IMGUI_CHECKVERSION(); |  | ||||||
|   ImGui::CreateContext(); |  | ||||||
|   ImGuiIO &io = ImGui::GetIO(); |  | ||||||
|  |  | ||||||
|   io.ConfigFlags |= |  | ||||||
|       ImGuiConfigFlags_NavEnableKeyboard;  // Enable Keyboard Controls |  | ||||||
|   io.ConfigFlags |= |  | ||||||
|       ImGuiConfigFlags_NavEnableGamepad;  // Enable Gamepad Controls |  | ||||||
|  |  | ||||||
|   if (config_center_.GetLanguage() == ConfigCenter::LANGUAGE::CHINESE) { |  | ||||||
|     // Load Fonts |  | ||||||
| #ifdef _WIN32 |  | ||||||
|     std::string default_font_path = "c:/windows/fonts/simhei.ttf"; |  | ||||||
|     std::ifstream font_path_f(default_font_path.c_str()); |  | ||||||
|     std::string font_path = |  | ||||||
|         font_path_f.good() ? "c:/windows/fonts/simhei.ttf" : ""; |  | ||||||
|     if (!font_path.empty()) { |  | ||||||
|       io.Fonts->AddFontFromFileTTF(font_path.c_str(), 13.0f, NULL, |  | ||||||
|                                    io.Fonts->GetGlyphRangesChineseFull()); |  | ||||||
|     } |  | ||||||
| #elif __APPLE__ |  | ||||||
|     std::string default_font_path = "/System/Library/Fonts/PingFang.ttc"; |  | ||||||
|     std::ifstream font_path_f(default_font_path.c_str()); |  | ||||||
|     std::string font_path = |  | ||||||
|         font_path_f.good() ? "/System/Library/Fonts/PingFang.ttc" : ""; |  | ||||||
|     if (!font_path.empty()) { |  | ||||||
|       io.Fonts->AddFontFromFileTTF(font_path.c_str(), 13.0f, NULL, |  | ||||||
|                                    io.Fonts->GetGlyphRangesChineseFull()); |  | ||||||
|     } |  | ||||||
| #elif __linux__ |  | ||||||
|     io.Fonts->AddFontFromFileTTF("c:/windows/fonts/msyh.ttc", 13.0f, NULL, |  | ||||||
|                                  io.Fonts->GetGlyphRangesChineseFull()); |  | ||||||
| #endif |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Setup Dear ImGui style |  | ||||||
|   // ImGui::StyleColorsDark(); |  | ||||||
|   ImGui::StyleColorsLight(); |  | ||||||
|  |  | ||||||
|   // Setup Platform/Renderer backends |  | ||||||
|   ImGui_ImplSDL2_InitForSDLRenderer(main_window_, sdl_renderer_); |  | ||||||
|   ImGui_ImplSDLRenderer2_Init(sdl_renderer_); |  | ||||||
|  |  | ||||||
|   // Our state |  | ||||||
|   ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); |  | ||||||
|  |  | ||||||
|   CreateConnectionPeer(); |  | ||||||
|  |  | ||||||
|   { |  | ||||||
|     nv12_buffer_ = new char[NV12_BUFFER_SIZE]; |  | ||||||
|  |  | ||||||
|     // Screen capture |  | ||||||
|     screen_capturer_factory_ = new ScreenCapturerFactory(); |  | ||||||
|  |  | ||||||
|     // Mouse control |  | ||||||
|     device_controller_factory_ = new DeviceControllerFactory(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Main loop |  | ||||||
|   while (!exit_) { |  | ||||||
|     if (SignalStatus::SignalConnected == signal_status_ && |  | ||||||
|         !is_create_connection_) { |  | ||||||
|       is_create_connection_ = |  | ||||||
|           CreateConnection(peer_, mac_addr_str_.c_str(), input_password_) |  | ||||||
|               ? false |  | ||||||
|               : true; |  | ||||||
|       LOG_INFO("Connected with signal server, create p2p connection"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!inited_ || |  | ||||||
|         localization_language_index_last_ != localization_language_index_) { |  | ||||||
|       connect_button_label_ = |  | ||||||
|           connect_button_pressed_ |  | ||||||
|               ? localization::disconnect[localization_language_index_] |  | ||||||
|               : localization::connect[localization_language_index_]; |  | ||||||
|       fullscreen_button_label_ = |  | ||||||
|           fullscreen_button_pressed_ |  | ||||||
|               ? localization::exit_fullscreen[localization_language_index_] |  | ||||||
|               : localization::fullscreen[localization_language_index_]; |  | ||||||
|  |  | ||||||
|       mouse_control_button_label_ = |  | ||||||
|           mouse_control_button_pressed_ |  | ||||||
|               ? localization::release_mouse[localization_language_index_] |  | ||||||
|               : localization::control_mouse[localization_language_index_]; |  | ||||||
|  |  | ||||||
|       settings_button_label_ = |  | ||||||
|           localization::settings[localization_language_index_]; |  | ||||||
|       inited_ = true; |  | ||||||
|       localization_language_index_last_ = localization_language_index_; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (start_screen_capture_ && !screen_capture_is_started_) { |  | ||||||
|       StartScreenCapture(); |  | ||||||
|       screen_capture_is_started_ = true; |  | ||||||
|     } else if (!start_screen_capture_ && screen_capture_is_started_) { |  | ||||||
|       StopScreenCapture(); |  | ||||||
|       screen_capture_is_started_ = false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (start_mouse_control_ && !mouse_control_is_started_) { |  | ||||||
|       StartMouseControl(); |  | ||||||
|       mouse_control_is_started_ = true; |  | ||||||
|     } else if (!start_mouse_control_ && mouse_control_is_started_) { |  | ||||||
|       StopMouseControl(); |  | ||||||
|       mouse_control_is_started_ = false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Start the Dear ImGui frame |  | ||||||
|     ImGui_ImplSDLRenderer2_NewFrame(); |  | ||||||
|     ImGui_ImplSDL2_NewFrame(); |  | ||||||
|     ImGui::NewFrame(); |  | ||||||
|  |  | ||||||
|     if (connection_established_ && !subwindow_hovered_ && control_mouse_) { |  | ||||||
|       ImGui::SetMouseCursor(ImGuiMouseCursor_None); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // main window layout |  | ||||||
|     { |  | ||||||
|       ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Once); |  | ||||||
|  |  | ||||||
|       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|         ImGui::SetNextWindowSize( |  | ||||||
|             ImVec2(MENU_WINDOW_WIDTH_CN, MENU_WINDOW_HEIGHT_CN)); |  | ||||||
|       } else { |  | ||||||
|         ImGui::SetNextWindowSize( |  | ||||||
|             ImVec2(MENU_WINDOW_WIDTH_EN, MENU_WINDOW_HEIGHT_EN)); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (!connection_established_) { |  | ||||||
|         ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); |  | ||||||
|         ImGui::Begin(localization::menu[localization_language_index_].c_str(), |  | ||||||
|                      nullptr, |  | ||||||
|                      ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | |  | ||||||
|                          ImGuiWindowFlags_NoMove); |  | ||||||
|       } else { |  | ||||||
|         // ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); |  | ||||||
|         ImGui::Begin(localization::menu[localization_language_index_].c_str(), |  | ||||||
|                      nullptr, ImGuiWindowFlags_None); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       { |  | ||||||
|         subwindow_hovered_ = ImGui::IsWindowHovered(); |  | ||||||
|  |  | ||||||
|         // local |  | ||||||
|         { |  | ||||||
|           ImGui::Text( |  | ||||||
|               localization::local_id[localization_language_index_].c_str()); |  | ||||||
|  |  | ||||||
|           ImGui::SameLine(); |  | ||||||
|           ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::InputText("##local_id", (char *)mac_addr_str_.c_str(), |  | ||||||
|                            mac_addr_str_.length() + 1, |  | ||||||
|                            ImGuiInputTextFlags_CharsUppercase | |  | ||||||
|                                ImGuiInputTextFlags_ReadOnly); |  | ||||||
|  |  | ||||||
|           ImGui::Text( |  | ||||||
|               localization::password[localization_language_index_].c_str()); |  | ||||||
|  |  | ||||||
|           ImGui::SameLine(); |  | ||||||
|  |  | ||||||
|           strncpy(input_password_tmp_, input_password_, |  | ||||||
|                   sizeof(input_password_)); |  | ||||||
|           ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::InputTextWithHint( |  | ||||||
|               "##server_pwd", |  | ||||||
|               localization::max_password_len[localization_language_index_] |  | ||||||
|                   .c_str(), |  | ||||||
|               input_password_, IM_ARRAYSIZE(input_password_), |  | ||||||
|               ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
|  |  | ||||||
|           if (strcmp(input_password_tmp_, input_password_)) { |  | ||||||
|             SaveSettingsIntoCacheFile(); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ImGui::Spacing(); |  | ||||||
|         ImGui::Separator(); |  | ||||||
|         ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|         // remote |  | ||||||
|         { |  | ||||||
|           ImGui::Text( |  | ||||||
|               localization::remote_id[localization_language_index_].c_str()); |  | ||||||
|  |  | ||||||
|           ImGui::SameLine(); |  | ||||||
|           ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::InputTextWithHint("##remote_id_", mac_addr_str_.c_str(), |  | ||||||
|                                    remote_id_, IM_ARRAYSIZE(remote_id_), |  | ||||||
|                                    ImGuiInputTextFlags_CharsUppercase | |  | ||||||
|                                        ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
|  |  | ||||||
|           ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|           ImGui::Text( |  | ||||||
|               localization::password[localization_language_index_].c_str()); |  | ||||||
|  |  | ||||||
|           ImGui::SameLine(); |  | ||||||
|           ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); |  | ||||||
|  |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(INPUT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           ImGui::InputTextWithHint( |  | ||||||
|               "##client_pwd", |  | ||||||
|               localization::max_password_len[localization_language_index_] |  | ||||||
|                   .c_str(), |  | ||||||
|               client_password_, IM_ARRAYSIZE(client_password_), |  | ||||||
|               ImGuiInputTextFlags_CharsNoBlank); |  | ||||||
|  |  | ||||||
|           ImGui::Spacing(); |  | ||||||
|           ImGui::Separator(); |  | ||||||
|           ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|           if (ImGui::Button(connect_button_label_.c_str()) || rejoin_) { |  | ||||||
|             int ret = -1; |  | ||||||
|             if ("SignalConnected" == signal_status_str_) { |  | ||||||
|               if (connect_button_label_ == |  | ||||||
|                       localization::connect[localization_language_index_] && |  | ||||||
|                   !connection_established_ && strlen(remote_id_)) { |  | ||||||
|                 if (remote_id_ == local_id_ && !peer_reserved_) { |  | ||||||
|                   peer_reserved_ = CreatePeer(¶ms_); |  | ||||||
|                   if (peer_reserved_) { |  | ||||||
|                     LOG_INFO("Create peer[reserved] instance successful"); |  | ||||||
|                     std::string local_id = "C-" + mac_addr_str_; |  | ||||||
|                     Init(peer_reserved_, local_id.c_str()); |  | ||||||
|                     LOG_INFO("Peer[reserved] init finish"); |  | ||||||
|                   } else { |  | ||||||
|                     LOG_INFO("Create peer[reserved] instance failed"); |  | ||||||
|                   } |  | ||||||
|                 } |  | ||||||
|                 ret = JoinConnection(peer_reserved_ ? peer_reserved_ : peer_, |  | ||||||
|                                      remote_id_, client_password_); |  | ||||||
|                 if (0 == ret) { |  | ||||||
|                   if (!peer_reserved_) { |  | ||||||
|                     is_client_mode_ = true; |  | ||||||
|                   } |  | ||||||
|                   rejoin_ = false; |  | ||||||
|                 } else { |  | ||||||
|                   rejoin_ = true; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|               } else if (connect_button_label_ == |  | ||||||
|                              localization::disconnect |  | ||||||
|                                  [localization_language_index_] && |  | ||||||
|                          connection_established_) { |  | ||||||
|                 ret = LeaveConnection(peer_reserved_ ? peer_reserved_ : peer_); |  | ||||||
|  |  | ||||||
|                 if (0 == ret) { |  | ||||||
|                   rejoin_ = false; |  | ||||||
|                   memset(audio_buffer_, 0, 960); |  | ||||||
|                   connection_established_ = false; |  | ||||||
|                   received_frame_ = false; |  | ||||||
|                   is_client_mode_ = false; |  | ||||||
|                 } |  | ||||||
|               } |  | ||||||
|  |  | ||||||
|               if (0 == ret) { |  | ||||||
|                 connect_button_pressed_ = !connect_button_pressed_; |  | ||||||
|                 connect_button_label_ = |  | ||||||
|                     connect_button_pressed_ |  | ||||||
|                         ? localization::disconnect[localization_language_index_] |  | ||||||
|                         : localization::connect[localization_language_index_]; |  | ||||||
|               } |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|       ImGui::Separator(); |  | ||||||
|  |  | ||||||
|       ImGui::Spacing(); |  | ||||||
|       // Mouse control |  | ||||||
|       if (ImGui::Button(mouse_control_button_label_.c_str())) { |  | ||||||
|         if (mouse_control_button_label_ == |  | ||||||
|                 localization::control_mouse[localization_language_index_] && |  | ||||||
|             connection_established_) { |  | ||||||
|           mouse_control_button_pressed_ = true; |  | ||||||
|           control_mouse_ = true; |  | ||||||
|           mouse_control_button_label_ = |  | ||||||
|               localization::release_mouse[localization_language_index_]; |  | ||||||
|         } else { |  | ||||||
|           control_mouse_ = false; |  | ||||||
|           mouse_control_button_label_ = |  | ||||||
|               localization::control_mouse[localization_language_index_]; |  | ||||||
|         } |  | ||||||
|         mouse_control_button_pressed_ = !mouse_control_button_pressed_; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       ImGui::SameLine(); |  | ||||||
|       // Fullscreen |  | ||||||
|       if (ImGui::Button(fullscreen_button_label_.c_str())) { |  | ||||||
|         if (fullscreen_button_label_ == |  | ||||||
|             localization::fullscreen[localization_language_index_]) { |  | ||||||
|           main_window_width_before_fullscreen_ = main_window_width_; |  | ||||||
|           main_window_height_before_fullscreen_ = main_window_height_; |  | ||||||
|           SDL_SetWindowFullscreen(main_window_, SDL_WINDOW_FULLSCREEN_DESKTOP); |  | ||||||
|           fullscreen_button_label_ = |  | ||||||
|               localization::exit_fullscreen[localization_language_index_]; |  | ||||||
|         } else { |  | ||||||
|           SDL_SetWindowFullscreen(main_window_, SDL_FALSE); |  | ||||||
|           SDL_SetWindowSize(main_window_, main_window_width_before_fullscreen_, |  | ||||||
|                             main_window_height_before_fullscreen_); |  | ||||||
|           main_window_width_ = main_window_width_before_fullscreen_; |  | ||||||
|           main_window_height_ = main_window_height_before_fullscreen_; |  | ||||||
|           fullscreen_button_label_ = |  | ||||||
|               localization::fullscreen[localization_language_index_]; |  | ||||||
|         } |  | ||||||
|         fullscreen_button_pressed_ = !fullscreen_button_pressed_; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|       ImGui::Separator(); |  | ||||||
|  |  | ||||||
|       ImGui::Spacing(); |  | ||||||
|  |  | ||||||
|       if (ImGui::Button(settings_button_label_.c_str())) { |  | ||||||
|         settings_button_pressed_ = !settings_button_pressed_; |  | ||||||
|         settings_window_pos_reset_ = true; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (settings_button_pressed_) { |  | ||||||
|         if (settings_window_pos_reset_) { |  | ||||||
|           const ImGuiViewport *viewport = ImGui::GetMainViewport(); |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetNextWindowPos( |  | ||||||
|                 ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - |  | ||||||
|                         SETTINGS_WINDOW_WIDTH_CN) / |  | ||||||
|                            2, |  | ||||||
|                        (viewport->WorkSize.y - viewport->WorkPos.y - |  | ||||||
|                         SETTINGS_WINDOW_HEIGHT_CN) / |  | ||||||
|                            2)); |  | ||||||
|  |  | ||||||
|             ImGui::SetNextWindowSize( |  | ||||||
|                 ImVec2(SETTINGS_WINDOW_WIDTH_CN, SETTINGS_WINDOW_HEIGHT_CN)); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetNextWindowPos( |  | ||||||
|                 ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - |  | ||||||
|                         SETTINGS_WINDOW_WIDTH_EN) / |  | ||||||
|                            2, |  | ||||||
|                        (viewport->WorkSize.y - viewport->WorkPos.y - |  | ||||||
|                         SETTINGS_WINDOW_HEIGHT_EN) / |  | ||||||
|                            2)); |  | ||||||
|  |  | ||||||
|             ImGui::SetNextWindowSize( |  | ||||||
|                 ImVec2(SETTINGS_WINDOW_WIDTH_EN, SETTINGS_WINDOW_HEIGHT_EN)); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           settings_window_pos_reset_ = false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Settings |  | ||||||
|         ImGui::Begin( |  | ||||||
|             localization::settings[localization_language_index_].c_str(), |  | ||||||
|             nullptr, |  | ||||||
|             ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | |  | ||||||
|                 ImGuiWindowFlags_NoSavedSettings); |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|           subwindow_hovered_ = ImGui::IsWindowHovered(); |  | ||||||
|  |  | ||||||
|           const char *language_items[] = { |  | ||||||
|               localization::language_zh[localization_language_index_].c_str(), |  | ||||||
|               localization::language_en[localization_language_index_].c_str()}; |  | ||||||
|  |  | ||||||
|           ImGui::SetCursorPosY(32); |  | ||||||
|           ImGui::Text( |  | ||||||
|               localization::language[localization_language_index_].c_str()); |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::SetCursorPosY(30); |  | ||||||
|           ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); |  | ||||||
|  |  | ||||||
|           ImGui::Combo("##language", &language_button_value_, language_items, |  | ||||||
|                        IM_ARRAYSIZE(language_items)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ImGui::Separator(); |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|           const char *video_quality_items[] = { |  | ||||||
|               localization::video_quality_high[localization_language_index_] |  | ||||||
|                   .c_str(), |  | ||||||
|               localization::video_quality_medium[localization_language_index_] |  | ||||||
|                   .c_str(), |  | ||||||
|               localization::video_quality_low[localization_language_index_] |  | ||||||
|                   .c_str()}; |  | ||||||
|  |  | ||||||
|           ImGui::SetCursorPosY(62); |  | ||||||
|           ImGui::Text(localization::video_quality[localization_language_index_] |  | ||||||
|                           .c_str()); |  | ||||||
|  |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::SetCursorPosY(60); |  | ||||||
|           ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); |  | ||||||
|  |  | ||||||
|           ImGui::Combo("##video_quality", &video_quality_button_value_, |  | ||||||
|                        video_quality_items, IM_ARRAYSIZE(video_quality_items)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ImGui::Separator(); |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|           const char *video_encode_format_items[] = { |  | ||||||
|               localization::av1[localization_language_index_].c_str(), |  | ||||||
|               localization::h264[localization_language_index_].c_str()}; |  | ||||||
|  |  | ||||||
|           ImGui::SetCursorPosY(92); |  | ||||||
|           ImGui::Text( |  | ||||||
|               localization::video_encode_format[localization_language_index_] |  | ||||||
|                   .c_str()); |  | ||||||
|  |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::SetCursorPosY(90); |  | ||||||
|           ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); |  | ||||||
|  |  | ||||||
|           ImGui::Combo("##video_encode_format", |  | ||||||
|                        &video_encode_format_button_value_, |  | ||||||
|                        video_encode_format_items, |  | ||||||
|                        IM_ARRAYSIZE(video_encode_format_items)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ImGui::Separator(); |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|           ImGui::SetCursorPosY(122); |  | ||||||
|           ImGui::Text(localization::enable_hardware_video_codec |  | ||||||
|                           [localization_language_index_] |  | ||||||
|                               .c_str()); |  | ||||||
|  |  | ||||||
|           if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|             ImGui::SetCursorPosX( |  | ||||||
|                 ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN); |  | ||||||
|           } else { |  | ||||||
|             ImGui::SetCursorPosX( |  | ||||||
|                 ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN); |  | ||||||
|           } |  | ||||||
|           ImGui::SetCursorPosY(120); |  | ||||||
|           ImGui::Checkbox("##enable_hardware_video_codec", |  | ||||||
|                           &enable_hardware_video_codec_); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { |  | ||||||
|           ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_CN); |  | ||||||
|         } else { |  | ||||||
|           ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN); |  | ||||||
|         } |  | ||||||
|         ImGui::SetCursorPosY(160.0f); |  | ||||||
|  |  | ||||||
|         // OK |  | ||||||
|         if (ImGui::Button( |  | ||||||
|                 localization::ok[localization_language_index_].c_str())) { |  | ||||||
|           settings_button_pressed_ = false; |  | ||||||
|  |  | ||||||
|           // Language |  | ||||||
|           if (language_button_value_ == 0) { |  | ||||||
|             config_center_.SetLanguage(ConfigCenter::LANGUAGE::CHINESE); |  | ||||||
|           } else { |  | ||||||
|             config_center_.SetLanguage(ConfigCenter::LANGUAGE::ENGLISH); |  | ||||||
|           } |  | ||||||
|           language_button_value_last_ = language_button_value_; |  | ||||||
|           localization_language_ = |  | ||||||
|               (ConfigCenter::LANGUAGE)language_button_value_; |  | ||||||
|           localization_language_index_ = language_button_value_; |  | ||||||
|           LOG_INFO("Set localization language: {}", |  | ||||||
|                    localization_language_index_ == 0 ? "zh" : "en"); |  | ||||||
|  |  | ||||||
|           // Video quality |  | ||||||
|           if (video_quality_button_value_ == 0) { |  | ||||||
|             config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH); |  | ||||||
|           } else if (video_quality_button_value_ == 1) { |  | ||||||
|             config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM); |  | ||||||
|           } else { |  | ||||||
|             config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW); |  | ||||||
|           } |  | ||||||
|           video_quality_button_value_last_ = video_quality_button_value_; |  | ||||||
|  |  | ||||||
|           // Video encode format |  | ||||||
|           if (video_encode_format_button_value_ == 0) { |  | ||||||
|             config_center_.SetVideoEncodeFormat( |  | ||||||
|                 ConfigCenter::VIDEO_ENCODE_FORMAT::AV1); |  | ||||||
|           } else if (video_encode_format_button_value_ == 1) { |  | ||||||
|             config_center_.SetVideoEncodeFormat( |  | ||||||
|                 ConfigCenter::VIDEO_ENCODE_FORMAT::H264); |  | ||||||
|           } |  | ||||||
|           video_encode_format_button_value_last_ = |  | ||||||
|               video_encode_format_button_value_; |  | ||||||
|  |  | ||||||
|           // Hardware video codec |  | ||||||
|           if (enable_hardware_video_codec_) { |  | ||||||
|             config_center_.SetHardwareVideoCodec(true); |  | ||||||
|           } else { |  | ||||||
|             config_center_.SetHardwareVideoCodec(false); |  | ||||||
|           } |  | ||||||
|           enable_hardware_video_codec_last_ = enable_hardware_video_codec_; |  | ||||||
|  |  | ||||||
|           SaveSettingsIntoCacheFile(); |  | ||||||
|           settings_window_pos_reset_ = true; |  | ||||||
|  |  | ||||||
|           // Recreate peer instance |  | ||||||
|           LoadSettingsIntoCacheFile(); |  | ||||||
|  |  | ||||||
|           // Recreate peer instance |  | ||||||
|           { |  | ||||||
|             DestroyPeer(peer_); |  | ||||||
|             CreateConnectionPeer(); |  | ||||||
|             LOG_INFO("Recreate peer instance successful"); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         ImGui::SameLine(); |  | ||||||
|         // Cancel |  | ||||||
|         if (ImGui::Button( |  | ||||||
|                 localization::cancel[localization_language_index_].c_str())) { |  | ||||||
|           settings_button_pressed_ = 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_; |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           settings_window_pos_reset_ = true; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ImGui::End(); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       ImGui::End(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Rendering |  | ||||||
|     ImGui::Render(); |  | ||||||
|     SDL_RenderSetScale(sdl_renderer_, io.DisplayFramebufferScale.x, |  | ||||||
|                        io.DisplayFramebufferScale.y); |  | ||||||
|  |  | ||||||
|     SDL_Event event; |  | ||||||
|     while (SDL_PollEvent(&event)) { |  | ||||||
|       ImGui_ImplSDL2_ProcessEvent(&event); |  | ||||||
|       if (event.type == SDL_QUIT) { |  | ||||||
|         exit_ = true; |  | ||||||
|       } else if (event.type == SDL_WINDOWEVENT && |  | ||||||
|                  event.window.event == SDL_WINDOWEVENT_RESIZED) { |  | ||||||
|         int window_w_last = main_window_width_; |  | ||||||
|         int window_h_last = main_window_height_; |  | ||||||
|  |  | ||||||
|         SDL_GetWindowSize(main_window_, &main_window_width_, |  | ||||||
|                           &main_window_height_); |  | ||||||
|  |  | ||||||
|         int w_change_ratio = abs(main_window_width_ - window_w_last) / 16; |  | ||||||
|         int h_change_ratio = abs(main_window_height_ - window_h_last) / 9; |  | ||||||
|  |  | ||||||
|         if (w_change_ratio > h_change_ratio) { |  | ||||||
|           main_window_height_ = main_window_width_ * 9 / 16; |  | ||||||
|         } else { |  | ||||||
|           main_window_width_ = main_window_height_ * 16 / 9; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SDL_SetWindowSize(main_window_, main_window_width_, |  | ||||||
|                           main_window_height_); |  | ||||||
|       } else if (event.type == SDL_WINDOWEVENT && |  | ||||||
|                  event.window.event == SDL_WINDOWEVENT_CLOSE && |  | ||||||
|                  event.window.windowID == SDL_GetWindowID(main_window_)) { |  | ||||||
|         exit_ = true; |  | ||||||
|       } else if (event.type == REFRESH_EVENT) { |  | ||||||
|         sdl_rect_.x = 0; |  | ||||||
|         sdl_rect_.y = 0; |  | ||||||
|         sdl_rect_.w = main_window_width_; |  | ||||||
|         sdl_rect_.h = main_window_height_; |  | ||||||
|  |  | ||||||
|         SDL_UpdateTexture(sdl_texture_, NULL, dst_buffer_, 1280); |  | ||||||
|       } else { |  | ||||||
|         if (connection_established_) { |  | ||||||
|           ProcessMouseKeyEven(event); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SDL_RenderClear(sdl_renderer_); |  | ||||||
|     SDL_RenderCopy(sdl_renderer_, sdl_texture_, NULL, &sdl_rect_); |  | ||||||
|  |  | ||||||
|     if (!connection_established_ || !received_frame_) { |  | ||||||
|       SDL_RenderClear(sdl_renderer_); |  | ||||||
|       SDL_SetRenderDrawColor( |  | ||||||
|           sdl_renderer_, (Uint8)(clear_color.x * 0), (Uint8)(clear_color.y * 0), |  | ||||||
|           (Uint8)(clear_color.z * 0), (Uint8)(clear_color.w * 0)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); |  | ||||||
|     SDL_RenderPresent(sdl_renderer_); |  | ||||||
|  |  | ||||||
|     frame_count_++; |  | ||||||
|     end_time_ = SDL_GetTicks(); |  | ||||||
|     elapsed_time_ = end_time_ - start_time_; |  | ||||||
|     if (elapsed_time_ >= 1000) { |  | ||||||
|       fps_ = frame_count_ / (elapsed_time_ / 1000); |  | ||||||
|       frame_count_ = 0; |  | ||||||
|       window_title = "Remote Desk Client FPS [" + std::to_string(fps_) + |  | ||||||
|                      "] status [" + connection_status_str_ + "|" + |  | ||||||
|                      connection_status_str_ + "]"; |  | ||||||
|       // For MacOS, UI frameworks can only be called from the main thread |  | ||||||
|       SDL_SetWindowTitle(main_window_, window_title.c_str()); |  | ||||||
|       start_time_ = end_time_; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Cleanup |  | ||||||
|   if (is_create_connection_) { |  | ||||||
|     LeaveConnection(peer_); |  | ||||||
|     is_client_mode_ = false; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (peer_) { |  | ||||||
|     DestroyPeer(peer_); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (peer_reserved_) { |  | ||||||
|     DestroyPeer(peer_reserved_); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   SDL_CloseAudioDevice(output_dev_); |  | ||||||
|   SDL_CloseAudioDevice(input_dev_); |  | ||||||
|  |  | ||||||
|   ImGui_ImplSDLRenderer2_Shutdown(); |  | ||||||
|   ImGui_ImplSDL2_Shutdown(); |  | ||||||
|   ImGui::DestroyContext(); |  | ||||||
|  |  | ||||||
|   SDL_DestroyRenderer(sdl_renderer_); |  | ||||||
|   SDL_DestroyWindow(main_window_); |  | ||||||
|  |  | ||||||
|   SDL_CloseAudio(); |  | ||||||
|   SDL_Quit(); |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
| @@ -1,190 +0,0 @@ | |||||||
| /* |  | ||||||
|  * @Author: DI JUNKUN |  | ||||||
|  * @Date: 2024-05-29 |  | ||||||
|  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #ifndef _MAIN_WINDOW_H_ |  | ||||||
| #define _MAIN_WINDOW_H_ |  | ||||||
|  |  | ||||||
| #include <SDL.h> |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <chrono> |  | ||||||
| #include <string> |  | ||||||
|  |  | ||||||
| #include "../../thirdparty/projectx/src/interface/x.h" |  | ||||||
| #include "config_center.h" |  | ||||||
| #include "device_controller_factory.h" |  | ||||||
| #include "imgui.h" |  | ||||||
| #include "imgui_impl_sdl2.h" |  | ||||||
| #include "imgui_impl_sdlrenderer2.h" |  | ||||||
| #include "screen_capturer_factory.h" |  | ||||||
|  |  | ||||||
| class MainWindow { |  | ||||||
|  public: |  | ||||||
|   MainWindow(); |  | ||||||
|   ~MainWindow(); |  | ||||||
|  |  | ||||||
|  public: |  | ||||||
|   int Run(); |  | ||||||
|  |  | ||||||
|  public: |  | ||||||
|   static void OnReceiveVideoBufferCb(const char *data, size_t size, |  | ||||||
|                                      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, void *user_data); |  | ||||||
|  |  | ||||||
|   static void OnConnectionStatusCb(ConnectionStatus status, void *user_data); |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   int ProcessMouseKeyEven(SDL_Event &ev); |  | ||||||
|   void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len); |  | ||||||
|   void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len); |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   int SaveSettingsIntoCacheFile(); |  | ||||||
|   int LoadSettingsIntoCacheFile(); |  | ||||||
|  |  | ||||||
|   int StartScreenCapture(); |  | ||||||
|   int StopScreenCapture(); |  | ||||||
|  |  | ||||||
|   int StartMouseControl(); |  | ||||||
|   int StopMouseControl(); |  | ||||||
|  |  | ||||||
|   int CreateConnectionPeer(); |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   typedef struct { |  | ||||||
|     char password[7]; |  | ||||||
|     int language; |  | ||||||
|     int video_quality; |  | ||||||
|     int video_encode_format; |  | ||||||
|     bool enable_hardware_video_codec; |  | ||||||
|   } CDCache; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   FILE *cd_cache_file_ = nullptr; |  | ||||||
|   CDCache cd_cache_; |  | ||||||
|  |  | ||||||
|   ConfigCenter config_center_; |  | ||||||
|  |  | ||||||
|   ConfigCenter::LANGUAGE localization_language_ = |  | ||||||
|       ConfigCenter::LANGUAGE::CHINESE; |  | ||||||
|  |  | ||||||
|   int localization_language_index_ = -1; |  | ||||||
|   int localization_language_index_last_ = -1; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   std::string window_title = "Remote Desk Client"; |  | ||||||
|   std::string mac_addr_str_ = ""; |  | ||||||
|   std::string connect_button_label_ = "Connect"; |  | ||||||
|   std::string fullscreen_button_label_ = "Fullscreen"; |  | ||||||
|   std::string mouse_control_button_label_ = "Mouse Control"; |  | ||||||
|   std::string settings_button_label_ = "Setting"; |  | ||||||
|   char input_password_tmp_[7] = ""; |  | ||||||
|   char input_password_[7] = ""; |  | ||||||
|   std::string local_id_ = ""; |  | ||||||
|   char remote_id_[20] = ""; |  | ||||||
|   char client_password_[20] = ""; |  | ||||||
|   bool is_client_mode_ = false; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   int screen_width_ = 1280; |  | ||||||
|   int screen_height_ = 720; |  | ||||||
|   int main_window_width_ = 1280; |  | ||||||
|   int main_window_height_ = 720; |  | ||||||
|   int main_window_width_before_fullscreen_ = 1280; |  | ||||||
|   int main_window_height_before_fullscreen_ = 720; |  | ||||||
|  |  | ||||||
|   int texture_width_ = 1280; |  | ||||||
|   int texture_height_ = 720; |  | ||||||
|  |  | ||||||
|   SDL_Texture *sdl_texture_ = nullptr; |  | ||||||
|   SDL_Renderer *sdl_renderer_ = nullptr; |  | ||||||
|   SDL_Rect sdl_rect_; |  | ||||||
|   SDL_Window *main_window_; |  | ||||||
|   uint32_t pixformat_ = 0; |  | ||||||
|  |  | ||||||
|   bool inited_ = false; |  | ||||||
|   bool exit_ = false; |  | ||||||
|   bool connection_established_ = false; |  | ||||||
|   bool subwindow_hovered_ = false; |  | ||||||
|   bool connect_button_pressed_ = false; |  | ||||||
|   bool fullscreen_button_pressed_ = false; |  | ||||||
|   bool mouse_control_button_pressed_ = false; |  | ||||||
|   bool settings_button_pressed_ = false; |  | ||||||
|   bool received_frame_ = false; |  | ||||||
|   bool is_create_connection_ = false; |  | ||||||
|   bool audio_buffer_fresh_ = false; |  | ||||||
|   bool rejoin_ = false; |  | ||||||
|   bool control_mouse_ = false; |  | ||||||
|  |  | ||||||
|   int fps_ = 0; |  | ||||||
|   uint32_t start_time_; |  | ||||||
|   uint32_t end_time_; |  | ||||||
|   uint32_t elapsed_time_; |  | ||||||
|   uint32_t frame_count_ = 0; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   ConnectionStatus connection_status_ = ConnectionStatus::Closed; |  | ||||||
|   SignalStatus signal_status_ = SignalStatus::SignalClosed; |  | ||||||
|   std::string signal_status_str_ = ""; |  | ||||||
|   std::string connection_status_str_ = ""; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   PeerPtr *peer_ = nullptr; |  | ||||||
|   PeerPtr *peer_reserved_ = nullptr; |  | ||||||
|   Params params_; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   SDL_AudioDeviceID input_dev_; |  | ||||||
|   SDL_AudioDeviceID output_dev_; |  | ||||||
|   unsigned char audio_buffer_[960]; |  | ||||||
|   int audio_len_ = 0; |  | ||||||
|   char *nv12_buffer_ = nullptr; |  | ||||||
|   unsigned char *dst_buffer_ = new unsigned char[1280 * 720 * 3]; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   ScreenCapturerFactory *screen_capturer_factory_ = nullptr; |  | ||||||
|   ScreenCapturer *screen_capturer_ = nullptr; |  | ||||||
|   DeviceControllerFactory *device_controller_factory_ = nullptr; |  | ||||||
|   MouseController *mouse_controller_ = nullptr; |  | ||||||
|  |  | ||||||
| #ifdef __linux__ |  | ||||||
|   std::chrono::_V2::system_clock::time_point last_frame_time_; |  | ||||||
| #else |  | ||||||
|   std::chrono::steady_clock::time_point last_frame_time_; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   int language_button_value_ = 0; |  | ||||||
|   int video_quality_button_value_ = 0; |  | ||||||
|   int video_encode_format_button_value_ = 0; |  | ||||||
|   bool enable_hardware_video_codec_ = 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; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   std::atomic<bool> start_screen_capture_{false}; |  | ||||||
|   std::atomic<bool> start_mouse_control_{false}; |  | ||||||
|   std::atomic<bool> screen_capture_is_started_{false}; |  | ||||||
|   std::atomic<bool> mouse_control_is_started_{false}; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   bool settings_window_pos_reset_ = true; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
| @@ -1,216 +0,0 @@ | |||||||
| #include "device_controller.h" |  | ||||||
| #include "localization.h" |  | ||||||
| #include "main_window.h" |  | ||||||
|  |  | ||||||
| // Refresh Event |  | ||||||
| #define REFRESH_EVENT (SDL_USEREVENT + 1) |  | ||||||
| #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 |  | ||||||
|  |  | ||||||
| #ifdef REMOTE_DESK_DEBUG |  | ||||||
| #else |  | ||||||
| #define MOUSE_CONTROL 1 |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| int MainWindow::ProcessMouseKeyEven(SDL_Event &ev) { |  | ||||||
|   if (!control_mouse_) { |  | ||||||
|     return 0; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   float ratio = (float)(1280.0 / main_window_width_); |  | ||||||
|  |  | ||||||
|   RemoteAction remote_action; |  | ||||||
|   remote_action.m.x = (size_t)(ev.button.x * ratio); |  | ||||||
|   remote_action.m.y = (size_t)(ev.button.y * ratio); |  | ||||||
|  |  | ||||||
|   if (SDL_KEYDOWN == ev.type)  // SDL_KEYUP |  | ||||||
|   { |  | ||||||
|     // printf("SDLK_DOWN: %d\n", SDL_KeyCode(ev.key.keysym.sym)); |  | ||||||
|     if (SDLK_DOWN == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_DOWN  \n"); |  | ||||||
|  |  | ||||||
|     } else if (SDLK_UP == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_UP  \n"); |  | ||||||
|  |  | ||||||
|     } else if (SDLK_LEFT == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_LEFT  \n"); |  | ||||||
|  |  | ||||||
|     } else if (SDLK_RIGHT == ev.key.keysym.sym) { |  | ||||||
|       // printf("SDLK_RIGHT  \n"); |  | ||||||
|     } |  | ||||||
|   } else if (SDL_MOUSEBUTTONDOWN == ev.type) { |  | ||||||
|     remote_action.type = ControlType::mouse; |  | ||||||
|     if (SDL_BUTTON_LEFT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::left_down; |  | ||||||
|     } else if (SDL_BUTTON_RIGHT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::right_down; |  | ||||||
|     } |  | ||||||
|     if (subwindow_hovered_) { |  | ||||||
|       remote_action.m.flag = MouseFlag::move; |  | ||||||
|     } |  | ||||||
|     SendData(peer_, DATA_TYPE::DATA, (const char *)&remote_action, |  | ||||||
|              sizeof(remote_action)); |  | ||||||
|   } else if (SDL_MOUSEBUTTONUP == ev.type) { |  | ||||||
|     remote_action.type = ControlType::mouse; |  | ||||||
|     if (SDL_BUTTON_LEFT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::left_up; |  | ||||||
|     } else if (SDL_BUTTON_RIGHT == ev.button.button) { |  | ||||||
|       remote_action.m.flag = MouseFlag::right_up; |  | ||||||
|     } |  | ||||||
|     if (subwindow_hovered_) { |  | ||||||
|       remote_action.m.flag = MouseFlag::move; |  | ||||||
|     } |  | ||||||
|     SendData(peer_, DATA_TYPE::DATA, (const char *)&remote_action, |  | ||||||
|              sizeof(remote_action)); |  | ||||||
|   } else if (SDL_MOUSEMOTION == ev.type) { |  | ||||||
|     remote_action.type = ControlType::mouse; |  | ||||||
|     remote_action.m.flag = MouseFlag::move; |  | ||||||
|     SendData(peer_, DATA_TYPE::DATA, (const char *)&remote_action, |  | ||||||
|              sizeof(remote_action)); |  | ||||||
|   } else if (SDL_QUIT == ev.type) { |  | ||||||
|     SDL_Event event; |  | ||||||
|     event.type = SDL_QUIT; |  | ||||||
|     SDL_PushEvent(&event); |  | ||||||
|     printf("SDL_QUIT\n"); |  | ||||||
|     return 0; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) { |  | ||||||
|   if (1) { |  | ||||||
|     if ("Connected" == connection_status_str_) { |  | ||||||
|       SendData(peer_, DATA_TYPE::AUDIO, (const char *)stream, len); |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     memcpy(audio_buffer_, stream, len); |  | ||||||
|     audio_len_ = len; |  | ||||||
|     SDL_Delay(10); |  | ||||||
|     audio_buffer_fresh_ = true; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) { |  | ||||||
|   if (!audio_buffer_fresh_) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   SDL_memset(stream, 0, len); |  | ||||||
|  |  | ||||||
|   if (audio_len_ == 0) { |  | ||||||
|     return; |  | ||||||
|   } else { |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   len = (len > audio_len_ ? audio_len_ : len); |  | ||||||
|   SDL_MixAudioFormat(stream, audio_buffer_, AUDIO_S16LSB, len, |  | ||||||
|                      SDL_MIX_MAXVOLUME); |  | ||||||
|   audio_buffer_fresh_ = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::OnReceiveVideoBufferCb(const char *data, size_t size, |  | ||||||
|                                         const char *user_id, |  | ||||||
|                                         size_t user_id_size, void *user_data) { |  | ||||||
|   MainWindow *main_window = (MainWindow *)user_data; |  | ||||||
|   if (main_window->connection_established_) { |  | ||||||
|     memcpy(main_window->dst_buffer_, data, size); |  | ||||||
|     SDL_Event event; |  | ||||||
|     event.type = REFRESH_EVENT; |  | ||||||
|     SDL_PushEvent(&event); |  | ||||||
|     main_window->received_frame_ = true; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::OnReceiveAudioBufferCb(const char *data, size_t size, |  | ||||||
|                                         const char *user_id, |  | ||||||
|                                         size_t user_id_size, void *user_data) { |  | ||||||
|   MainWindow *main_window = (MainWindow *)user_data; |  | ||||||
|   main_window->audio_buffer_fresh_ = true; |  | ||||||
|   SDL_QueueAudio(main_window->output_dev_, data, (uint32_t)size); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::OnReceiveDataBufferCb(const char *data, size_t size, |  | ||||||
|                                        const char *user_id, size_t user_id_size, |  | ||||||
|                                        void *user_data) { |  | ||||||
|   MainWindow *main_window = (MainWindow *)user_data; |  | ||||||
|   std::string user(user_id, user_id_size); |  | ||||||
|   RemoteAction remote_action; |  | ||||||
|   memcpy(&remote_action, data, sizeof(remote_action)); |  | ||||||
|  |  | ||||||
| #if MOUSE_CONTROL |  | ||||||
|   if (main_window->mouse_controller_) { |  | ||||||
|     main_window->mouse_controller_->SendCommand(remote_action); |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::OnSignalStatusCb(SignalStatus status, void *user_data) { |  | ||||||
|   MainWindow *main_window = (MainWindow *)user_data; |  | ||||||
|   main_window->signal_status_ = status; |  | ||||||
|   if (SignalStatus::SignalConnecting == status) { |  | ||||||
|     main_window->signal_status_str_ = "SignalConnecting"; |  | ||||||
|   } else if (SignalStatus::SignalConnected == status) { |  | ||||||
|     main_window->signal_status_str_ = "SignalConnected"; |  | ||||||
|   } else if (SignalStatus::SignalFailed == status) { |  | ||||||
|     main_window->signal_status_str_ = "SignalFailed"; |  | ||||||
|   } else if (SignalStatus::SignalClosed == status) { |  | ||||||
|     main_window->signal_status_str_ = "SignalClosed"; |  | ||||||
|   } else if (SignalStatus::SignalReconnecting == status) { |  | ||||||
|     main_window->signal_status_str_ = "SignalReconnecting"; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::OnConnectionStatusCb(ConnectionStatus status, |  | ||||||
|                                       void *user_data) { |  | ||||||
|   MainWindow *main_window = (MainWindow *)user_data; |  | ||||||
|   main_window->connection_status_ = status; |  | ||||||
|   if (ConnectionStatus::Connecting == status) { |  | ||||||
|     main_window->connection_status_str_ = "Connecting"; |  | ||||||
|   } else if (ConnectionStatus::Connected == status) { |  | ||||||
|     main_window->connection_status_str_ = "Connected"; |  | ||||||
|     main_window->connection_established_ = true; |  | ||||||
|     if (!main_window->is_client_mode_) { |  | ||||||
|       main_window->start_screen_capture_ = true; |  | ||||||
|       main_window->start_mouse_control_ = true; |  | ||||||
|     } |  | ||||||
|   } else if (ConnectionStatus::Disconnected == status) { |  | ||||||
|     main_window->connection_status_str_ = "Disconnected"; |  | ||||||
|   } else if (ConnectionStatus::Failed == status) { |  | ||||||
|     main_window->connection_status_str_ = "Failed"; |  | ||||||
|   } else if (ConnectionStatus::Closed == status) { |  | ||||||
|     main_window->connection_status_str_ = "Closed"; |  | ||||||
|     main_window->start_screen_capture_ = false; |  | ||||||
|     main_window->start_mouse_control_ = false; |  | ||||||
|     main_window->connection_established_ = false; |  | ||||||
|     main_window->control_mouse_ = false; |  | ||||||
|     if (main_window->dst_buffer_) { |  | ||||||
|       memset(main_window->dst_buffer_, 0, 1280 * 720 * 3); |  | ||||||
|       SDL_UpdateTexture(main_window->sdl_texture_, NULL, |  | ||||||
|                         main_window->dst_buffer_, 1280); |  | ||||||
|     } |  | ||||||
|   } else if (ConnectionStatus::IncorrectPassword == status) { |  | ||||||
|     main_window->connection_status_str_ = "Incorrect password"; |  | ||||||
|     if (main_window->connect_button_pressed_) { |  | ||||||
|       main_window->connect_button_pressed_ = false; |  | ||||||
|       main_window->connection_established_ = false; |  | ||||||
|       main_window->connect_button_label_ = |  | ||||||
|           main_window->connect_button_pressed_ |  | ||||||
|               ? localization::disconnect[main_window |  | ||||||
|                                              ->localization_language_index_] |  | ||||||
|               : localization::connect[main_window |  | ||||||
|                                           ->localization_language_index_]; |  | ||||||
|     } |  | ||||||
|   } else if (ConnectionStatus::NoSuchTransmissionId == status) { |  | ||||||
|     main_window->connection_status_str_ = "No such transmission id"; |  | ||||||
|     if (main_window->connect_button_pressed_) { |  | ||||||
|       main_window->connect_button_pressed_ = false; |  | ||||||
|       main_window->connection_established_ = false; |  | ||||||
|       main_window->connect_button_label_ = |  | ||||||
|           main_window->connect_button_pressed_ |  | ||||||
|               ? localization::disconnect[main_window |  | ||||||
|                                              ->localization_language_index_] |  | ||||||
|               : localization::connect[main_window |  | ||||||
|                                           ->localization_language_index_]; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										91
									
								
								src/path_manager/path_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/path_manager/path_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | #include "path_manager.h" | ||||||
|  |  | ||||||
|  | #include <cstdlib> | ||||||
|  |  | ||||||
|  | PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {} | ||||||
|  |  | ||||||
|  | std::filesystem::path PathManager::GetConfigPath() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_; | ||||||
|  | #elif __APPLE__ | ||||||
|  |   return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; | ||||||
|  | #else | ||||||
|  |   return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::filesystem::path PathManager::GetCachePath() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache"; | ||||||
|  | #elif __APPLE__ | ||||||
|  |   return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; | ||||||
|  | #else | ||||||
|  |   return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::filesystem::path PathManager::GetLogPath() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs"; | ||||||
|  | #elif __APPLE__ | ||||||
|  |   return GetHome() + "/Library/Logs/" + app_name_; | ||||||
|  | #else | ||||||
|  |   return GetCachePath() / app_name_ / "logs"; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::filesystem::path PathManager::GetCertPath() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   // %APPDATA%\AppName\Certs | ||||||
|  |   return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_ / "certs"; | ||||||
|  | #elif __APPLE__ | ||||||
|  |   // $HOME/Library/Application Support/AppName/certs | ||||||
|  |   return GetHome() + "/Library/Application Support/" + app_name_ + "/certs"; | ||||||
|  | #else | ||||||
|  |   // $XDG_CONFIG_HOME/AppName/certs | ||||||
|  |   return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / | ||||||
|  |          app_name_ / "certs"; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool PathManager::CreateDirectories(const std::filesystem::path& p) { | ||||||
|  |   std::error_code ec; | ||||||
|  |   bool created = std::filesystem::create_directories(p, ec); | ||||||
|  |   if (ec) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   return created || std::filesystem::exists(p); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #ifdef _WIN32 | ||||||
|  | std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) { | ||||||
|  |   PWSTR path = NULL; | ||||||
|  |   if (SUCCEEDED(SHGetKnownFolderPath(id, 0, NULL, &path))) { | ||||||
|  |     std::wstring wpath(path); | ||||||
|  |     CoTaskMemFree(path); | ||||||
|  |     return std::filesystem::path(wpath); | ||||||
|  |   } | ||||||
|  |   return {}; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | std::string PathManager::GetHome() { | ||||||
|  |   if (const char* home = getenv("HOME")) { | ||||||
|  |     return std::string(home); | ||||||
|  |   } | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   char path[MAX_PATH]; | ||||||
|  |   if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path))) | ||||||
|  |     return std::string(path); | ||||||
|  | #endif | ||||||
|  |   return {}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var, | ||||||
|  |                                                    const std::string& def) { | ||||||
|  |   if (const char* val = getenv(env_var)) { | ||||||
|  |     return std::filesystem::path(val); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return std::filesystem::path(def); | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								src/path_manager/path_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/path_manager/path_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2025-07-16 | ||||||
|  |  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _PATH_MANAGER_H_ | ||||||
|  | #define _PATH_MANAGER_H_ | ||||||
|  |  | ||||||
|  | #include <filesystem> | ||||||
|  | #include <string> | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <shlobj.h> | ||||||
|  | #include <windows.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | class PathManager { | ||||||
|  |  public: | ||||||
|  |   explicit PathManager(const std::string& app_name); | ||||||
|  |  | ||||||
|  |   std::filesystem::path GetConfigPath(); | ||||||
|  |  | ||||||
|  |   std::filesystem::path GetCachePath(); | ||||||
|  |  | ||||||
|  |   std::filesystem::path GetLogPath(); | ||||||
|  |  | ||||||
|  |   std::filesystem::path GetCertPath(); | ||||||
|  |  | ||||||
|  |   bool CreateDirectories(const std::filesystem::path& p); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  | #ifdef _WIN32 | ||||||
|  |   std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   std::string GetHome(); | ||||||
|  |   std::filesystem::path GetEnvOrDefault(const char* env_var, | ||||||
|  |                                         const std::string& def); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::string app_name_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -1,144 +1,174 @@ | |||||||
| #include "screen_capturer_x11.h" | #include "screen_capturer_x11.h" | ||||||
|  |  | ||||||
| #include <iostream> | #include <chrono> | ||||||
|  | #include <thread> | ||||||
|  |  | ||||||
| #include "log.h" | #include "libyuv.h" | ||||||
|  | #include "rd_log.h" | ||||||
| #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 |  | ||||||
| unsigned char nv12_buffer_[NV12_BUFFER_SIZE]; |  | ||||||
|  |  | ||||||
| ScreenCapturerX11::ScreenCapturerX11() {} | ScreenCapturerX11::ScreenCapturerX11() {} | ||||||
|  |  | ||||||
| ScreenCapturerX11::~ScreenCapturerX11() { | ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); } | ||||||
|   if (inited_ && capture_thread_->joinable()) { |  | ||||||
|     capture_thread_->join(); |  | ||||||
|     inited_ = false; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int ScreenCapturerX11::Init(const RECORD_DESKTOP_RECT &rect, const int fps, | int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) { | ||||||
|                             cb_desktop_data cb) { |   display_ = XOpenDisplay(nullptr); | ||||||
|   if (cb) { |   if (!display_) { | ||||||
|     _on_data = cb; |     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; |   fps_ = fps; | ||||||
|  |   callback_ = cb; | ||||||
|  |  | ||||||
|   av_log_set_level(AV_LOG_QUIET); |   y_plane_.resize(width_ * height_); | ||||||
|  |   uv_plane_.resize((width_ / 2) * (height_ / 2) * 2); | ||||||
|   pFormatCtx_ = avformat_alloc_context(); |  | ||||||
|  |  | ||||||
|   avdevice_register_all(); |  | ||||||
|  |  | ||||||
|   // grabbing frame rate |  | ||||||
|   av_dict_set(&options_, "framerate", "30", 0); |  | ||||||
|   // Make the grabbed area follow the mouse |  | ||||||
|   // av_dict_set(&options_, "follow_mouse", "centered", 0); |  | ||||||
|   // Video frame size. The default is to capture the full screen |  | ||||||
|   // av_dict_set(&options_, "video_size", "1280x720", 0); |  | ||||||
|   std::string capture_method = "x11grab"; |  | ||||||
|   ifmt_ = (AVInputFormat *)av_find_input_format(capture_method.c_str()); |  | ||||||
|   if (!ifmt_) { |  | ||||||
|     LOG_ERROR("Couldn't find_input_format [{}]", capture_method.c_str()); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Grab at position 10,20 |  | ||||||
|   if (avformat_open_input(&pFormatCtx_, ":0.0", ifmt_, &options_) != 0) { |  | ||||||
|     printf("Couldn't open input stream.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) { |  | ||||||
|     printf("Couldn't find stream information.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   videoindex_ = -1; |  | ||||||
|   for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++) |  | ||||||
|     if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |  | ||||||
|       videoindex_ = i_; |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   if (videoindex_ == -1) { |  | ||||||
|     printf("Didn't find a video stream.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar; |  | ||||||
|  |  | ||||||
|   pCodecCtx_ = avcodec_alloc_context3(NULL); |  | ||||||
|   avcodec_parameters_to_context(pCodecCtx_, pCodecParam_); |  | ||||||
|  |  | ||||||
|   pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id)); |  | ||||||
|   if (pCodec_ == NULL) { |  | ||||||
|     printf("Codec not found.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|   if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) { |  | ||||||
|     printf("Could not open codec.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width; |  | ||||||
|   const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height; |  | ||||||
|  |  | ||||||
|   pFrame_ = av_frame_alloc(); |  | ||||||
|   pFrameNv12_ = av_frame_alloc(); |  | ||||||
|  |  | ||||||
|   pFrame_->width = screen_w; |  | ||||||
|   pFrame_->height = screen_h; |  | ||||||
|   pFrameNv12_->width = 1280; |  | ||||||
|   pFrameNv12_->height = 720; |  | ||||||
|  |  | ||||||
|   packet_ = (AVPacket *)av_malloc(sizeof(AVPacket)); |  | ||||||
|  |  | ||||||
|   img_convert_ctx_ = sws_getContext( |  | ||||||
|       pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width, |  | ||||||
|       pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL); |  | ||||||
|  |  | ||||||
|   inited_ = true; |  | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerX11::Destroy() { 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() { | int ScreenCapturerX11::Start() { | ||||||
|   capture_thread_.reset(new std::thread([this]() { |   if (running_) return 0; | ||||||
|     while (1) { |   running_ = true; | ||||||
|       if (av_read_frame(pFormatCtx_, packet_) >= 0) { |   paused_ = false; | ||||||
|         if (packet_->stream_index == videoindex_) { |   thread_ = std::thread([this]() { | ||||||
|           avcodec_send_packet(pCodecCtx_, packet_); |     while (running_) { | ||||||
|           av_packet_unref(packet_); |       if (!paused_) OnFrame(); | ||||||
|           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; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerX11::Stop() { return 0; } | int ScreenCapturerX11::Stop() { | ||||||
|  |   if (!running_) return 0; | ||||||
|  |   running_ = false; | ||||||
|  |   if (thread_.joinable()) thread_.join(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| int ScreenCapturerX11::Pause() { return 0; } | int ScreenCapturerX11::Pause(int monitor_index) { | ||||||
|  |   paused_ = true; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| int ScreenCapturerX11::Resume() { return 0; } | int ScreenCapturerX11::Resume(int monitor_index) { | ||||||
|  |   paused_ = false; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| void ScreenCapturerX11::OnFrame() {} | int ScreenCapturerX11::SwitchTo(int monitor_index) { | ||||||
|  |   monitor_index_ = monitor_index; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| void ScreenCapturerX11::CleanUp() {} | std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() { | ||||||
|  |   return display_info_list_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ScreenCapturerX11::OnFrame() { | ||||||
|  |   if (!display_) { | ||||||
|  |     LOG_ERROR("Display is not initialized"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (monitor_index_ < 0 || monitor_index_ >= display_info_list_.size()) { | ||||||
|  |     LOG_ERROR("Invalid monitor index: {}", monitor_index_.load()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   left_ = display_info_list_[monitor_index_].left; | ||||||
|  |   top_ = display_info_list_[monitor_index_].top; | ||||||
|  |   width_ = display_info_list_[monitor_index_].width; | ||||||
|  |   height_ = display_info_list_[monitor_index_].height; | ||||||
|  |  | ||||||
|  |   XImage* image = XGetImage(display_, root_, left_, top_, width_, height_, | ||||||
|  |                             AllPlanes, ZPixmap); | ||||||
|  |   if (!image) return; | ||||||
|  |  | ||||||
|  |   bool needs_copy = image->bytes_per_line != width_ * 4; | ||||||
|  |   std::vector<uint8_t> argb_buf; | ||||||
|  |   uint8_t* src_argb = nullptr; | ||||||
|  |  | ||||||
|  |   if (needs_copy) { | ||||||
|  |     argb_buf.resize(width_ * height_ * 4); | ||||||
|  |     for (int y = 0; y < height_; ++y) { | ||||||
|  |       memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line, | ||||||
|  |              width_ * 4); | ||||||
|  |     } | ||||||
|  |     src_argb = argb_buf.data(); | ||||||
|  |   } else { | ||||||
|  |     src_argb = reinterpret_cast<uint8_t*>(image->data); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_, | ||||||
|  |                      uv_plane_.data(), width_, width_, height_); | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> nv12; | ||||||
|  |   nv12.reserve(y_plane_.size() + uv_plane_.size()); | ||||||
|  |   nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end()); | ||||||
|  |   nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end()); | ||||||
|  |  | ||||||
|  |   if (callback_) { | ||||||
|  |     callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_, | ||||||
|  |               display_info_list_[monitor_index_].name.c_str()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   XDestroyImage(image); | ||||||
|  | } | ||||||
| @@ -1,23 +1,24 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2025-05-07 | ||||||
|  |  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _SCREEN_CAPTURER_X11_H_ | #ifndef _SCREEN_CAPTURER_X11_H_ | ||||||
| #define _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 <atomic> | ||||||
|  | #include <cstring> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <string> | #include <iostream> | ||||||
| #include <thread> | #include <thread> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| #include "screen_capturer.h" | #include "screen_capturer.h" | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| #include <libavcodec/avcodec.h> |  | ||||||
| #include <libavdevice/avdevice.h> |  | ||||||
| #include <libavformat/avformat.h> |  | ||||||
| #include <libavutil/imgutils.h> |  | ||||||
| #include <libswscale/swscale.h> |  | ||||||
| #ifdef __cplusplus |  | ||||||
| }; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| class ScreenCapturerX11 : public ScreenCapturer { | class ScreenCapturerX11 : public ScreenCapturer { | ||||||
|  public: |  public: | ||||||
| @@ -25,59 +26,39 @@ class ScreenCapturerX11 : public ScreenCapturer { | |||||||
|   ~ScreenCapturerX11(); |   ~ScreenCapturerX11(); | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps, |   int Init(const int fps, cb_desktop_data cb) override; | ||||||
|                    cb_desktop_data cb); |   int Destroy() override; | ||||||
|  |   int Start() override; | ||||||
|  |   int Stop() override; | ||||||
|  |  | ||||||
|   virtual int Destroy(); |   int Pause(int monitor_index) override; | ||||||
|  |   int Resume(int monitor_index) override; | ||||||
|  |  | ||||||
|   virtual int Start(); |   int SwitchTo(int monitor_index) override; | ||||||
|  |  | ||||||
|   virtual int Stop(); |   std::vector<DisplayInfo> GetDisplayInfoList() override; | ||||||
|  |  | ||||||
|   int Pause(); |  | ||||||
|   int Resume(); |  | ||||||
|  |  | ||||||
|   void OnFrame(); |   void OnFrame(); | ||||||
|  |  | ||||||
|  protected: |  | ||||||
|   void CleanUp(); |  | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   std::atomic_bool _running; |   Display* display_ = nullptr; | ||||||
|   std::atomic_bool _paused; |   Window root_ = 0; | ||||||
|   std::atomic_bool _inited; |   XRRScreenResources* screen_res_ = nullptr; | ||||||
|  |   int left_ = 0; | ||||||
|  |   int top_ = 0; | ||||||
|  |   int width_ = 0; | ||||||
|  |   int height_ = 0; | ||||||
|  |   std::thread thread_; | ||||||
|  |   std::atomic<bool> running_{false}; | ||||||
|  |   std::atomic<bool> paused_{false}; | ||||||
|  |   std::atomic<int> monitor_index_{0}; | ||||||
|  |   int fps_ = 30; | ||||||
|  |   cb_desktop_data callback_; | ||||||
|  |   std::vector<DisplayInfo> display_info_list_; | ||||||
|  |  | ||||||
|   std::thread _thread; |   // 缓冲区 | ||||||
|  |   std::vector<uint8_t> y_plane_; | ||||||
|   std::string _device_name; |   std::vector<uint8_t> uv_plane_; | ||||||
|  |  | ||||||
|   RECORD_DESKTOP_RECT _rect; |  | ||||||
|  |  | ||||||
|   int _fps; |  | ||||||
|  |  | ||||||
|   cb_desktop_data _on_data; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   int i_ = 0; |  | ||||||
|   int videoindex_ = 0; |  | ||||||
|   int got_picture_ = 0; |  | ||||||
|   int fps_ = 0; |  | ||||||
|   bool inited_ = false; |  | ||||||
|  |  | ||||||
|   // ffmpeg |  | ||||||
|   AVFormatContext *pFormatCtx_ = nullptr; |  | ||||||
|   AVCodecContext *pCodecCtx_ = nullptr; |  | ||||||
|   AVCodec *pCodec_ = nullptr; |  | ||||||
|   AVCodecParameters *pCodecParam_ = nullptr; |  | ||||||
|   AVDictionary *options_ = nullptr; |  | ||||||
|   AVInputFormat *ifmt_ = nullptr; |  | ||||||
|   AVFrame *pFrame_ = nullptr; |  | ||||||
|   AVFrame *pFrameNv12_ = nullptr; |  | ||||||
|   AVPacket *packet_ = nullptr; |  | ||||||
|   struct SwsContext *img_convert_ctx_ = nullptr; |  | ||||||
|  |  | ||||||
|   // thread |  | ||||||
|   std::unique_ptr<std::thread> capture_thread_ = nullptr; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif | #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,145 +0,0 @@ | |||||||
| #include "screen_capturer_avf.h" |  | ||||||
|  |  | ||||||
| #include <iostream> |  | ||||||
|  |  | ||||||
| #include "log.h" |  | ||||||
|  |  | ||||||
| #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 |  | ||||||
| unsigned char nv12_buffer_[NV12_BUFFER_SIZE]; |  | ||||||
|  |  | ||||||
| ScreenCapturerAvf::ScreenCapturerAvf() {} |  | ||||||
|  |  | ||||||
| ScreenCapturerAvf::~ScreenCapturerAvf() { |  | ||||||
|   if (inited_ && capture_thread_->joinable()) { |  | ||||||
|     capture_thread_->join(); |  | ||||||
|     inited_ = false; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int ScreenCapturerAvf::Init(const RECORD_DESKTOP_RECT &rect, const int fps, |  | ||||||
|                             cb_desktop_data cb) { |  | ||||||
|   if (cb) { |  | ||||||
|     _on_data = cb; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   av_log_set_level(AV_LOG_QUIET); |  | ||||||
|  |  | ||||||
|   pFormatCtx_ = avformat_alloc_context(); |  | ||||||
|  |  | ||||||
|   avdevice_register_all(); |  | ||||||
|  |  | ||||||
|   // grabbing frame rate |  | ||||||
|   av_dict_set(&options_, "framerate", "60", 0); |  | ||||||
|   av_dict_set(&options_, "pixel_format", "nv12", 0); |  | ||||||
|   // show remote cursor |  | ||||||
|   av_dict_set(&options_, "capture_cursor", "1", 0); |  | ||||||
|   // Make the grabbed area follow the mouse |  | ||||||
|   // av_dict_set(&options_, "follow_mouse", "centered", 0); |  | ||||||
|   // Video frame size. The default is to capture the full screen |  | ||||||
|   // av_dict_set(&options_, "video_size", "1280x720", 0); |  | ||||||
|   ifmt_ = (AVInputFormat *)av_find_input_format("avfoundation"); |  | ||||||
|   if (!ifmt_) { |  | ||||||
|     printf("Couldn't find_input_format\n"); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Grab at position 10,20 |  | ||||||
|   if (avformat_open_input(&pFormatCtx_, "Capture screen 0", ifmt_, &options_) != |  | ||||||
|       0) { |  | ||||||
|     printf("Couldn't open input stream.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) { |  | ||||||
|     printf("Couldn't find stream information.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   videoindex_ = -1; |  | ||||||
|   for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++) |  | ||||||
|     if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |  | ||||||
|       videoindex_ = i_; |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   if (videoindex_ == -1) { |  | ||||||
|     printf("Didn't find a video stream.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar; |  | ||||||
|  |  | ||||||
|   pCodecCtx_ = avcodec_alloc_context3(NULL); |  | ||||||
|   avcodec_parameters_to_context(pCodecCtx_, pCodecParam_); |  | ||||||
|  |  | ||||||
|   pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id)); |  | ||||||
|   if (pCodec_ == NULL) { |  | ||||||
|     printf("Codec not found.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|   if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) { |  | ||||||
|     printf("Could not open codec.\n"); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width; |  | ||||||
|   const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height; |  | ||||||
|  |  | ||||||
|   pFrame_ = av_frame_alloc(); |  | ||||||
|   pFrameNv12_ = av_frame_alloc(); |  | ||||||
|  |  | ||||||
|   pFrame_->width = screen_w; |  | ||||||
|   pFrame_->height = screen_h; |  | ||||||
|   pFrameNv12_->width = 1280; |  | ||||||
|   pFrameNv12_->height = 720; |  | ||||||
|  |  | ||||||
|   packet_ = (AVPacket *)av_malloc(sizeof(AVPacket)); |  | ||||||
|  |  | ||||||
|   img_convert_ctx_ = sws_getContext( |  | ||||||
|       pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width, |  | ||||||
|       pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL); |  | ||||||
|  |  | ||||||
|   inited_ = true; |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int ScreenCapturerAvf::Destroy() { return 0; } |  | ||||||
|  |  | ||||||
| int ScreenCapturerAvf::Start() { |  | ||||||
|   capture_thread_.reset(new std::thread([this]() { |  | ||||||
|     while (1) { |  | ||||||
|       if (av_read_frame(pFormatCtx_, packet_) >= 0) { |  | ||||||
|         if (packet_->stream_index == videoindex_) { |  | ||||||
|           avcodec_send_packet(pCodecCtx_, packet_); |  | ||||||
|           av_packet_unref(packet_); |  | ||||||
|           got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_); |  | ||||||
|  |  | ||||||
|           if (!got_picture_) { |  | ||||||
|             av_image_fill_arrays(pFrameNv12_->data, pFrameNv12_->linesize, |  | ||||||
|                                  nv12_buffer_, AV_PIX_FMT_NV12, |  | ||||||
|                                  pFrameNv12_->width, pFrameNv12_->height, 1); |  | ||||||
|  |  | ||||||
|             sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0, |  | ||||||
|                       pFrame_->height, pFrameNv12_->data, |  | ||||||
|                       pFrameNv12_->linesize); |  | ||||||
|  |  | ||||||
|             _on_data((unsigned char *)nv12_buffer_, |  | ||||||
|                      pFrameNv12_->width * pFrameNv12_->height * 3 / 2, |  | ||||||
|                      pFrameNv12_->width, pFrameNv12_->height); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   })); |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int ScreenCapturerAvf::Stop() { return 0; } |  | ||||||
|  |  | ||||||
| int ScreenCapturerAvf::Pause() { return 0; } |  | ||||||
|  |  | ||||||
| int ScreenCapturerAvf::Resume() { return 0; } |  | ||||||
|  |  | ||||||
| void ScreenCapturerAvf::OnFrame() {} |  | ||||||
|  |  | ||||||
| void ScreenCapturerAvf::CleanUp() {} |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| /* |  | ||||||
|  * @Author: DI JUNKUN |  | ||||||
|  * @Date: 2023-12-01 |  | ||||||
|  * Copyright (c) 2023 by DI JUNKUN, All Rights Reserved. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| #ifndef _SCREEN_CAPTURER_AVF_H_ |  | ||||||
| #define _SCREEN_CAPTURER_AVF_H_ |  | ||||||
|  |  | ||||||
| #include <atomic> |  | ||||||
| #include <functional> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
|  |  | ||||||
| #include "screen_capturer.h" |  | ||||||
|  |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| #include <libavcodec/avcodec.h> |  | ||||||
| #include <libavdevice/avdevice.h> |  | ||||||
| #include <libavformat/avformat.h> |  | ||||||
| #include <libavutil/imgutils.h> |  | ||||||
| #include <libswscale/swscale.h> |  | ||||||
| #ifdef __cplusplus |  | ||||||
| }; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| class ScreenCapturerAvf : public ScreenCapturer { |  | ||||||
|  public: |  | ||||||
|   ScreenCapturerAvf(); |  | ||||||
|   ~ScreenCapturerAvf(); |  | ||||||
|  |  | ||||||
|  public: |  | ||||||
|   virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps, |  | ||||||
|                    cb_desktop_data cb); |  | ||||||
|   virtual int Destroy(); |  | ||||||
|  |  | ||||||
|   virtual int Start(); |  | ||||||
|  |  | ||||||
|   virtual int Stop(); |  | ||||||
|  |  | ||||||
|   int Pause(); |  | ||||||
|   int Resume(); |  | ||||||
|  |  | ||||||
|   void OnFrame(); |  | ||||||
|  |  | ||||||
|  protected: |  | ||||||
|   void CleanUp(); |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   std::atomic_bool _running; |  | ||||||
|   std::atomic_bool _paused; |  | ||||||
|   std::atomic_bool _inited; |  | ||||||
|  |  | ||||||
|   std::thread _thread; |  | ||||||
|  |  | ||||||
|   std::string _device_name; |  | ||||||
|  |  | ||||||
|   RECORD_DESKTOP_RECT _rect; |  | ||||||
|  |  | ||||||
|   int _fps; |  | ||||||
|  |  | ||||||
|   cb_desktop_data _on_data; |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   int i_ = 0; |  | ||||||
|   int videoindex_ = 0; |  | ||||||
|   int got_picture_ = 0; |  | ||||||
|   bool inited_ = false; |  | ||||||
|  |  | ||||||
|   // ffmpeg |  | ||||||
|   AVFormatContext *pFormatCtx_ = nullptr; |  | ||||||
|   AVCodecContext *pCodecCtx_ = nullptr; |  | ||||||
|   AVCodec *pCodec_ = nullptr; |  | ||||||
|   AVCodecParameters *pCodecParam_ = nullptr; |  | ||||||
|   AVDictionary *options_ = nullptr; |  | ||||||
|   AVInputFormat *ifmt_ = nullptr; |  | ||||||
|   AVFrame *pFrame_ = nullptr; |  | ||||||
|   AVFrame *pFrameNv12_ = nullptr; |  | ||||||
|   AVPacket *packet_ = nullptr; |  | ||||||
|   struct SwsContext *img_convert_ctx_ = nullptr; |  | ||||||
|  |  | ||||||
|   // thread |  | ||||||
|   std::unique_ptr<std::thread> capture_thread_ = nullptr; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
							
								
								
									
										73
									
								
								src/screen_capturer/macosx/screen_capturer_sck.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/screen_capturer/macosx/screen_capturer_sck.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | #include "screen_capturer_sck.h" | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | ScreenCapturerSck::ScreenCapturerSck() {} | ||||||
|  | ScreenCapturerSck::~ScreenCapturerSck() {} | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) { | ||||||
|  |   if (cb) { | ||||||
|  |     on_data_ = cb; | ||||||
|  |   } else { | ||||||
|  |     LOG_ERROR("cb is null"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   screen_capturer_sck_impl_ = CreateScreenCapturerSck(); | ||||||
|  |   screen_capturer_sck_impl_->Init(fps, on_data_); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::Destroy() { | ||||||
|  |   if (screen_capturer_sck_impl_) { | ||||||
|  |     screen_capturer_sck_impl_->Destroy(); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::Start() { | ||||||
|  |   screen_capturer_sck_impl_->Start(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::Stop() { | ||||||
|  |   if (screen_capturer_sck_impl_) { | ||||||
|  |     screen_capturer_sck_impl_->Stop(); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::Pause(int monitor_index) { | ||||||
|  |   if (screen_capturer_sck_impl_) { | ||||||
|  |     return screen_capturer_sck_impl_->Pause(monitor_index); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::Resume(int monitor_index) { | ||||||
|  |   if (screen_capturer_sck_impl_) { | ||||||
|  |     return screen_capturer_sck_impl_->Resume(monitor_index); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSck::SwitchTo(int monitor_index) { | ||||||
|  |   if (screen_capturer_sck_impl_) { | ||||||
|  |     return screen_capturer_sck_impl_->SwitchTo(monitor_index); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() { | ||||||
|  |   if (screen_capturer_sck_impl_) { | ||||||
|  |     return screen_capturer_sck_impl_->GetDisplayInfoList(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return std::vector<DisplayInfo>(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ScreenCapturerSck::OnFrame() {} | ||||||
|  |  | ||||||
|  | void ScreenCapturerSck::CleanUp() {} | ||||||
							
								
								
									
										59
									
								
								src/screen_capturer/macosx/screen_capturer_sck.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/screen_capturer/macosx/screen_capturer_sck.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-10-17 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _SCREEN_CAPTURER_SCK_H_ | ||||||
|  | #define _SCREEN_CAPTURER_SCK_H_ | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <functional> | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "screen_capturer.h" | ||||||
|  |  | ||||||
|  | class ScreenCapturerSck : public ScreenCapturer { | ||||||
|  |  public: | ||||||
|  |   ScreenCapturerSck(); | ||||||
|  |   ~ScreenCapturerSck(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   int Init(const int fps, cb_desktop_data cb) override; | ||||||
|  |   int Destroy() override; | ||||||
|  |   int Start() override; | ||||||
|  |   int Stop() override; | ||||||
|  |  | ||||||
|  |   int Pause(int monitor_index) override; | ||||||
|  |   int Resume(int monitor_index) override; | ||||||
|  |  | ||||||
|  |   int SwitchTo(int monitor_index) override; | ||||||
|  |  | ||||||
|  |   std::vector<DisplayInfo> GetDisplayInfoList() override; | ||||||
|  |  | ||||||
|  |   void OnFrame(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void CleanUp(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::unique_ptr<ScreenCapturer> CreateScreenCapturerSck(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int _fps; | ||||||
|  |   cb_desktop_data on_data_; | ||||||
|  |   unsigned char* nv12_frame_ = nullptr; | ||||||
|  |   bool inited_ = false; | ||||||
|  |  | ||||||
|  |   // thread | ||||||
|  |   std::thread capture_thread_; | ||||||
|  |   std::atomic_bool running_; | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::unique_ptr<ScreenCapturer> screen_capturer_sck_impl_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										488
									
								
								src/screen_capturer/macosx/screen_capturer_sck_impl.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								src/screen_capturer/macosx/screen_capturer_sck_impl.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,488 @@ | |||||||
|  | /* | ||||||
|  |  *  Copyright (c) 2024 The WebRTC project authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  *  Use of this source code is governed by a BSD-style license | ||||||
|  |  *  that can be found in the LICENSE file in the root of the source | ||||||
|  |  *  tree. An additional intellectual property rights grant can be found | ||||||
|  |  *  in the file PATENTS.  All contributing project authors may | ||||||
|  |  *  be found in the AUTHORS file in the root of the source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include "screen_capturer_sck.h" | ||||||
|  |  | ||||||
|  | #include <ApplicationServices/ApplicationServices.h> | ||||||
|  | #include <CoreGraphics/CoreGraphics.h> | ||||||
|  | #include <IOKit/IOKitLib.h> | ||||||
|  | #include <IOKit/graphics/IOGraphicsLib.h> | ||||||
|  | #include <IOSurface/IOSurface.h> | ||||||
|  | #include <ScreenCaptureKit/ScreenCaptureKit.h> | ||||||
|  | #include <atomic> | ||||||
|  | #include <mutex> | ||||||
|  | #include <vector> | ||||||
|  | #include "display_info.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | static const int kFullDesktopScreenId = -1; | ||||||
|  | class ScreenCapturerSckImpl; | ||||||
|  |  | ||||||
|  | // The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture | ||||||
|  | // was reported to be broken before macOS 13 - see http://crbug.com/40234870. | ||||||
|  | // Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were | ||||||
|  | // introduced in macOS 14. | ||||||
|  | API_AVAILABLE(macos(14.0)) | ||||||
|  | @interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput> | ||||||
|  |  | ||||||
|  | - (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer; | ||||||
|  |  | ||||||
|  | - (void)onShareableContentCreated:(SCShareableContent *)content; | ||||||
|  |  | ||||||
|  | // Called just before the capturer is destroyed. This avoids a dangling pointer, | ||||||
|  | // and prevents any new calls into a deleted capturer. If any method-call on the | ||||||
|  | // capturer is currently running on a different thread, this blocks until it | ||||||
|  | // completes. | ||||||
|  | - (void)releaseCapturer; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer { | ||||||
|  |  public: | ||||||
|  |   explicit ScreenCapturerSckImpl(); | ||||||
|  |  | ||||||
|  |   ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete; | ||||||
|  |   ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete; | ||||||
|  |   ~ScreenCapturerSckImpl(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   int Init(const int fps, cb_desktop_data cb) override; | ||||||
|  |  | ||||||
|  |   int Start() override; | ||||||
|  |  | ||||||
|  |   int SwitchTo(int monitor_index) override; | ||||||
|  |  | ||||||
|  |   int Destroy() override; | ||||||
|  |  | ||||||
|  |   int Stop() override; | ||||||
|  |  | ||||||
|  |   int Pause(int monitor_index) override { return 0; } | ||||||
|  |  | ||||||
|  |   int Resume(int monitor_index) override { return 0; } | ||||||
|  |  | ||||||
|  |   std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::vector<DisplayInfo> display_info_list_; | ||||||
|  |   std::map<int, CGDirectDisplayID> display_id_map_; | ||||||
|  |   std::map<CGDirectDisplayID, int> display_id_map_reverse_; | ||||||
|  |   std::map<CGDirectDisplayID, std::string> display_id_name_map_; | ||||||
|  |   unsigned char *nv12_frame_ = nullptr; | ||||||
|  |   int width_ = 0; | ||||||
|  |   int height_ = 0; | ||||||
|  |   int fps_ = 30; | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   // Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be | ||||||
|  |   // nil if an error occurred. May run on an arbitrary thread. | ||||||
|  |   void OnShareableContentCreated(SCShareableContent *content); | ||||||
|  |   // Called by SckHelper to notify of a newly captured frame. May run on an arbitrary thread. | ||||||
|  |   // void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment); | ||||||
|  |   void OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, CFDictionaryRef attachment); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   // Called when starting the capturer or the configuration has changed (either from a | ||||||
|  |   // SwitchTo() call, or the screen-resolution has changed). This tells SCK to fetch new | ||||||
|  |   // shareable content, and the completion-handler will either start a new stream, or reconfigure | ||||||
|  |   // the existing stream. Runs on the caller's thread. | ||||||
|  |   void StartOrReconfigureCapturer(); | ||||||
|  |   // Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++ | ||||||
|  |   // object. The helper may outlive this C++ instance, if a completion-handler is passed to | ||||||
|  |   // ScreenCaptureKit APIs and the C++ object is deleted before the handler executes. | ||||||
|  |   SckHelper *__strong helper_; | ||||||
|  |   // Callback for returning captured frames, or errors, to the caller. Only used on the caller's | ||||||
|  |   // thread. | ||||||
|  |   cb_desktop_data _on_data = nullptr; | ||||||
|  |   // Signals that a permanent error occurred. This may be set on any thread, and is read by | ||||||
|  |   // CaptureFrame() which runs on the caller's thread. | ||||||
|  |   std::atomic<bool> permanent_error_ = false; | ||||||
|  |   // Guards some variables that may be accessed on different threads. | ||||||
|  |   std::mutex lock_; | ||||||
|  |   // Provides captured desktop frames. | ||||||
|  |   SCStream *__strong stream_; | ||||||
|  |   // Currently selected display, or 0 if the full desktop is selected. This capturer does not | ||||||
|  |   // support full-desktop capture, and will fall back to the first display. | ||||||
|  |   CGDirectDisplayID current_display_ = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | std::string GetDisplayName(CGDirectDisplayID display_id) { | ||||||
|  |   io_iterator_t iter; | ||||||
|  |   io_service_t serv = 0, matched_serv = 0; | ||||||
|  |  | ||||||
|  |   CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect"); | ||||||
|  |   if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) { | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   while ((serv = IOIteratorNext(iter)) != 0) { | ||||||
|  |     CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName); | ||||||
|  |     if (info) { | ||||||
|  |       CFNumberRef vendorID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayVendorID)); | ||||||
|  |       CFNumberRef productID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayProductID)); | ||||||
|  |       uint32_t vID = 0, pID = 0; | ||||||
|  |       if (vendorID && productID && CFNumberGetValue(vendorID, kCFNumberIntType, &vID) && | ||||||
|  |           CFNumberGetValue(productID, kCFNumberIntType, &pID) && | ||||||
|  |           vID == CGDisplayVendorNumber(display_id) && pID == CGDisplayModelNumber(display_id)) { | ||||||
|  |         matched_serv = serv; | ||||||
|  |         CFRelease(info); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       CFRelease(info); | ||||||
|  |     } | ||||||
|  |     IOObjectRelease(serv); | ||||||
|  |   } | ||||||
|  |   IOObjectRelease(iter); | ||||||
|  |  | ||||||
|  |   if (!matched_serv) return ""; | ||||||
|  |  | ||||||
|  |   CFDictionaryRef display_info = | ||||||
|  |       IODisplayCreateInfoDictionary(matched_serv, kIODisplayOnlyPreferredName); | ||||||
|  |   IOObjectRelease(matched_serv); | ||||||
|  |   if (!display_info) return ""; | ||||||
|  |  | ||||||
|  |   CFDictionaryRef product_name_dict = | ||||||
|  |       (CFDictionaryRef)CFDictionaryGetValue(display_info, CFSTR(kDisplayProductName)); | ||||||
|  |   std::string result; | ||||||
|  |   if (product_name_dict) { | ||||||
|  |     CFIndex count = CFDictionaryGetCount(product_name_dict); | ||||||
|  |     if (count > 0) { | ||||||
|  |       std::vector<const void *> keys(count); | ||||||
|  |       std::vector<const void *> values(count); | ||||||
|  |       CFDictionaryGetKeysAndValues(product_name_dict, keys.data(), values.data()); | ||||||
|  |       CFStringRef name_ref = (CFStringRef)values[0]; | ||||||
|  |       if (name_ref) { | ||||||
|  |         CFIndex maxSize = | ||||||
|  |             CFStringGetMaximumSizeForEncoding(CFStringGetLength(name_ref), kCFStringEncodingUTF8) + | ||||||
|  |             1; | ||||||
|  |         std::vector<char> buffer(maxSize); | ||||||
|  |         if (CFStringGetCString(name_ref, buffer.data(), buffer.size(), kCFStringEncodingUTF8)) { | ||||||
|  |           result = buffer.data(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   CFRelease(display_info); | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ScreenCapturerSckImpl::ScreenCapturerSckImpl() { | ||||||
|  |   helper_ = [[SckHelper alloc] initWithCapturer:this]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ScreenCapturerSckImpl::~ScreenCapturerSckImpl() { | ||||||
|  |   display_info_list_.clear(); | ||||||
|  |   display_id_map_.clear(); | ||||||
|  |   display_id_map_reverse_.clear(); | ||||||
|  |   display_id_name_map_.clear(); | ||||||
|  |  | ||||||
|  |   if (nv12_frame_) { | ||||||
|  |     delete[] nv12_frame_; | ||||||
|  |     nv12_frame_ = nullptr; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   [stream_ stopCaptureWithCompletionHandler:nil]; | ||||||
|  |   [helper_ releaseCapturer]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) { | ||||||
|  |   _on_data = cb; | ||||||
|  |  | ||||||
|  |   dispatch_semaphore_t sema = dispatch_semaphore_create(0); | ||||||
|  |   __block SCShareableContent *content = nil; | ||||||
|  |  | ||||||
|  |   [SCShareableContent | ||||||
|  |       getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) { | ||||||
|  |         if (error) { | ||||||
|  |           NSLog(@"Failed to get shareable content: %@", error); | ||||||
|  |         } else { | ||||||
|  |           content = result; | ||||||
|  |         } | ||||||
|  |         dispatch_semaphore_signal(sema); | ||||||
|  |       }]; | ||||||
|  |   dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | ||||||
|  |  | ||||||
|  |   if (!content || content.displays.count == 0) { | ||||||
|  |     LOG_ERROR("Failed to get display info"); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   CGDirectDisplayID displays[10]; | ||||||
|  |   uint32_t count; | ||||||
|  |   CGGetActiveDisplayList(10, displays, &count); | ||||||
|  |  | ||||||
|  |   int unnamed_count = 1; | ||||||
|  |   for (SCDisplay *display in content.displays) { | ||||||
|  |     CGDirectDisplayID display_id = display.displayID; | ||||||
|  |     CGRect bounds = CGDisplayBounds(display_id); | ||||||
|  |     bool is_primary = CGDisplayIsMain(display_id); | ||||||
|  |  | ||||||
|  |     std::string name; | ||||||
|  |     name = GetDisplayName(display_id); | ||||||
|  |  | ||||||
|  |     if (name.empty()) { | ||||||
|  |       name = "Display " + std::to_string(unnamed_count++); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary, | ||||||
|  |                      static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y), | ||||||
|  |                      static_cast<int>(bounds.origin.x + bounds.size.width), | ||||||
|  |                      static_cast<int>(bounds.origin.y + bounds.size.height)); | ||||||
|  |  | ||||||
|  |     display_info_list_.push_back(info); | ||||||
|  |     display_id_map_[display_info_list_.size() - 1] = display_id; | ||||||
|  |     display_id_map_reverse_[display_id] = display_info_list_.size() - 1; | ||||||
|  |     display_id_name_map_[display_id] = name; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSckImpl::Start() { | ||||||
|  |   StartOrReconfigureCapturer(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSckImpl::SwitchTo(int monitor_index) { | ||||||
|  |   if (stream_) { | ||||||
|  |     [stream_ stopCaptureWithCompletionHandler:^(NSError *error) { | ||||||
|  |       std::lock_guard<std::mutex> lock(lock_); | ||||||
|  |       stream_ = nil; | ||||||
|  |       current_display_ = display_id_map_[monitor_index]; | ||||||
|  |       StartOrReconfigureCapturer(); | ||||||
|  |     }]; | ||||||
|  |   } else { | ||||||
|  |     current_display_ = display_id_map_[monitor_index]; | ||||||
|  |     StartOrReconfigureCapturer(); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSckImpl::Destroy() { | ||||||
|  |   std::lock_guard<std::mutex> lock(lock_); | ||||||
|  |   if (stream_) { | ||||||
|  |     LOG_INFO("Destroying stream"); | ||||||
|  |     [stream_ stopCaptureWithCompletionHandler:nil]; | ||||||
|  |     stream_ = nil; | ||||||
|  |   } | ||||||
|  |   current_display_ = 0; | ||||||
|  |   permanent_error_ = false; | ||||||
|  |   _on_data = nullptr; | ||||||
|  |   [helper_ releaseCapturer]; | ||||||
|  |   helper_ = nil; | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int ScreenCapturerSckImpl::Stop() { | ||||||
|  |   std::lock_guard<std::mutex> lock(lock_); | ||||||
|  |   if (stream_) { | ||||||
|  |     LOG_INFO("Stopping stream"); | ||||||
|  |     [stream_ stopCaptureWithCompletionHandler:nil]; | ||||||
|  |     stream_ = nil; | ||||||
|  |   } | ||||||
|  |   current_display_ = 0; | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *content) { | ||||||
|  |   if (!content) { | ||||||
|  |     LOG_ERROR("getShareableContent failed"); | ||||||
|  |     permanent_error_ = true; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!content.displays.count) { | ||||||
|  |     LOG_ERROR("getShareableContent returned no displays"); | ||||||
|  |     permanent_error_ = true; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SCDisplay *captured_display; | ||||||
|  |   { | ||||||
|  |     std::lock_guard<std::mutex> lock(lock_); | ||||||
|  |     for (SCDisplay *display in content.displays) { | ||||||
|  |       if (current_display_ == display.displayID) { | ||||||
|  |         LOG_WARN("current display: {}, name: {}", current_display_, | ||||||
|  |                  display_id_name_map_[current_display_]); | ||||||
|  |         captured_display = display; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (!captured_display) { | ||||||
|  |       captured_display = content.displays.firstObject; | ||||||
|  |       current_display_ = captured_display.displayID; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display | ||||||
|  |                                                     excludingWindows:@[]]; | ||||||
|  |   SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init]; | ||||||
|  |   config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; | ||||||
|  |   config.showsCursor = false; | ||||||
|  |   config.width = filter.contentRect.size.width * filter.pointPixelScale; | ||||||
|  |   config.height = filter.contentRect.size.height * filter.pointPixelScale; | ||||||
|  |   config.captureResolution = SCCaptureResolutionAutomatic; | ||||||
|  |   config.minimumFrameInterval = CMTimeMake(1, fps_); | ||||||
|  |  | ||||||
|  |   std::lock_guard<std::mutex> lock(lock_); | ||||||
|  |  | ||||||
|  |   if (stream_) { | ||||||
|  |     LOG_INFO("Updating stream configuration"); | ||||||
|  |     [stream_ updateContentFilter:filter completionHandler:nil]; | ||||||
|  |     [stream_ updateConfiguration:config completionHandler:nil]; | ||||||
|  |   } else { | ||||||
|  |     stream_ = [[SCStream alloc] initWithFilter:filter configuration:config delegate:helper_]; | ||||||
|  |  | ||||||
|  |     // TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for | ||||||
|  |     // best performance. | ||||||
|  |     NSError *add_stream_output_error; | ||||||
|  |     dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL); | ||||||
|  |     bool add_stream_output_result = [stream_ addStreamOutput:helper_ | ||||||
|  |                                                         type:SCStreamOutputTypeScreen | ||||||
|  |                                           sampleHandlerQueue:queue | ||||||
|  |                                                        error:&add_stream_output_error]; | ||||||
|  |  | ||||||
|  |     if (!add_stream_output_result) { | ||||||
|  |       stream_ = nil; | ||||||
|  |       LOG_ERROR("addStreamOutput failed"); | ||||||
|  |       permanent_error_ = true; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auto handler = ^(NSError *error) { | ||||||
|  |       if (error) { | ||||||
|  |         // It should be safe to access `this` here, because the C++ destructor | ||||||
|  |         // calls stopCaptureWithCompletionHandler on the stream, which cancels | ||||||
|  |         // this handler. | ||||||
|  |         permanent_error_ = true; | ||||||
|  |         LOG_ERROR("startCaptureWithCompletionHandler failed"); | ||||||
|  |       } else { | ||||||
|  |         LOG_INFO("Capture started"); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     [stream_ startCaptureWithCompletionHandler:handler]; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, | ||||||
|  |                                                CFDictionaryRef attachment) { | ||||||
|  |   size_t width = CVPixelBufferGetWidth(pixelBuffer); | ||||||
|  |   size_t height = CVPixelBufferGetHeight(pixelBuffer); | ||||||
|  |  | ||||||
|  |   CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); | ||||||
|  |   if (status != kCVReturnSuccess) { | ||||||
|  |     LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   size_t required_size = width * height * 3 / 2; | ||||||
|  |   if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) { | ||||||
|  |     delete[] nv12_frame_; | ||||||
|  |     nv12_frame_ = new unsigned char[required_size]; | ||||||
|  |     width_ = width; | ||||||
|  |     height_ = height; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); | ||||||
|  |   size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); | ||||||
|  |  | ||||||
|  |   void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); | ||||||
|  |   size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); | ||||||
|  |  | ||||||
|  |   unsigned char *dst_y = nv12_frame_; | ||||||
|  |   for (size_t row = 0; row < height; ++row) { | ||||||
|  |     memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   unsigned char *dst_uv = nv12_frame_ + width * height; | ||||||
|  |   for (size_t row = 0; row < height / 2; ++row) { | ||||||
|  |     memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   _on_data(nv12_frame_, width * height * 3 / 2, width, height, | ||||||
|  |            display_id_name_map_[current_display_].c_str()); | ||||||
|  |  | ||||||
|  |   CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ScreenCapturerSckImpl::StartOrReconfigureCapturer() { | ||||||
|  |   // The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_` | ||||||
|  |   // inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is | ||||||
|  |   // deleted before the block is executed. | ||||||
|  |   SckHelper *local_helper = helper_; | ||||||
|  |   auto handler = ^(SCShareableContent *content, NSError *error) { | ||||||
|  |     [local_helper onShareableContentCreated:content]; | ||||||
|  |   }; | ||||||
|  |   [SCShareableContent getShareableContentWithCompletionHandler:handler]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() { | ||||||
|  |   return std::make_unique<ScreenCapturerSckImpl>(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @implementation SckHelper { | ||||||
|  |   // This lock is to prevent the capturer being destroyed while an instance | ||||||
|  |   // method is still running on another thread. | ||||||
|  |   std::mutex _capturer_lock; | ||||||
|  |   ScreenCapturerSckImpl *_capturer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer { | ||||||
|  |   self = [super init]; | ||||||
|  |   if (self) { | ||||||
|  |     _capturer = capturer; | ||||||
|  |   } | ||||||
|  |   return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)onShareableContentCreated:(SCShareableContent *)content { | ||||||
|  |   std::lock_guard<std::mutex> lock(_capturer_lock); | ||||||
|  |   if (_capturer) { | ||||||
|  |     _capturer->OnShareableContentCreated(content); | ||||||
|  |   } else { | ||||||
|  |     LOG_ERROR("Invalid capturer"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)stream:(SCStream *)stream | ||||||
|  |     didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer | ||||||
|  |                    ofType:(SCStreamOutputType)type { | ||||||
|  |   CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); | ||||||
|  |   if (!pixelBuffer) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   CFRetain(pixelBuffer); | ||||||
|  |  | ||||||
|  |   CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false); | ||||||
|  |   if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) { | ||||||
|  |     LOG_ERROR("Discarding frame with no attachments"); | ||||||
|  |     CFRelease(pixelBuffer); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   CFDictionaryRef attachment = | ||||||
|  |       static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0)); | ||||||
|  |  | ||||||
|  |   std::lock_guard<std::mutex> lock(_capturer_lock); | ||||||
|  |   if (_capturer) { | ||||||
|  |     _capturer->OnNewCVPixelBuffer(pixelBuffer, attachment); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   CFRelease(pixelBuffer); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)releaseCapturer { | ||||||
|  |   std::lock_guard<std::mutex> lock(_capturer_lock); | ||||||
|  |   _capturer = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @end | ||||||
| @@ -9,27 +9,26 @@ | |||||||
|  |  | ||||||
| #include <functional> | #include <functional> | ||||||
|  |  | ||||||
|  | #include "display_info.h" | ||||||
|  |  | ||||||
| class ScreenCapturer { | class ScreenCapturer { | ||||||
|  public: |  public: | ||||||
|   typedef struct { |   typedef std::function<void(unsigned char*, int, int, int, const char*)> | ||||||
|     int left; |       cb_desktop_data; | ||||||
|     int top; |  | ||||||
|     int right; |  | ||||||
|     int bottom; |  | ||||||
|   } RECORD_DESKTOP_RECT; |  | ||||||
|   typedef std::function<void(unsigned char *, int, int, int)> cb_desktop_data; |  | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual ~ScreenCapturer() {} |   virtual ~ScreenCapturer() {} | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps, |   virtual int Init(const int fps, cb_desktop_data cb) = 0; | ||||||
|                    cb_desktop_data cb) = 0; |  | ||||||
|   virtual int Destroy() = 0; |   virtual int Destroy() = 0; | ||||||
|  |  | ||||||
|   virtual int Start() = 0; |   virtual int Start() = 0; | ||||||
|  |  | ||||||
|   virtual int Stop() = 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 | #endif | ||||||
| @@ -8,12 +8,12 @@ | |||||||
| #define _SCREEN_CAPTURER_FACTORY_H_ | #define _SCREEN_CAPTURER_FACTORY_H_ | ||||||
|  |  | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|  |  | ||||||
| #include "screen_capturer_wgc.h" | #include "screen_capturer_wgc.h" | ||||||
| #elif __linux__ | #elif __linux__ | ||||||
| #include "screen_capturer_x11.h" | #include "screen_capturer_x11.h" | ||||||
| #elif __APPLE__ | #elif __APPLE__ | ||||||
| #include "screen_capturer_avf.h" | // #include "screen_capturer_avf.h" | ||||||
|  | #include "screen_capturer_sck.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| class ScreenCapturerFactory { | class ScreenCapturerFactory { | ||||||
| @@ -27,7 +27,8 @@ class ScreenCapturerFactory { | |||||||
| #elif __linux__ | #elif __linux__ | ||||||
|     return new ScreenCapturerX11(); |     return new ScreenCapturerX11(); | ||||||
| #elif __APPLE__ | #elif __APPLE__ | ||||||
|     return new ScreenCapturerAvf(); |     // return new ScreenCapturerAvf(); | ||||||
|  |     return new ScreenCapturerSck(); | ||||||
| #else | #else | ||||||
|     return nullptr; |     return nullptr; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -8,19 +8,44 @@ | |||||||
| #include <iostream> | #include <iostream> | ||||||
|  |  | ||||||
| #include "libyuv.h" | #include "libyuv.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
| BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc, | static std::vector<DisplayInfo> gs_display_list; | ||||||
|                             LPARAM data) { |  | ||||||
|   MONITORINFOEX info_ex; |  | ||||||
|   info_ex.cbSize = sizeof(MONITORINFOEX); |  | ||||||
|  |  | ||||||
|   GetMonitorInfo(hmonitor, &info_ex); | std::string WideToUtf8(const wchar_t *wideStr) { | ||||||
|  |   int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0, | ||||||
|  |                                         nullptr, nullptr); | ||||||
|  |   std::string result(size_needed, 0); | ||||||
|  |   WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr, | ||||||
|  |                       nullptr); | ||||||
|  |   result.pop_back(); | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|   if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true; | BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc, | ||||||
|  |                             [[maybe_unused]] LPRECT lprc, LPARAM data) { | ||||||
|  |   MONITORINFOEX monitor_info_; | ||||||
|  |   monitor_info_.cbSize = sizeof(MONITORINFOEX); | ||||||
|  |  | ||||||
|   if (info_ex.dwFlags & MONITORINFOF_PRIMARY) { |   if (GetMonitorInfo(hmonitor, &monitor_info_)) { | ||||||
|  |     if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) { | ||||||
|  |       gs_display_list.insert( | ||||||
|  |           gs_display_list.begin(), | ||||||
|  |           {(void *)hmonitor, WideToUtf8(monitor_info_.szDevice), | ||||||
|  |            (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, | ||||||
|  |            monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, | ||||||
|  |            monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom}); | ||||||
|       *(HMONITOR *)data = hmonitor; |       *(HMONITOR *)data = hmonitor; | ||||||
|  |     } else { | ||||||
|  |       gs_display_list.push_back(DisplayInfo( | ||||||
|  |           (void *)hmonitor, WideToUtf8(monitor_info_.szDevice), | ||||||
|  |           (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, | ||||||
|  |           monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, | ||||||
|  |           monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom)); | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true; | ||||||
|  |  | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| @@ -28,12 +53,13 @@ BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc, | |||||||
| HMONITOR GetPrimaryMonitor() { | HMONITOR GetPrimaryMonitor() { | ||||||
|   HMONITOR hmonitor = nullptr; |   HMONITOR hmonitor = nullptr; | ||||||
|  |  | ||||||
|  |   gs_display_list.clear(); | ||||||
|   ::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor); |   ::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor); | ||||||
|  |  | ||||||
|   return hmonitor; |   return hmonitor; | ||||||
| } | } | ||||||
|  |  | ||||||
| ScreenCapturerWgc::ScreenCapturerWgc() {} | ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {} | ||||||
|  |  | ||||||
| ScreenCapturerWgc::~ScreenCapturerWgc() { | ScreenCapturerWgc::~ScreenCapturerWgc() { | ||||||
|   Stop(); |   Stop(); | ||||||
| @@ -62,146 +88,185 @@ bool ScreenCapturerWgc::IsWgcSupported() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps, | int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) { | ||||||
|                             cb_desktop_data cb) { |  | ||||||
|   int error = 0; |   int error = 0; | ||||||
|   if (_inited == true) return error; |   if (inited_ == true) return error; | ||||||
|  |  | ||||||
|   int r = rect.right; |   // nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2]; | ||||||
|   int b = rect.bottom; |   // nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2]; | ||||||
|  |  | ||||||
|   nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2]; |   fps_ = fps; | ||||||
|   nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2]; |  | ||||||
|  |  | ||||||
|   _fps = fps; |   on_data_ = cb; | ||||||
|  |  | ||||||
|   _on_data = cb; |  | ||||||
|  |  | ||||||
|   do { |  | ||||||
|   if (!IsWgcSupported()) { |   if (!IsWgcSupported()) { | ||||||
|       std::cout << "AE_UNSUPPORT" << std::endl; |     LOG_ERROR("WGC not supported"); | ||||||
|     error = 2; |     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; |     return error; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   monitor_ = GetPrimaryMonitor(); | ||||||
|  |  | ||||||
|  |   display_info_list_ = gs_display_list; | ||||||
|  |  | ||||||
|  |   if (display_info_list_.empty()) { | ||||||
|  |     LOG_ERROR("No display found"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (int i = 0; i < display_info_list_.size(); i++) { | ||||||
|  |     const auto &display = display_info_list_[i]; | ||||||
|  |     LOG_INFO( | ||||||
|  |         "index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - " | ||||||
|  |         "({}, {})", | ||||||
|  |         i, display.name, (display.is_primary ? "yes" : "no"), display.left, | ||||||
|  |         display.top, display.right, display.bottom); | ||||||
|  |  | ||||||
|  |     sessions_.push_back( | ||||||
|  |         {std::make_unique<WgcSessionImpl>(i), false, false, false}); | ||||||
|  |     sessions_.back().session_->RegisterObserver(this); | ||||||
|  |     error = sessions_.back().session_->Initialize((HMONITOR)display.handle); | ||||||
|  |     if (error != 0) { | ||||||
|  |       return error; | ||||||
|  |     } | ||||||
|  |     sessions_[i].inited_ = true; | ||||||
|  |     inited_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LOG_INFO("Default on monitor {}:{}", monitor_index_, | ||||||
|  |            display_info_list_[monitor_index_].name); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerWgc::Destroy() { return 0; } | int ScreenCapturerWgc::Destroy() { return 0; } | ||||||
|  |  | ||||||
| int ScreenCapturerWgc::Start() { | int ScreenCapturerWgc::Start() { | ||||||
|   if (_running == true) { |   if (running_ == true) { | ||||||
|     std::cout << "record desktop duplication is already running" << std::endl; |     LOG_ERROR("Screen capturer already running"); | ||||||
|     return 0; |     return 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (_inited == false) { |   if (inited_ == false) { | ||||||
|     std::cout << "AE_NEED_INIT" << std::endl; |     LOG_ERROR("Screen capturer not inited"); | ||||||
|     return 4; |     return 4; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   _running = true; |   for (int i = 0; i < sessions_.size(); i++) { | ||||||
|   session_->Start(); |     if (sessions_[i].inited_ == false) { | ||||||
|  |       LOG_ERROR("Session {} not inited", i); | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (sessions_[i].running_) { | ||||||
|  |       LOG_ERROR("Session {} is already running", i); | ||||||
|  |     } else { | ||||||
|  |       sessions_[i].session_->Start(); | ||||||
|  |  | ||||||
|  |       if (i != 0) { | ||||||
|  |         sessions_[i].session_->Pause(); | ||||||
|  |         sessions_[i].paused_ = true; | ||||||
|  |       } | ||||||
|  |       sessions_[i].running_ = true; | ||||||
|  |     } | ||||||
|  |     running_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerWgc::Pause() { | int ScreenCapturerWgc::Pause(int monitor_index) { | ||||||
|   _paused = true; |   if (monitor_index >= sessions_.size() || monitor_index < 0) { | ||||||
|   if (session_) session_->Pause(); |     LOG_ERROR("Invalid session index: {}", monitor_index); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!sessions_[monitor_index].paused_) { | ||||||
|  |     sessions_[monitor_index].session_->Pause(); | ||||||
|  |     sessions_[monitor_index].paused_ = true; | ||||||
|  |     LOG_INFO("Pausing session {}", monitor_index); | ||||||
|  |   } | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerWgc::Resume() { | int ScreenCapturerWgc::Resume(int monitor_index) { | ||||||
|   _paused = false; |   if (monitor_index >= sessions_.size() || monitor_index < 0) { | ||||||
|   if (session_) session_->Resume(); |     LOG_ERROR("Invalid session index: {}", monitor_index); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (sessions_[monitor_index].paused_) { | ||||||
|  |     sessions_[monitor_index].session_->Resume(); | ||||||
|  |     sessions_[monitor_index].paused_ = false; | ||||||
|  |     LOG_INFO("Resuming session {}", monitor_index); | ||||||
|  |   } | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int ScreenCapturerWgc::Stop() { | int ScreenCapturerWgc::Stop() { | ||||||
|   _running = false; |   for (int i = 0; i < sessions_.size(); i++) { | ||||||
|  |     if (sessions_[i].running_) { | ||||||
|   if (session_) session_->Stop(); |       sessions_[i].session_->Stop(); | ||||||
|  |       sessions_[i].running_ = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   running_ = false; | ||||||
|  |  | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void ConvertABGRtoBGRA(const uint8_t *abgr_data, uint8_t *bgra_data, int width, | int ScreenCapturerWgc::SwitchTo(int monitor_index) { | ||||||
|                        int height, int abgr_stride, int bgra_stride) { |   if (monitor_index_ == monitor_index) { | ||||||
|   for (int i = 0; i < height; ++i) { |     LOG_INFO("Already on monitor {}:{}", monitor_index_ + 1, | ||||||
|     for (int j = 0; j < width; ++j) { |              display_info_list_[monitor_index_].name); | ||||||
|       // ABGR<47><52>BGRA<52><41><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӳ<EFBFBD><D3B3> |     return 0; | ||||||
|       int abgr_index = (i * abgr_stride + j) * 4; |   } | ||||||
|       int bgra_index = (i * bgra_stride + j) * 4; |  | ||||||
|  |  | ||||||
|       // ֱ<>ӽ<EFBFBD><D3BD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD>ͺ<EFBFBD>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬʱ<CDAC><CAB1><EFBFBD><EFBFBD>Alphaͨ<61><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |   if (monitor_index >= display_info_list_.size()) { | ||||||
|       bgra_data[bgra_index + 0] = abgr_data[abgr_index + 2];  // <20><>ɫ |     LOG_ERROR("Invalid monitor index: {}", monitor_index); | ||||||
|       bgra_data[bgra_index + 1] = abgr_data[abgr_index + 1];  // <20><>ɫ |     return -1; | ||||||
|       bgra_data[bgra_index + 2] = abgr_data[abgr_index + 0];  // <20><>ɫ |  | ||||||
|       bgra_data[bgra_index + 3] = abgr_data[abgr_index + 3];  // Alpha |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (!sessions_[monitor_index].inited_) { | ||||||
|  |     LOG_ERROR("Monitor {} not inited", monitor_index); | ||||||
|  |     return -1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Pause(monitor_index_); | ||||||
|  |  | ||||||
|  |   monitor_index_ = monitor_index; | ||||||
|  |   LOG_INFO("Switching to monitor {}:{}", monitor_index_, | ||||||
|  |            display_info_list_[monitor_index_].name); | ||||||
|  |  | ||||||
|  |   Resume(monitor_index); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void ConvertBGRAtoABGR(const uint8_t *bgra_data, uint8_t *abgr_data, int width, | void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame, | ||||||
|                        int height, int bgra_stride, int abgr_stride) { |                                 int id) { | ||||||
|   for (int i = 0; i < height; ++i) { |   if (on_data_) { | ||||||
|     for (int j = 0; j < width; ++j) { |     if (!nv12_frame_) { | ||||||
|       // BGRA<52><41>ABGR<47><52><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӳ<EFBFBD><D3B3> |       nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2]; | ||||||
|       int bgra_index = (i * bgra_stride + j) * 4; |  | ||||||
|       int abgr_index = (i * abgr_stride + j) * 4; |  | ||||||
|  |  | ||||||
|       // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬʱ<CDAC><CAB1><EFBFBD><EFBFBD>Alphaͨ<61><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0> |  | ||||||
|       abgr_data[abgr_index + 0] = bgra_data[bgra_index + 3];  // Alpha |  | ||||||
|       abgr_data[abgr_index + 1] = bgra_data[bgra_index + 0];  // Blue |  | ||||||
|       abgr_data[abgr_index + 2] = bgra_data[bgra_index + 1];  // Green |  | ||||||
|       abgr_data[abgr_index + 3] = bgra_data[bgra_index + 2];  // Red |  | ||||||
|     } |     } | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) { |  | ||||||
|   if (_on_data) { |  | ||||||
|     int width = 1280; |  | ||||||
|     int height = 720; |  | ||||||
|  |  | ||||||
|     libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4, |     libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4, | ||||||
|                        (uint8_t *)nv12_frame_, frame.width, |                        (uint8_t *)nv12_frame_, frame.width, | ||||||
|                        (uint8_t *)(nv12_frame_ + frame.width * frame.height), |                        (uint8_t *)(nv12_frame_ + frame.width * frame.height), | ||||||
|                        frame.width, frame.width, frame.height); |                        frame.width, frame.width, frame.height); | ||||||
|  |  | ||||||
|     libyuv::NV12Scale( |     on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width, | ||||||
|         (const uint8_t *)nv12_frame_, frame.width, |              frame.height, display_info_list_[id].name.c_str()); | ||||||
|         (const uint8_t *)(nv12_frame_ + frame.width * frame.height), |  | ||||||
|         frame.width, frame.width, frame.height, (uint8_t *)nv12_frame_scaled_, |  | ||||||
|         width, (uint8_t *)(nv12_frame_scaled_ + width * height), width, width, |  | ||||||
|         height, libyuv::FilterMode::kFilterLinear); |  | ||||||
|  |  | ||||||
|     _on_data(nv12_frame_scaled_, width * height * 3 / 2, width, height); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ScreenCapturerWgc::CleanUp() { | void ScreenCapturerWgc::CleanUp() { | ||||||
|   _inited = false; |   if (inited_) { | ||||||
|  |     for (auto &session : sessions_) { | ||||||
|   if (session_) session_->Release(); |       if (session.session_) { | ||||||
|  |         session.session_->Stop(); | ||||||
|   session_ = nullptr; |       } | ||||||
|  |     } | ||||||
|  |     sessions_.clear(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| #include <functional> | #include <functional> | ||||||
| #include <string> | #include <string> | ||||||
| #include <thread> | #include <thread> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| #include "screen_capturer.h" | #include "screen_capturer.h" | ||||||
| #include "wgc_session.h" | #include "wgc_session.h" | ||||||
| @@ -19,37 +20,46 @@ class ScreenCapturerWgc : public ScreenCapturer, | |||||||
|  public: |  public: | ||||||
|   bool IsWgcSupported(); |   bool IsWgcSupported(); | ||||||
|  |  | ||||||
|   virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps, |   int Init(const int fps, cb_desktop_data cb) override; | ||||||
|                    cb_desktop_data cb); |   int Destroy() override; | ||||||
|   virtual int Destroy(); |   int Start() override; | ||||||
|  |   int Stop() override; | ||||||
|  |  | ||||||
|   virtual int Start(); |   int Pause(int monitor_index) override; | ||||||
|  |   int Resume(int monitor_index) override; | ||||||
|  |  | ||||||
|   int Pause(); |   std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; } | ||||||
|   int Resume(); |  | ||||||
|   virtual int Stop(); |  | ||||||
|  |  | ||||||
|   void OnFrame(const WgcSession::wgc_session_frame &frame); |   int SwitchTo(int monitor_index); | ||||||
|  |  | ||||||
|  |   void OnFrame(const WgcSession::wgc_session_frame &frame, int id); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void CleanUp(); |   void CleanUp(); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   WgcSession *session_ = nullptr; |   HMONITOR monitor_; | ||||||
|  |   MONITORINFOEX monitor_info_; | ||||||
|  |   std::vector<DisplayInfo> display_info_list_; | ||||||
|  |   int monitor_index_ = 0; | ||||||
|  |  | ||||||
|   std::atomic_bool _running; |  private: | ||||||
|   std::atomic_bool _paused; |   class WgcSessionInfo { | ||||||
|   std::atomic_bool _inited; |    public: | ||||||
|  |     std::unique_ptr<WgcSession> session_; | ||||||
|  |     bool inited_ = false; | ||||||
|  |     bool running_ = false; | ||||||
|  |     bool paused_ = false; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   std::thread _thread; |   std::vector<WgcSessionInfo> sessions_; | ||||||
|  |  | ||||||
|   std::string _device_name; |   std::atomic_bool running_; | ||||||
|  |   std::atomic_bool inited_; | ||||||
|  |  | ||||||
|   RECORD_DESKTOP_RECT _rect; |   int fps_; | ||||||
|  |  | ||||||
|   int _fps; |   cb_desktop_data on_data_ = nullptr; | ||||||
|  |  | ||||||
|   cb_desktop_data _on_data = nullptr; |  | ||||||
|  |  | ||||||
|   unsigned char *nv12_frame_ = nullptr; |   unsigned char *nv12_frame_ = nullptr; | ||||||
|   unsigned char *nv12_frame_scaled_ = nullptr; |   unsigned char *nv12_frame_scaled_ = nullptr; | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ class WgcSession { | |||||||
|   class wgc_session_observer { |   class wgc_session_observer { | ||||||
|    public: |    public: | ||||||
|     virtual ~wgc_session_observer() {} |     virtual ~wgc_session_observer() {} | ||||||
|     virtual void OnFrame(const wgc_session_frame &frame) = 0; |     virtual void OnFrame(const wgc_session_frame &frame, int id) = 0; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
| @@ -33,7 +33,6 @@ class WgcSession { | |||||||
|   virtual int Pause() = 0; |   virtual int Pause() = 0; | ||||||
|   virtual int Resume() = 0; |   virtual int Resume() = 0; | ||||||
|  |  | ||||||
|  protected: |  | ||||||
|   virtual ~WgcSession(){}; |   virtual ~WgcSession(){}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice( | |||||||
|     ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); |     ::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice); | ||||||
| } | } | ||||||
|  |  | ||||||
| WgcSessionImpl::WgcSessionImpl() {} | WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {} | ||||||
|  |  | ||||||
| WgcSessionImpl::~WgcSessionImpl() { | WgcSessionImpl::~WgcSessionImpl() { | ||||||
|   Stop(); |   Stop(); | ||||||
| @@ -89,6 +89,8 @@ int WgcSessionImpl::Start() { | |||||||
|  |  | ||||||
|     capture_session_.StartCapture(); |     capture_session_.StartCapture(); | ||||||
|  |  | ||||||
|  |     capture_session_.IsCursorCaptureEnabled(false); | ||||||
|  |  | ||||||
|     error = 0; |     error = 0; | ||||||
|   } catch (winrt::hresult_error) { |   } catch (winrt::hresult_error) { | ||||||
|     std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl; |     std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl; | ||||||
| @@ -122,6 +124,8 @@ int WgcSessionImpl::Stop() { | |||||||
| int WgcSessionImpl::Pause() { | int WgcSessionImpl::Pause() { | ||||||
|   std::lock_guard locker(lock_); |   std::lock_guard locker(lock_); | ||||||
|  |  | ||||||
|  |   is_paused_ = true; | ||||||
|  |  | ||||||
|   CHECK_INIT; |   CHECK_INIT; | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
| @@ -129,6 +133,8 @@ int WgcSessionImpl::Pause() { | |||||||
| int WgcSessionImpl::Resume() { | int WgcSessionImpl::Resume() { | ||||||
|   std::lock_guard locker(lock_); |   std::lock_guard locker(lock_); | ||||||
|  |  | ||||||
|  |   is_paused_ = false; | ||||||
|  |  | ||||||
|   CHECK_INIT; |   CHECK_INIT; | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
| @@ -146,7 +152,7 @@ auto WgcSessionImpl::CreateD3D11Device() { | |||||||
|  |  | ||||||
|   if (DXGI_ERROR_UNSUPPORTED == hr) { |   if (DXGI_ERROR_UNSUPPORTED == hr) { | ||||||
|     // change D3D_DRIVER_TYPE |     // change D3D_DRIVER_TYPE | ||||||
|     D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_WARP; |     type = D3D_DRIVER_TYPE_WARP; | ||||||
|     hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, |     hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, | ||||||
|                            D3D11_SDK_VERSION, d3d_device.put(), nullptr, |                            D3D11_SDK_VERSION, d3d_device.put(), nullptr, | ||||||
|                            nullptr); |                            nullptr); | ||||||
| @@ -211,7 +217,7 @@ HRESULT WgcSessionImpl::CreateMappedTexture( | |||||||
|  |  | ||||||
| void WgcSessionImpl::OnFrame( | void WgcSessionImpl::OnFrame( | ||||||
|     winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, |     winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, | ||||||
|     winrt::Windows::Foundation::IInspectable const &args) { |     [[maybe_unused]] winrt::Windows::Foundation::IInspectable const &args) { | ||||||
|   std::lock_guard locker(lock_); |   std::lock_guard locker(lock_); | ||||||
|  |  | ||||||
|   auto is_new_size = false; |   auto is_new_size = false; | ||||||
| @@ -231,6 +237,10 @@ void WgcSessionImpl::OnFrame( | |||||||
|  |  | ||||||
|     // copy to mapped texture |     // copy to mapped texture | ||||||
|     { |     { | ||||||
|  |       if (is_paused_) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       auto frame_captured = |       auto frame_captured = | ||||||
|           GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface()); |           GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface()); | ||||||
|  |  | ||||||
| @@ -254,11 +264,13 @@ void WgcSessionImpl::OnFrame( | |||||||
|  |  | ||||||
|       // copy data from map_result.pData |       // copy data from map_result.pData | ||||||
|       if (map_result.pData && observer_) { |       if (map_result.pData && observer_) { | ||||||
|         observer_->OnFrame(wgc_session_frame{ |         observer_->OnFrame( | ||||||
|             static_cast<unsigned int>(frame_size.Width), |             wgc_session_frame{static_cast<unsigned int>(frame_size.Width), | ||||||
|             static_cast<unsigned int>(frame_size.Height), map_result.RowPitch, |                               static_cast<unsigned int>(frame_size.Height), | ||||||
|  |                               map_result.RowPitch, | ||||||
|                               const_cast<const unsigned char *>( |                               const_cast<const unsigned char *>( | ||||||
|                 (unsigned char *)map_result.pData)}); |                                   (unsigned char *)map_result.pData)}, | ||||||
|  |             id_); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0); |       d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0); | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ class WgcSessionImpl : public WgcSession { | |||||||
|   } target_{0}; |   } target_{0}; | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   WgcSessionImpl(); |   WgcSessionImpl(int id); | ||||||
|   ~WgcSessionImpl() override; |   ~WgcSessionImpl() override; | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
| @@ -72,6 +72,7 @@ class WgcSessionImpl : public WgcSession { | |||||||
|   // void message_func(); |   // void message_func(); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|  |   int id_ = -1; | ||||||
|   std::mutex lock_; |   std::mutex lock_; | ||||||
|   bool is_initialized_ = false; |   bool is_initialized_ = false; | ||||||
|   bool is_running_ = false; |   bool is_running_ = false; | ||||||
|   | |||||||
							
								
								
									
										1408
									
								
								src/single_window/IconsFontAwesome6.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1408
									
								
								src/single_window/IconsFontAwesome6.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										55
									
								
								src/single_window/about_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/single_window/about_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | #include "layout_style.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::AboutWindow() { | ||||||
|  |   if (show_about_window_) { | ||||||
|  |     const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||||
|  |  | ||||||
|  |     ImGui::SetNextWindowPos(ImVec2( | ||||||
|  |         (viewport->WorkSize.x - viewport->WorkPos.x - about_window_width_) / 2, | ||||||
|  |         (viewport->WorkSize.y - viewport->WorkPos.y - about_window_height_) / | ||||||
|  |             2)); | ||||||
|  |  | ||||||
|  |     ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height_)); | ||||||
|  |  | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |     ImGui::SetWindowFontScale(0.5f); | ||||||
|  |     ImGui::Begin( | ||||||
|  |         localization::about[localization_language_index_].c_str(), nullptr, | ||||||
|  |         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |             ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); | ||||||
|  |     ImGui::SetWindowFontScale(1.0f); | ||||||
|  |     ImGui::SetWindowFontScale(0.5f); | ||||||
|  |  | ||||||
|  |     std::string version; | ||||||
|  | #ifdef RD_VERSION | ||||||
|  |     version = RD_VERSION; | ||||||
|  | #else | ||||||
|  |     version = "Unknown"; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     std::string text = | ||||||
|  |         localization::version[localization_language_index_] + ": " + version; | ||||||
|  |     ImGui::Text("%s", text.c_str()); | ||||||
|  |  | ||||||
|  |     ImGui::SetCursorPosX(about_window_width_ * 0.42f); | ||||||
|  |     ImGui::SetCursorPosY(about_window_height_ * 0.75f); | ||||||
|  |     // OK | ||||||
|  |     if (ImGui::Button(localization::ok[localization_language_index_].c_str())) { | ||||||
|  |       show_about_window_ = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SetWindowFontScale(1.0f); | ||||||
|  |     ImGui::SetWindowFontScale(0.5f); | ||||||
|  |     ImGui::End(); | ||||||
|  |     ImGui::SetWindowFontScale(1.0f); | ||||||
|  |     ImGui::PopStyleVar(3); | ||||||
|  |     ImGui::PopStyleColor(); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										179
									
								
								src/single_window/connection_status_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/single_window/connection_status_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | |||||||
|  | #include "layout_style.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::ConnectionStatusWindow( | ||||||
|  |     std::shared_ptr<SubStreamWindowProperties> &props) { | ||||||
|  |   if (show_connection_status_window_) { | ||||||
|  |     const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||||
|  |  | ||||||
|  |     ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||||
|  |                                     connection_status_window_width_) / | ||||||
|  |                                        2, | ||||||
|  |                                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||||
|  |                                     connection_status_window_height_) / | ||||||
|  |                                        2)); | ||||||
|  |  | ||||||
|  |     ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_, | ||||||
|  |                                     connection_status_window_height_)); | ||||||
|  |  | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |  | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0)); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||||
|  |  | ||||||
|  |     ImGui::Begin("ConnectionStatusWindow", nullptr, | ||||||
|  |                  ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                      ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                      ImGuiWindowFlags_NoSavedSettings); | ||||||
|  |     ImGui::PopStyleVar(2); | ||||||
|  |     ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |     ImGui::SetWindowFontScale(0.5f); | ||||||
|  |     std::string text; | ||||||
|  |  | ||||||
|  |     if (ConnectionStatus::Connecting == props->connection_status_) { | ||||||
|  |       text = localization::p2p_connecting[localization_language_index_]; | ||||||
|  |       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |     } else if (ConnectionStatus::Connected == props->connection_status_) { | ||||||
|  |       text = localization::p2p_connected[localization_language_index_]; | ||||||
|  |       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |       // ok | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::ok[localization_language_index_].c_str()) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||||
|  |         show_connection_status_window_ = false; | ||||||
|  |       } | ||||||
|  |     } else if (ConnectionStatus::Disconnected == props->connection_status_) { | ||||||
|  |       text = localization::p2p_disconnected[localization_language_index_]; | ||||||
|  |       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |       // ok | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::ok[localization_language_index_].c_str()) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||||
|  |         show_connection_status_window_ = false; | ||||||
|  |       } | ||||||
|  |     } else if (ConnectionStatus::Failed == props->connection_status_) { | ||||||
|  |       text = localization::p2p_failed[localization_language_index_]; | ||||||
|  |       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |       // ok | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::ok[localization_language_index_].c_str()) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||||
|  |         show_connection_status_window_ = false; | ||||||
|  |       } | ||||||
|  |     } else if (ConnectionStatus::Closed == props->connection_status_) { | ||||||
|  |       text = localization::p2p_closed[localization_language_index_]; | ||||||
|  |       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |       // ok | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::ok[localization_language_index_].c_str()) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Enter) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||||
|  |         show_connection_status_window_ = false; | ||||||
|  |       } | ||||||
|  |     } else if (ConnectionStatus::IncorrectPassword == | ||||||
|  |                props->connection_status_) { | ||||||
|  |       if (!password_validating_) { | ||||||
|  |         if (password_validating_time_ == 1) { | ||||||
|  |           text = localization::input_password[localization_language_index_]; | ||||||
|  |         } else { | ||||||
|  |           text = localization::reinput_password[localization_language_index_]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         auto window_width = ImGui::GetWindowSize().x; | ||||||
|  |         auto window_height = ImGui::GetWindowSize().y; | ||||||
|  |         ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f); | ||||||
|  |         ImGui::SetCursorPosY(window_height * 0.4f); | ||||||
|  |         ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2); | ||||||
|  |  | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||||
|  |  | ||||||
|  |         if (focus_on_input_widget_) { | ||||||
|  |           ImGui::SetKeyboardFocusHere(); | ||||||
|  |           focus_on_input_widget_ = false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui::InputText("##password", props->remote_password_, | ||||||
|  |                          IM_ARRAYSIZE(props->remote_password_), | ||||||
|  |                          ImGuiInputTextFlags_CharsNoBlank); | ||||||
|  |  | ||||||
|  |         ImGui::SetWindowFontScale(0.4f); | ||||||
|  |  | ||||||
|  |         ImVec2 text_size = ImGui::CalcTextSize( | ||||||
|  |             localization::remember_password[localization_language_index_] | ||||||
|  |                 .c_str()); | ||||||
|  |         ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f); | ||||||
|  |         ImGui::Checkbox( | ||||||
|  |             localization::remember_password[localization_language_index_] | ||||||
|  |                 .c_str(), | ||||||
|  |             &(props->remember_password_)); | ||||||
|  |         ImGui::SetWindowFontScale(0.5f); | ||||||
|  |         ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |         ImGui::SetCursorPosX(window_width * 0.315f); | ||||||
|  |         ImGui::SetCursorPosY(window_height * 0.75f); | ||||||
|  |         // ok | ||||||
|  |         if (ImGui::Button( | ||||||
|  |                 localization::ok[localization_language_index_].c_str()) || | ||||||
|  |             ImGui::IsKeyPressed(ImGuiKey_Enter)) { | ||||||
|  |           show_connection_status_window_ = true; | ||||||
|  |           password_validating_ = true; | ||||||
|  |           props->rejoin_ = true; | ||||||
|  |           need_to_rejoin_ = true; | ||||||
|  |           focus_on_input_widget_ = true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui::SameLine(); | ||||||
|  |         // cancel | ||||||
|  |         if (ImGui::Button( | ||||||
|  |                 localization::cancel[localization_language_index_].c_str()) || | ||||||
|  |             ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||||
|  |           memset(props->remote_password_, 0, sizeof(props->remote_password_)); | ||||||
|  |           show_connection_status_window_ = false; | ||||||
|  |           focus_on_input_widget_ = true; | ||||||
|  |         } | ||||||
|  |       } else if (password_validating_) { | ||||||
|  |         text = localization::validate_password[localization_language_index_]; | ||||||
|  |         ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |         ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |       } | ||||||
|  |     } else if (ConnectionStatus::NoSuchTransmissionId == | ||||||
|  |                props->connection_status_) { | ||||||
|  |       text = localization::no_such_id[localization_language_index_]; | ||||||
|  |       ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7); | ||||||
|  |       ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |       // ok | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::ok[localization_language_index_].c_str()) || | ||||||
|  |           ImGui::IsKeyPressed(ImGuiKey_Enter)) { | ||||||
|  |         show_connection_status_window_ = false; | ||||||
|  |         re_enter_remote_id_ = true; | ||||||
|  |         DestroyPeer(&props->peer_); | ||||||
|  |         client_properties_.erase(props->remote_id_); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auto window_width = ImGui::GetWindowSize().x; | ||||||
|  |     auto window_height = ImGui::GetWindowSize().y; | ||||||
|  |     auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||||
|  |     ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||||
|  |     ImGui::SetCursorPosY(window_height * 0.2f); | ||||||
|  |     ImGui::Text("%s", text.c_str()); | ||||||
|  |     ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |     ImGui::End(); | ||||||
|  |     ImGui::PopStyleVar(); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										325
									
								
								src/single_window/control_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								src/single_window/control_bar.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,325 @@ | |||||||
|  | #include "layout_style.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int CountDigits(int number) { | ||||||
|  |   if (number == 0) return 1; | ||||||
|  |   return (int)std::floor(std::log10(std::abs(number))) + 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int BitrateDisplay(int bitrate) { | ||||||
|  |   int num_of_digits = CountDigits(bitrate); | ||||||
|  |   if (num_of_digits <= 3) { | ||||||
|  |     ImGui::Text("%d bps", bitrate); | ||||||
|  |   } else if (num_of_digits > 3 && num_of_digits <= 6) { | ||||||
|  |     ImGui::Text("%d kbps", bitrate / 1000); | ||||||
|  |   } else { | ||||||
|  |     ImGui::Text("%.1f mbps", bitrate / 1000000.0f); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int LossRateDisplay(float loss_rate) { | ||||||
|  |   if (loss_rate < 0.01f) { | ||||||
|  |     ImGui::Text("0%%"); | ||||||
|  |   } else { | ||||||
|  |     ImGui::Text("%.0f%%", loss_rate * 100); | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) { | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |  | ||||||
|  |   if (props->control_bar_expand_) { | ||||||
|  |     ImGui::SetCursorPosX(props->is_control_bar_in_left_ | ||||||
|  |                              ? (props->control_window_width_ + 5.0f) | ||||||
|  |                              : 38.0f); | ||||||
|  |     // mouse control button | ||||||
|  |     ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||||
|  |  | ||||||
|  |     if (props->is_control_bar_in_left_) { | ||||||
|  |       draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 5.0f, | ||||||
|  |                                 ImGui::GetCursorScreenPos().y - 7.0f), | ||||||
|  |                          ImVec2(ImGui::GetCursorScreenPos().x - 5.0f, | ||||||
|  |                                 ImGui::GetCursorScreenPos().y - 7.0f + | ||||||
|  |                                     props->control_window_height_), | ||||||
|  |                          IM_COL32(178, 178, 178, 255), 1.0f); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::string display = ICON_FA_DISPLAY; | ||||||
|  |     if (ImGui::Button(display.c_str(), ImVec2(25, 25))) { | ||||||
|  |       ImGui::OpenPopup("display"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImVec2 btn_min = ImGui::GetItemRectMin(); | ||||||
|  |     ImVec2 btn_size_actual = ImGui::GetItemRectSize(); | ||||||
|  |  | ||||||
|  |     if (ImGui::BeginPopup("display")) { | ||||||
|  |       ImGui::SetWindowFontScale(0.5f); | ||||||
|  |       for (int i = 0; i < props->display_info_list_.size(); i++) { | ||||||
|  |         if (ImGui::Selectable(props->display_info_list_[i].name.c_str())) { | ||||||
|  |           props->selected_display_ = i; | ||||||
|  |  | ||||||
|  |           RemoteAction remote_action; | ||||||
|  |           remote_action.type = ControlType::display_id; | ||||||
|  |           remote_action.d = i; | ||||||
|  |           if (props->connection_status_ == ConnectionStatus::Connected) { | ||||||
|  |             SendDataFrame(props->peer_, (const char*)&remote_action, | ||||||
|  |                           sizeof(remote_action), props->data_label_.c_str()); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         props->display_selectable_hovered_ = ImGui::IsWindowHovered(); | ||||||
|  |       } | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::EndPopup(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SetWindowFontScale(0.6f); | ||||||
|  |     ImVec2 text_size = ImGui::CalcTextSize( | ||||||
|  |         std::to_string(props->selected_display_ + 1).c_str()); | ||||||
|  |     ImVec2 text_pos = | ||||||
|  |         ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.5f, | ||||||
|  |                btn_min.y + (btn_size_actual.y - text_size.y) * 0.5f - 2.0f); | ||||||
|  |     ImGui::GetWindowDrawList()->AddText( | ||||||
|  |         text_pos, IM_COL32(0, 0, 0, 255), | ||||||
|  |         std::to_string(props->selected_display_ + 1).c_str()); | ||||||
|  |     ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |     ImGui::SameLine(); | ||||||
|  |     float disable_mouse_x = ImGui::GetCursorScreenPos().x + 4.0f; | ||||||
|  |     float disable_mouse_y = ImGui::GetCursorScreenPos().y + 4.0f; | ||||||
|  |     std::string mouse = props->mouse_control_button_pressed_ | ||||||
|  |                             ? ICON_FA_COMPUTER_MOUSE | ||||||
|  |                             : ICON_FA_COMPUTER_MOUSE; | ||||||
|  |     if (ImGui::Button(mouse.c_str(), ImVec2(25, 25))) { | ||||||
|  |       if (props->connection_established_) { | ||||||
|  |         start_keyboard_capturer_ = !start_keyboard_capturer_; | ||||||
|  |         props->control_mouse_ = !props->control_mouse_; | ||||||
|  |         props->mouse_control_button_pressed_ = | ||||||
|  |             !props->mouse_control_button_pressed_; | ||||||
|  |         props->mouse_control_button_label_ = | ||||||
|  |             props->mouse_control_button_pressed_ | ||||||
|  |                 ? localization::release_mouse[localization_language_index_] | ||||||
|  |                 : localization::control_mouse[localization_language_index_]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (!props->mouse_control_button_pressed_) { | ||||||
|  |       draw_list->AddLine( | ||||||
|  |           ImVec2(disable_mouse_x, disable_mouse_y), | ||||||
|  |           ImVec2(disable_mouse_x + 16.0f, disable_mouse_y + 14.2f), | ||||||
|  |           IM_COL32(0, 0, 0, 255), 2.0f); | ||||||
|  |       draw_list->AddLine( | ||||||
|  |           ImVec2(disable_mouse_x - 1.2f, disable_mouse_y + 1.2f), | ||||||
|  |           ImVec2(disable_mouse_x + 15.3f, disable_mouse_y + 15.4f), | ||||||
|  |           ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255) | ||||||
|  |                                  : IM_COL32(179, 213, 253, 255), | ||||||
|  |           2.0f); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SameLine(); | ||||||
|  |     // audio capture button | ||||||
|  |     float disable_audio_x = ImGui::GetCursorScreenPos().x + 4; | ||||||
|  |     float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f; | ||||||
|  |     // std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH | ||||||
|  |     //                                                   : | ||||||
|  |     //                                                   ICON_FA_VOLUME_XMARK; | ||||||
|  |     std::string audio = props->audio_capture_button_pressed_ | ||||||
|  |                             ? ICON_FA_VOLUME_HIGH | ||||||
|  |                             : ICON_FA_VOLUME_HIGH; | ||||||
|  |     if (ImGui::Button(audio.c_str(), ImVec2(25, 25))) { | ||||||
|  |       if (props->connection_established_) { | ||||||
|  |         props->audio_capture_button_pressed_ = | ||||||
|  |             !props->audio_capture_button_pressed_; | ||||||
|  |         props->audio_capture_button_label_ = | ||||||
|  |             props->audio_capture_button_pressed_ | ||||||
|  |                 ? localization::audio_capture[localization_language_index_] | ||||||
|  |                 : localization::mute[localization_language_index_]; | ||||||
|  |  | ||||||
|  |         RemoteAction remote_action; | ||||||
|  |         remote_action.type = ControlType::audio_capture; | ||||||
|  |         remote_action.a = props->audio_capture_button_pressed_; | ||||||
|  |         SendDataFrame(props->peer_, (const char*)&remote_action, | ||||||
|  |                       sizeof(remote_action), props->data_label_.c_str()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (!props->audio_capture_button_pressed_) { | ||||||
|  |       draw_list->AddLine( | ||||||
|  |           ImVec2(disable_audio_x, disable_audio_y), | ||||||
|  |           ImVec2(disable_audio_x + 16.0f, disable_audio_y + 14.2f), | ||||||
|  |           IM_COL32(0, 0, 0, 255), 2.0f); | ||||||
|  |       draw_list->AddLine( | ||||||
|  |           ImVec2(disable_audio_x - 1.2f, disable_audio_y + 1.2f), | ||||||
|  |           ImVec2(disable_audio_x + 15.3f, disable_audio_y + 15.4f), | ||||||
|  |           ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255) | ||||||
|  |                                  : IM_COL32(179, 213, 253, 255), | ||||||
|  |           2.0f); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SameLine(); | ||||||
|  |     // net traffic stats button | ||||||
|  |     bool button_color_style_pushed = false; | ||||||
|  |     if (props->net_traffic_stats_button_pressed_) { | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(66 / 255.0f, 150 / 255.0f, | ||||||
|  |                                                     250 / 255.0f, 1.0f)); | ||||||
|  |       button_color_style_pushed = true; | ||||||
|  |     } | ||||||
|  |     std::string net_traffic_stats = ICON_FA_SIGNAL; | ||||||
|  |     if (ImGui::Button(net_traffic_stats.c_str(), ImVec2(25, 25))) { | ||||||
|  |       props->net_traffic_stats_button_pressed_ = | ||||||
|  |           !props->net_traffic_stats_button_pressed_; | ||||||
|  |       props->control_window_height_is_changing_ = true; | ||||||
|  |       props->net_traffic_stats_button_pressed_time_ = ImGui::GetTime(); | ||||||
|  |       props->net_traffic_stats_button_label_ = | ||||||
|  |           props->net_traffic_stats_button_pressed_ | ||||||
|  |               ? localization::hide_net_traffic_stats | ||||||
|  |                     [localization_language_index_] | ||||||
|  |               : localization::show_net_traffic_stats | ||||||
|  |                     [localization_language_index_]; | ||||||
|  |     } | ||||||
|  |     if (button_color_style_pushed) { | ||||||
|  |       ImGui::PopStyleColor(); | ||||||
|  |       button_color_style_pushed = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SameLine(); | ||||||
|  |     // fullscreen button | ||||||
|  |     std::string fullscreen = | ||||||
|  |         fullscreen_button_pressed_ ? ICON_FA_COMPRESS : ICON_FA_EXPAND; | ||||||
|  |     if (ImGui::Button(fullscreen.c_str(), ImVec2(25, 25))) { | ||||||
|  |       fullscreen_button_pressed_ = !fullscreen_button_pressed_; | ||||||
|  |       props->fullscreen_button_label_ = | ||||||
|  |           fullscreen_button_pressed_ | ||||||
|  |               ? localization::exit_fullscreen[localization_language_index_] | ||||||
|  |               : localization::fullscreen[localization_language_index_]; | ||||||
|  |  | ||||||
|  |       if (fullscreen_button_pressed_) { | ||||||
|  |         SDL_SetWindowFullscreen(stream_window_, SDL_WINDOW_FULLSCREEN_DESKTOP); | ||||||
|  |       } else { | ||||||
|  |         SDL_SetWindowFullscreen(stream_window_, SDL_FALSE); | ||||||
|  |       } | ||||||
|  |       props->reset_control_bar_pos_ = true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SameLine(); | ||||||
|  |     // close button | ||||||
|  |     std::string close_button = ICON_FA_XMARK; | ||||||
|  |     if (ImGui::Button(close_button.c_str(), ImVec2(25, 25))) { | ||||||
|  |       CleanupPeer(props); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SameLine(); | ||||||
|  |  | ||||||
|  |     if (!props->is_control_bar_in_left_) { | ||||||
|  |       draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 3.0f, | ||||||
|  |                                 ImGui::GetCursorScreenPos().y - 7.0f), | ||||||
|  |                          ImVec2(ImGui::GetCursorScreenPos().x - 3.0f, | ||||||
|  |                                 ImGui::GetCursorScreenPos().y - 7.0f + | ||||||
|  |                                     props->control_window_height_), | ||||||
|  |                          IM_COL32(178, 178, 178, 255), 1.0f); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::SetCursorPosX(props->is_control_bar_in_left_ | ||||||
|  |                            ? (props->control_window_width_ * 2 - 20.0f) | ||||||
|  |                            : 5.0f); | ||||||
|  |  | ||||||
|  |   std::string control_bar = | ||||||
|  |       props->control_bar_expand_ | ||||||
|  |           ? (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_LEFT | ||||||
|  |                                             : ICON_FA_ANGLE_RIGHT) | ||||||
|  |           : (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT | ||||||
|  |                                             : ICON_FA_ANGLE_LEFT); | ||||||
|  |   if (ImGui::Button(control_bar.c_str(), ImVec2(15, 25))) { | ||||||
|  |     props->control_bar_expand_ = !props->control_bar_expand_; | ||||||
|  |     props->control_bar_button_pressed_time_ = ImGui::GetTime(); | ||||||
|  |     props->control_window_width_is_changing_ = true; | ||||||
|  |  | ||||||
|  |     if (!props->control_bar_expand_) { | ||||||
|  |       props->control_window_height_ = 40; | ||||||
|  |       props->net_traffic_stats_button_pressed_ = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) { | ||||||
|  |     NetTrafficStats(props); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) { | ||||||
|  |   ImGui::SetCursorPos(ImVec2(props->is_control_bar_in_left_ | ||||||
|  |                                  ? (props->control_window_width_ + 5.0f) | ||||||
|  |                                  : 5.0f, | ||||||
|  |                              40.0f)); | ||||||
|  |  | ||||||
|  |   if (ImGui::BeginTable("NetTrafficStats", 4, ImGuiTableFlags_BordersH, | ||||||
|  |                         ImVec2(props->control_window_max_width_ - 10.0f, | ||||||
|  |                                props->control_window_max_height_ - 40.0f))) { | ||||||
|  |     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); | ||||||
|  |     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); | ||||||
|  |     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); | ||||||
|  |     ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); | ||||||
|  |  | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text(" "); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", localization::in[localization_language_index_].c_str()); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", localization::out[localization_language_index_].c_str()); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", | ||||||
|  |                 localization::loss_rate[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |     ImGui::TableNextRow(); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", | ||||||
|  |                 localization::video[localization_language_index_].c_str()); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.video_inbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.video_outbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     LossRateDisplay(props->net_traffic_stats_.video_inbound_stats.loss_rate); | ||||||
|  |  | ||||||
|  |     ImGui::TableNextRow(); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", | ||||||
|  |                 localization::audio[localization_language_index_].c_str()); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.audio_inbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.audio_outbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     LossRateDisplay(props->net_traffic_stats_.audio_inbound_stats.loss_rate); | ||||||
|  |  | ||||||
|  |     ImGui::TableNextRow(); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", localization::data[localization_language_index_].c_str()); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.data_inbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.data_outbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     LossRateDisplay(props->net_traffic_stats_.data_inbound_stats.loss_rate); | ||||||
|  |  | ||||||
|  |     ImGui::TableNextRow(); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     ImGui::Text("%s", | ||||||
|  |                 localization::total[localization_language_index_].c_str()); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.total_inbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     BitrateDisplay((int)props->net_traffic_stats_.total_outbound_stats.bitrate); | ||||||
|  |     ImGui::TableNextColumn(); | ||||||
|  |     LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate); | ||||||
|  |  | ||||||
|  |     ImGui::EndTable(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										223
									
								
								src/single_window/control_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								src/single_window/control_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | |||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props) { | ||||||
|  |   double time_duration = | ||||||
|  |       ImGui::GetTime() - props->control_bar_button_pressed_time_; | ||||||
|  |   if (props->control_window_width_is_changing_) { | ||||||
|  |     if (props->control_bar_expand_) { | ||||||
|  |       props->control_window_width_ = | ||||||
|  |           (float)(props->control_window_min_width_ + | ||||||
|  |                   (props->control_window_max_width_ - | ||||||
|  |                    props->control_window_min_width_) * | ||||||
|  |                       4 * time_duration); | ||||||
|  |     } else { | ||||||
|  |       props->control_window_width_ = | ||||||
|  |           (float)(props->control_window_max_width_ - | ||||||
|  |                   (props->control_window_max_width_ - | ||||||
|  |                    props->control_window_min_width_) * | ||||||
|  |                       4 * time_duration); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   time_duration = | ||||||
|  |       ImGui::GetTime() - props->net_traffic_stats_button_pressed_time_; | ||||||
|  |   if (props->control_window_height_is_changing_) { | ||||||
|  |     if (props->control_bar_expand_ && | ||||||
|  |         props->net_traffic_stats_button_pressed_) { | ||||||
|  |       props->control_window_height_ = | ||||||
|  |           (float)(props->control_window_min_height_ + | ||||||
|  |                   (props->control_window_max_height_ - | ||||||
|  |                    props->control_window_min_height_) * | ||||||
|  |                       4 * time_duration); | ||||||
|  |     } else if (props->control_bar_expand_ && | ||||||
|  |                !props->net_traffic_stats_button_pressed_) { | ||||||
|  |       props->control_window_height_ = | ||||||
|  |           (float)(props->control_window_max_height_ - | ||||||
|  |                   (props->control_window_max_height_ - | ||||||
|  |                    props->control_window_min_height_) * | ||||||
|  |                       4 * time_duration); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 1)); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); | ||||||
|  |  | ||||||
|  |   ImGui::SetNextWindowSize( | ||||||
|  |       ImVec2(props->control_window_width_, props->control_window_height_), | ||||||
|  |       ImGuiCond_Always); | ||||||
|  |  | ||||||
|  |   ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_ + 1), ImGuiCond_Once); | ||||||
|  |  | ||||||
|  |   float pos_x = 0; | ||||||
|  |   float pos_y = 0; | ||||||
|  |   float y_boundary = fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1); | ||||||
|  |  | ||||||
|  |   if (props->reset_control_bar_pos_) { | ||||||
|  |     float new_cursor_pos_x = 0; | ||||||
|  |     float new_cursor_pos_y = 0; | ||||||
|  |  | ||||||
|  |     // set control window pos | ||||||
|  |     if (props->control_window_pos_.y + props->control_window_height_ > | ||||||
|  |         stream_window_height_) { | ||||||
|  |       pos_y = stream_window_height_ - props->control_window_height_; | ||||||
|  |     } else if (props->control_window_pos_.y < y_boundary) { | ||||||
|  |       pos_y = y_boundary; | ||||||
|  |     } else { | ||||||
|  |       pos_y = props->control_window_pos_.y; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (props->is_control_bar_in_left_) { | ||||||
|  |       pos_x = 0; | ||||||
|  |     } else { | ||||||
|  |       pos_x = stream_window_width_ - props->control_window_width_; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always); | ||||||
|  |  | ||||||
|  |     if (0 != props->mouse_diff_control_bar_pos_x_ && | ||||||
|  |         0 != props->mouse_diff_control_bar_pos_y_) { | ||||||
|  |       // set cursor pos | ||||||
|  |       new_cursor_pos_x = pos_x + props->mouse_diff_control_bar_pos_x_; | ||||||
|  |       new_cursor_pos_y = pos_y + props->mouse_diff_control_bar_pos_y_; | ||||||
|  |  | ||||||
|  |       SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x, | ||||||
|  |                             (int)new_cursor_pos_y); | ||||||
|  |     } | ||||||
|  |     props->reset_control_bar_pos_ = false; | ||||||
|  |   } else if (!props->reset_control_bar_pos_ && | ||||||
|  |                  ImGui::IsMouseReleased(ImGuiMouseButton_Left) || | ||||||
|  |              props->control_window_width_is_changing_) { | ||||||
|  |     if (props->control_window_pos_.x <= stream_window_width_ / 2) { | ||||||
|  |       if (props->control_window_pos_.y + props->control_window_height_ > | ||||||
|  |           stream_window_height_) { | ||||||
|  |         pos_y = stream_window_height_ - props->control_window_height_; | ||||||
|  |       } else { | ||||||
|  |         pos_y = props->control_window_pos_.y; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (props->control_bar_expand_) { | ||||||
|  |         if (props->control_window_width_ >= props->control_window_max_width_) { | ||||||
|  |           props->control_window_width_ = props->control_window_max_width_; | ||||||
|  |           props->control_window_width_is_changing_ = false; | ||||||
|  |         } else { | ||||||
|  |           props->control_window_width_is_changing_ = true; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         if (props->control_window_width_ <= props->control_window_min_width_) { | ||||||
|  |           props->control_window_width_ = props->control_window_min_width_; | ||||||
|  |           props->control_window_width_is_changing_ = false; | ||||||
|  |         } else { | ||||||
|  |           props->control_window_width_is_changing_ = true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       props->is_control_bar_in_left_ = true; | ||||||
|  |     } else if (props->control_window_pos_.x > stream_window_width_ / 2) { | ||||||
|  |       pos_x = 0; | ||||||
|  |       pos_y = | ||||||
|  |           (props->control_window_pos_.y >= y_boundary && | ||||||
|  |            props->control_window_pos_.y <= | ||||||
|  |                stream_window_height_ - props->control_window_height_) | ||||||
|  |               ? props->control_window_pos_.y | ||||||
|  |               : (props->control_window_pos_.y < (fullscreen_button_pressed_ | ||||||
|  |                                                      ? 0 | ||||||
|  |                                                      : (title_bar_height_ + 1)) | ||||||
|  |                      ? (fullscreen_button_pressed_ ? 0 | ||||||
|  |                                                    : (title_bar_height_ + 1)) | ||||||
|  |                      : (stream_window_height_ - props->control_window_height_)); | ||||||
|  |  | ||||||
|  |       if (props->control_bar_expand_) { | ||||||
|  |         if (props->control_window_width_ >= props->control_window_max_width_) { | ||||||
|  |           props->control_window_width_ = props->control_window_max_width_; | ||||||
|  |           props->control_window_width_is_changing_ = false; | ||||||
|  |           pos_x = stream_window_width_ - props->control_window_max_width_; | ||||||
|  |         } else { | ||||||
|  |           props->control_window_width_is_changing_ = true; | ||||||
|  |           pos_x = stream_window_width_ - props->control_window_width_; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         if (props->control_window_width_ <= props->control_window_min_width_) { | ||||||
|  |           props->control_window_width_ = props->control_window_min_width_; | ||||||
|  |           props->control_window_width_is_changing_ = false; | ||||||
|  |           pos_x = stream_window_width_ - props->control_window_min_width_; | ||||||
|  |         } else { | ||||||
|  |           props->control_window_width_is_changing_ = true; | ||||||
|  |           pos_x = stream_window_width_ - props->control_window_width_; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       props->is_control_bar_in_left_ = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (props->control_window_pos_.y + props->control_window_height_ > | ||||||
|  |         stream_window_height_) { | ||||||
|  |       pos_y = stream_window_height_ - props->control_window_height_; | ||||||
|  |     } else if (props->control_window_pos_.y < y_boundary) { | ||||||
|  |       pos_y = y_boundary; | ||||||
|  |     } | ||||||
|  |     ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (props->control_bar_expand_ && props->control_window_height_is_changing_) { | ||||||
|  |     if (props->net_traffic_stats_button_pressed_) { | ||||||
|  |       if (props->control_window_height_ >= props->control_window_max_height_) { | ||||||
|  |         props->control_window_height_ = props->control_window_max_height_; | ||||||
|  |         props->control_window_height_is_changing_ = false; | ||||||
|  |       } else { | ||||||
|  |         props->control_window_height_is_changing_ = true; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       if (props->control_window_height_ <= props->control_window_min_height_) { | ||||||
|  |         props->control_window_height_ = props->control_window_min_height_; | ||||||
|  |         props->control_window_height_is_changing_ = false; | ||||||
|  |       } else { | ||||||
|  |         props->control_window_height_is_changing_ = true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string control_window_title = props->remote_id_ + "ControlWindow"; | ||||||
|  |   ImGui::Begin(control_window_title.c_str(), nullptr, | ||||||
|  |                ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | | ||||||
|  |                    ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |   props->control_window_pos_ = ImGui::GetWindowPos(); | ||||||
|  |   SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_); | ||||||
|  |   props->mouse_diff_control_bar_pos_x_ = | ||||||
|  |       props->mouse_pos_x_ - props->control_window_pos_.x; | ||||||
|  |   props->mouse_diff_control_bar_pos_y_ = | ||||||
|  |       props->mouse_pos_y_ - props->control_window_pos_.y; | ||||||
|  |  | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   static bool a, b, c, d, e; | ||||||
|  |   ImGui::SetNextWindowPos( | ||||||
|  |       ImVec2(props->is_control_bar_in_left_ | ||||||
|  |                  ? props->control_window_pos_.x - props->control_window_width_ | ||||||
|  |                  : props->control_window_pos_.x, | ||||||
|  |              props->control_window_pos_.y), | ||||||
|  |       ImGuiCond_Always); | ||||||
|  |   ImGui::SetWindowFontScale(0.5f); | ||||||
|  |  | ||||||
|  |   std::string control_child_window_title = | ||||||
|  |       props->remote_id_ + "ControlChildWindow"; | ||||||
|  |   ImGui::BeginChild( | ||||||
|  |       control_child_window_title.c_str(), | ||||||
|  |       ImVec2(props->control_window_width_ * 2, props->control_window_height_), | ||||||
|  |       ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration); | ||||||
|  |   ImGui::SetWindowFontScale(1.0f); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   ControlBar(props); | ||||||
|  |   props->control_bar_hovered_ = ImGui::IsWindowHovered(); | ||||||
|  |  | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |   ImGui::End(); | ||||||
|  |   ImGui::PopStyleVar(4); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								src/single_window/layout_style.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/single_window/layout_style.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-06-14 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _LAYOUT_STYLE_H_ | ||||||
|  | #define _LAYOUT_STYLE_H_ | ||||||
|  |  | ||||||
|  | #define MENU_WINDOW_WIDTH_CN 300 | ||||||
|  | #define MENU_WINDOW_HEIGHT_CN 280 | ||||||
|  | #define LOCAL_WINDOW_WIDTH_CN 300 | ||||||
|  | #define LOCAL_WINDOW_HEIGHT_CN 280 | ||||||
|  | #define REMOTE_WINDOW_WIDTH_CN 300 | ||||||
|  | #define REMOTE_WINDOW_HEIGHT_CN 280 | ||||||
|  | #define MENU_WINDOW_WIDTH_EN 190 | ||||||
|  | #define MENU_WINDOW_HEIGHT_EN 245 | ||||||
|  | #define IPUT_WINDOW_WIDTH 160 | ||||||
|  | #define INPUT_WINDOW_PADDING_CN 66 | ||||||
|  | #define INPUT_WINDOW_PADDING_EN 96 | ||||||
|  | #define SETTINGS_WINDOW_WIDTH_CN 181 | ||||||
|  | #define SETTINGS_WINDOW_WIDTH_EN 228 | ||||||
|  | #define SETTINGS_WINDOW_HEIGHT_CN 220 | ||||||
|  | #define SETTINGS_WINDOW_HEIGHT_EN 220 | ||||||
|  | #define LANGUAGE_SELECT_WINDOW_PADDING_CN 100 | ||||||
|  | #define LANGUAGE_SELECT_WINDOW_PADDING_EN 147 | ||||||
|  | #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 100 | ||||||
|  | #define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 147 | ||||||
|  | #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 100 | ||||||
|  | #define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 147 | ||||||
|  | #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 151 | ||||||
|  | #define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 198 | ||||||
|  | #define ENABLE_TURN_CHECKBOX_PADDING_CN 151 | ||||||
|  | #define ENABLE_TURN_CHECKBOX_PADDING_EN 198 | ||||||
|  | #define SETTINGS_SELECT_WINDOW_WIDTH 73 | ||||||
|  | #define SETTINGS_OK_BUTTON_PADDING_CN 55 | ||||||
|  | #define SETTINGS_OK_BUTTON_PADDING_EN 78 | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										303
									
								
								src/single_window/local_peer_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								src/single_window/local_peer_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | |||||||
|  | #include <random> | ||||||
|  |  | ||||||
|  | #include "layout_style.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::LocalWindow() { | ||||||
|  |   ImGui::SetNextWindowPos(ImVec2(-1.0f, title_bar_height_), ImGuiCond_Always); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |  | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   ImGui::BeginChild("LocalDesktopWindow", | ||||||
|  |                     ImVec2(local_window_width_, local_window_height_), | ||||||
|  |                     ImGuiChildFlags_None, | ||||||
|  |                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                         ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_); | ||||||
|  |   ImGui::Indent(main_child_window_x_padding_); | ||||||
|  |  | ||||||
|  |   ImGui::TextColored( | ||||||
|  |       ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s", | ||||||
|  |       localization::local_desktop[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |   ImGui::Spacing(); | ||||||
|  |   { | ||||||
|  |     ImGui::SetNextWindowPos( | ||||||
|  |         ImVec2(main_child_window_x_padding_, | ||||||
|  |                title_bar_height_ + main_child_window_y_padding_), | ||||||
|  |         ImGuiCond_Always); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255, | ||||||
|  |                                                    242.0f / 255, 1.0f)); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||||
|  |     ImGui::BeginChild( | ||||||
|  |         "LocalDesktopWindow_1", | ||||||
|  |         ImVec2(local_child_window_width_, local_child_window_height_), | ||||||
|  |         ImGuiChildFlags_Border, | ||||||
|  |         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |             ImGuiWindowFlags_NoTitleBar | | ||||||
|  |             ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |     ImGui::PopStyleVar(); | ||||||
|  |     ImGui::PopStyleColor(); | ||||||
|  |     { | ||||||
|  |       ImGui::SetWindowFontScale(0.8f); | ||||||
|  |       ImGui::Text("%s", | ||||||
|  |                   localization::local_id[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |       ImGui::Spacing(); | ||||||
|  |  | ||||||
|  |       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||||
|  |  | ||||||
|  |       if (strcmp(client_id_display_, client_id_)) { | ||||||
|  |         for (int i = 0, j = 0; i < sizeof(client_id_); i++, j++) { | ||||||
|  |           client_id_display_[j] = client_id_[i]; | ||||||
|  |           if (i == 2 || i == 5) { | ||||||
|  |             client_id_display_[++j] = ' '; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::InputText( | ||||||
|  |           "##local_id", client_id_display_, IM_ARRAYSIZE(client_id_display_), | ||||||
|  |           ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_ReadOnly); | ||||||
|  |       ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |       ImGui::SameLine(); | ||||||
|  |  | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0)); | ||||||
|  |       ImGui::SetWindowFontScale(0.5f); | ||||||
|  |       if (ImGui::Button(ICON_FA_COPY, ImVec2(35, 38))) { | ||||||
|  |         local_id_copied_ = true; | ||||||
|  |         ImGui::SetClipboardText(client_id_); | ||||||
|  |         copy_start_time_ = ImGui::GetTime(); | ||||||
|  |       } | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::PopStyleColor(3); | ||||||
|  |  | ||||||
|  |       double time_duration = ImGui::GetTime() - copy_start_time_; | ||||||
|  |       if (local_id_copied_ && time_duration < 1.0f) { | ||||||
|  |         const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||||
|  |         ImGui::SetNextWindowPos( | ||||||
|  |             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||||
|  |                     notification_window_width_) / | ||||||
|  |                        2, | ||||||
|  |                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||||
|  |                     notification_window_height_) / | ||||||
|  |                        2)); | ||||||
|  |  | ||||||
|  |         ImGui::SetNextWindowSize( | ||||||
|  |             ImVec2(notification_window_width_, notification_window_height_)); | ||||||
|  |         ImGui::PushStyleColor( | ||||||
|  |             ImGuiCol_WindowBg, | ||||||
|  |             ImVec4(1.0f, 1.0f, 1.0f, 1.0f - (float)time_duration)); | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||||
|  |         ImGui::Begin("ConnectionStatusWindow", nullptr, | ||||||
|  |                      ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | | ||||||
|  |                          ImGuiWindowFlags_NoSavedSettings); | ||||||
|  |         ImGui::PopStyleVar(2); | ||||||
|  |         ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |         auto window_width = ImGui::GetWindowSize().x; | ||||||
|  |         auto window_height = ImGui::GetWindowSize().y; | ||||||
|  |         ImGui::SetWindowFontScale(0.8f); | ||||||
|  |         std::string text = localization::local_id_copied_to_clipboard | ||||||
|  |             [localization_language_index_]; | ||||||
|  |         auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||||
|  |         ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||||
|  |         ImGui::SetCursorPosY(window_height * 0.5f); | ||||||
|  |         ImGui::PushStyleColor(ImGuiCol_Text, | ||||||
|  |                               ImVec4(0, 0, 0, 1.0f - (float)time_duration)); | ||||||
|  |         ImGui::Text("%s", text.c_str()); | ||||||
|  |         ImGui::PopStyleColor(); | ||||||
|  |         ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |         ImGui::End(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::Spacing(); | ||||||
|  |       ImGui::Separator(); | ||||||
|  |       ImGui::Spacing(); | ||||||
|  |  | ||||||
|  |       ImGui::SetWindowFontScale(0.8f); | ||||||
|  |       ImGui::Text("%s", | ||||||
|  |                   localization::password[localization_language_index_].c_str()); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); | ||||||
|  |       ImGui::Spacing(); | ||||||
|  |  | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||||
|  |       ImGui::InputTextWithHint( | ||||||
|  |           "##server_pwd", | ||||||
|  |           localization::max_password_len[localization_language_index_].c_str(), | ||||||
|  |           password_saved_, IM_ARRAYSIZE(password_saved_), | ||||||
|  |           show_password_ | ||||||
|  |               ? ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_ReadOnly | ||||||
|  |               : ImGuiInputTextFlags_CharsNoBlank | | ||||||
|  |                     ImGuiInputTextFlags_Password | | ||||||
|  |                     ImGuiInputTextFlags_ReadOnly); | ||||||
|  |       ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |       ImGui::SameLine(); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0)); | ||||||
|  |       ImGui::SetWindowFontScale(0.5f); | ||||||
|  |       auto l_x = ImGui::GetCursorScreenPos().x; | ||||||
|  |       auto l_y = ImGui::GetCursorScreenPos().y; | ||||||
|  |       if (ImGui::Button(ICON_FA_EYE, ImVec2(22, 38))) { | ||||||
|  |         show_password_ = !show_password_; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (!show_password_) { | ||||||
|  |         ImDrawList *draw_list = ImGui::GetWindowDrawList(); | ||||||
|  |         draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 12.5f), | ||||||
|  |                            ImVec2(l_x + 20.3f, l_y + 26.5f), | ||||||
|  |                            IM_COL32(239, 240, 242, 255), 2.0f); | ||||||
|  |         draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 11.0f), | ||||||
|  |                            ImVec2(l_x + 20.3f, l_y + 25.0f), | ||||||
|  |                            IM_COL32(0, 0, 0, 255), 1.5f); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::SameLine(); | ||||||
|  |  | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               regenerate_password_ ? ICON_FA_SPINNER : ICON_FA_ARROWS_ROTATE, | ||||||
|  |               ImVec2(22, 38))) { | ||||||
|  |         regenerate_password_ = true; | ||||||
|  |         regenerate_password_start_time_ = ImGui::GetTime(); | ||||||
|  |         LeaveConnection(peer_, client_id_); | ||||||
|  |       } | ||||||
|  |       if (ImGui::GetTime() - regenerate_password_start_time_ > 0.3f) { | ||||||
|  |         regenerate_password_ = false; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::SameLine(); | ||||||
|  |  | ||||||
|  |       if (ImGui::Button(ICON_FA_PEN, ImVec2(22, 38))) { | ||||||
|  |         show_reset_password_window_ = true; | ||||||
|  |       } | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::PopStyleColor(3); | ||||||
|  |  | ||||||
|  |       if (show_reset_password_window_) { | ||||||
|  |         const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||||
|  |  | ||||||
|  |         ImGui::SetNextWindowPos( | ||||||
|  |             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||||
|  |                     connection_status_window_width_) / | ||||||
|  |                        2, | ||||||
|  |                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||||
|  |                     connection_status_window_height_) / | ||||||
|  |                        2)); | ||||||
|  |  | ||||||
|  |         ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_, | ||||||
|  |                                         connection_status_window_height_)); | ||||||
|  |  | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |  | ||||||
|  |         ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0)); | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||||
|  |  | ||||||
|  |         ImGui::Begin("ResetPasswordWindow", nullptr, | ||||||
|  |                      ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                          ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                          ImGuiWindowFlags_NoSavedSettings); | ||||||
|  |         ImGui::PopStyleVar(2); | ||||||
|  |         ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |         auto window_width = ImGui::GetWindowSize().x; | ||||||
|  |         auto window_height = ImGui::GetWindowSize().y; | ||||||
|  |         std::string text = | ||||||
|  |             localization::new_password[localization_language_index_]; | ||||||
|  |         ImGui::SetWindowFontScale(0.5f); | ||||||
|  |         auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||||
|  |         ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||||
|  |         ImGui::SetCursorPosY(window_height * 0.2f); | ||||||
|  |         ImGui::Text("%s", text.c_str()); | ||||||
|  |  | ||||||
|  |         ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f); | ||||||
|  |         ImGui::SetCursorPosY(window_height * 0.4f); | ||||||
|  |         ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2); | ||||||
|  |  | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||||
|  |  | ||||||
|  |         if (focus_on_input_widget_) { | ||||||
|  |           ImGui::SetKeyboardFocusHere(); | ||||||
|  |           focus_on_input_widget_ = false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         bool enter_pressed = ImGui::InputText( | ||||||
|  |             "##new_password", new_password_, IM_ARRAYSIZE(new_password_), | ||||||
|  |             ImGuiInputTextFlags_CharsNoBlank | | ||||||
|  |                 ImGuiInputTextFlags_EnterReturnsTrue); | ||||||
|  |  | ||||||
|  |         ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |         ImGui::SetCursorPosX(window_width * 0.315f); | ||||||
|  |         ImGui::SetCursorPosY(window_height * 0.75f); | ||||||
|  |  | ||||||
|  |         // OK | ||||||
|  |         if (ImGui::Button( | ||||||
|  |                 localization::ok[localization_language_index_].c_str()) || | ||||||
|  |             enter_pressed) { | ||||||
|  |           if (6 != strlen(new_password_)) { | ||||||
|  |             LOG_ERROR("Invalid password length"); | ||||||
|  |             show_reset_password_window_ = true; | ||||||
|  |             focus_on_input_widget_ = true; | ||||||
|  |           } else { | ||||||
|  |             show_reset_password_window_ = false; | ||||||
|  |             memset(&password_saved_, 0, sizeof(password_saved_)); | ||||||
|  |             strncpy(password_saved_, new_password_, | ||||||
|  |                     sizeof(password_saved_) - 1); | ||||||
|  |             password_saved_[sizeof(password_saved_) - 1] = '\0'; | ||||||
|  |  | ||||||
|  |             std::string client_id_with_password = | ||||||
|  |                 std::string(client_id_) + "@" + password_saved_; | ||||||
|  |             strncpy(client_id_with_password_, client_id_with_password.c_str(), | ||||||
|  |                     sizeof(client_id_with_password_) - 1); | ||||||
|  |             client_id_with_password_[sizeof(client_id_with_password_) - 1] = | ||||||
|  |                 '\0'; | ||||||
|  |  | ||||||
|  |             SaveSettingsIntoCacheFile(); | ||||||
|  |  | ||||||
|  |             LeaveConnection(peer_, client_id_); | ||||||
|  |             DestroyPeer(&peer_); | ||||||
|  |             focus_on_input_widget_ = true; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui::SameLine(); | ||||||
|  |  | ||||||
|  |         if (ImGui::Button( | ||||||
|  |                 localization::cancel[localization_language_index_].c_str())) { | ||||||
|  |           show_reset_password_window_ = false; | ||||||
|  |           focus_on_input_widget_ = true; | ||||||
|  |           memset(new_password_, 0, sizeof(new_password_)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |         ImGui::End(); | ||||||
|  |         ImGui::PopStyleVar(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::EndChild(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								src/single_window/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/single_window/main_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::MainWindow() { | ||||||
|  |   ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   ImGui::BeginChild("DeskWindow", | ||||||
|  |                     ImVec2(main_window_width_default_, local_window_height_), | ||||||
|  |                     ImGuiChildFlags_Border, | ||||||
|  |                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                         ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   LocalWindow(); | ||||||
|  |  | ||||||
|  |   ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||||
|  |   draw_list->AddLine( | ||||||
|  |       ImVec2(main_window_width_default_ / 2, title_bar_height_ + 15.0f), | ||||||
|  |       ImVec2(main_window_width_default_ / 2, title_bar_height_ + 225.0f), | ||||||
|  |       IM_COL32(0, 0, 0, 122), 1.0f); | ||||||
|  |  | ||||||
|  |   RemoteWindow(); | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |  | ||||||
|  |   RecentConnectionsWindow(); | ||||||
|  |   StatusBar(); | ||||||
|  |  | ||||||
|  |   for (auto& it : client_properties_) { | ||||||
|  |     ConnectionStatusWindow(it.second); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										286
									
								
								src/single_window/recent_connections.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								src/single_window/recent_connections.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,286 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::RecentConnectionsWindow() { | ||||||
|  |   ImGui::SetNextWindowPos( | ||||||
|  |       ImVec2(0, title_bar_height_ + local_window_height_ - 1.0f), | ||||||
|  |       ImGuiCond_Always); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   ImGui::BeginChild( | ||||||
|  |       "RecentConnectionsWindow", | ||||||
|  |       ImVec2(main_window_width_default_, | ||||||
|  |              main_window_height_default_ - title_bar_height_ - | ||||||
|  |                  local_window_height_ - status_bar_height_ + 1.0f), | ||||||
|  |       ImGuiChildFlags_Border, | ||||||
|  |       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |           ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |           ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_); | ||||||
|  |   ImGui::Indent(main_child_window_x_padding_); | ||||||
|  |  | ||||||
|  |   ImGui::TextColored( | ||||||
|  |       ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s", | ||||||
|  |       localization::recent_connections[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |   ShowRecentConnections(); | ||||||
|  |  | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::ShowRecentConnections() { | ||||||
|  |   ImGui::SetCursorPosX(25.0f); | ||||||
|  |   ImVec2 sub_window_pos = ImGui::GetCursorPos(); | ||||||
|  |   std::map<std::string, ImVec2> sub_containers_pos; | ||||||
|  |   float recent_connection_sub_container_width = | ||||||
|  |       recent_connection_image_width_ + 16.0f; | ||||||
|  |   float recent_connection_sub_container_height = | ||||||
|  |       recent_connection_image_height_ + 36.0f; | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, | ||||||
|  |                         ImVec4(239.0f / 255, 240.0f / 255, 242.0f / 255, 1.0f)); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||||
|  |   ImGui::BeginChild("RecentConnectionsContainer", | ||||||
|  |                     ImVec2(main_window_width_default_ - 50.0f, 145.0f), | ||||||
|  |                     ImGuiChildFlags_Border, | ||||||
|  |                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                         ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||||
|  |                         ImGuiWindowFlags_AlwaysHorizontalScrollbar | | ||||||
|  |                         ImGuiWindowFlags_NoScrollbar | | ||||||
|  |                         ImGuiWindowFlags_NoScrollWithMouse); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |   size_t recent_connections_count = recent_connections_.size(); | ||||||
|  |   int count = 0; | ||||||
|  |   float button_width = 22; | ||||||
|  |   float button_height = 22; | ||||||
|  |   for (auto& it : recent_connections_) { | ||||||
|  |     sub_containers_pos[it.first] = ImGui::GetCursorPos(); | ||||||
|  |     std::string recent_connection_sub_window_name = | ||||||
|  |         "RecentConnectionsSubContainer" + it.first; | ||||||
|  |     // recent connections sub container | ||||||
|  |     ImGui::BeginChild(recent_connection_sub_window_name.c_str(), | ||||||
|  |                       ImVec2(recent_connection_sub_container_width, | ||||||
|  |                              recent_connection_sub_container_height), | ||||||
|  |                       ImGuiChildFlags_None, | ||||||
|  |                       ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                           ImGuiWindowFlags_NoMove | | ||||||
|  |                           ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                           ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||||
|  |                           ImGuiWindowFlags_NoScrollbar); | ||||||
|  |     std::string connection_info = it.first; | ||||||
|  |  | ||||||
|  |     // remote id length is 9 | ||||||
|  |     // password length is 6 | ||||||
|  |     // connection_info -> remote_id + 'Y' + host_name + '@' + password | ||||||
|  |     //                 -> remote_id + 'N' + host_name | ||||||
|  |     if ('Y' == connection_info[9] && connection_info.size() >= 16) { | ||||||
|  |       size_t pos_y = connection_info.find('Y'); | ||||||
|  |       size_t pos_at = connection_info.find('@'); | ||||||
|  |  | ||||||
|  |       if (pos_y == std::string::npos || pos_at == std::string::npos || | ||||||
|  |           pos_y >= pos_at) { | ||||||
|  |         LOG_ERROR("Invalid filename"); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       it.second.remote_id = connection_info.substr(0, pos_y); | ||||||
|  |       it.second.remote_host_name = | ||||||
|  |           connection_info.substr(pos_y + 1, pos_at - pos_y - 1); | ||||||
|  |       it.second.password = connection_info.substr(pos_at + 1); | ||||||
|  |       it.second.remember_password = true; | ||||||
|  |     } else if ('N' == connection_info[9] && connection_info.size() >= 10) { | ||||||
|  |       size_t pos_n = connection_info.find('N'); | ||||||
|  |       size_t pos_at = connection_info.find('@'); | ||||||
|  |  | ||||||
|  |       if (pos_n == std::string::npos) { | ||||||
|  |         LOG_ERROR("Invalid filename"); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       it.second.remote_id = connection_info.substr(0, pos_n); | ||||||
|  |       it.second.remote_host_name = connection_info.substr(pos_n + 1); | ||||||
|  |       it.second.password = ""; | ||||||
|  |       it.second.remember_password = false; | ||||||
|  |     } else { | ||||||
|  |       it.second.remote_host_name = "unknown"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImVec2 image_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x + 5.0f, | ||||||
|  |                                      ImGui::GetCursorScreenPos().y + 5.0f); | ||||||
|  |     ImVec2 image_pos = | ||||||
|  |         ImVec2(ImGui::GetCursorPosX() + 5.0f, ImGui::GetCursorPosY() + 5.0f); | ||||||
|  |     ImGui::SetCursorPos(image_pos); | ||||||
|  |     ImGui::Image((ImTextureID)(intptr_t)it.second.texture, | ||||||
|  |                  ImVec2((float)recent_connection_image_width_, | ||||||
|  |                         (float)recent_connection_image_height_)); | ||||||
|  |  | ||||||
|  |     // remote id display button | ||||||
|  |     { | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.2f)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0.2f)); | ||||||
|  |  | ||||||
|  |       ImVec2 dummy_button_pos = | ||||||
|  |           ImVec2(image_pos.x, image_pos.y + recent_connection_image_height_); | ||||||
|  |       std::string dummy_button_name = "##DummyButton" + it.second.remote_id; | ||||||
|  |       ImGui::SetCursorPos(dummy_button_pos); | ||||||
|  |       ImGui::SetWindowFontScale(0.6f); | ||||||
|  |       ImGui::Button(dummy_button_name.c_str(), | ||||||
|  |                     ImVec2(recent_connection_image_width_ - 2 * button_width, | ||||||
|  |                            button_height)); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::SetCursorPos( | ||||||
|  |           ImVec2(dummy_button_pos.x + 2.0f, dummy_button_pos.y + 1.0f)); | ||||||
|  |       ImGui::SetWindowFontScale(0.65f); | ||||||
|  |       ImGui::Text("%s", it.second.remote_id.c_str()); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::PopStyleColor(3); | ||||||
|  |  | ||||||
|  |       if (ImGui::IsItemHovered()) { | ||||||
|  |         ImGui::BeginTooltip(); | ||||||
|  |         ImGui::SetWindowFontScale(0.5f); | ||||||
|  |         ImGui::Text("%s", it.second.remote_host_name.c_str()); | ||||||
|  |         ImGui::SetWindowFontScale(1.0f); | ||||||
|  |         ImGui::EndTooltip(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, | ||||||
|  |                           ImVec4(0.1f, 0.4f, 0.8f, 1.0f)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ButtonActive, | ||||||
|  |                           ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); | ||||||
|  |     ImGui::SetWindowFontScale(0.5f); | ||||||
|  |     // trash button | ||||||
|  |     { | ||||||
|  |       ImVec2 trash_can_button_pos = ImVec2( | ||||||
|  |           image_pos.x + recent_connection_image_width_ - 2 * button_width, | ||||||
|  |           image_pos.y + recent_connection_image_height_); | ||||||
|  |       ImGui::SetCursorPos(trash_can_button_pos); | ||||||
|  |       std::string trash_can = ICON_FA_TRASH_CAN; | ||||||
|  |       std::string recent_connection_delete_button_name = | ||||||
|  |           trash_can + "##RecentConnectionDelete" + | ||||||
|  |           std::to_string(trash_can_button_pos.x); | ||||||
|  |       if (ImGui::Button(recent_connection_delete_button_name.c_str(), | ||||||
|  |                         ImVec2(button_width, button_height))) { | ||||||
|  |         show_confirm_delete_connection_ = true; | ||||||
|  |         delete_connection_name_ = it.first; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (delete_connection_ && delete_connection_name_ == it.first) { | ||||||
|  |         if (!thumbnail_->DeleteThumbnail(it.first)) { | ||||||
|  |           reload_recent_connections_ = true; | ||||||
|  |           delete_connection_ = false; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // connect button | ||||||
|  |     { | ||||||
|  |       ImVec2 connect_button_pos = | ||||||
|  |           ImVec2(image_pos.x + recent_connection_image_width_ - button_width, | ||||||
|  |                  image_pos.y + recent_connection_image_height_); | ||||||
|  |       ImGui::SetCursorPos(connect_button_pos); | ||||||
|  |       std::string connect = ICON_FA_ARROW_RIGHT_LONG; | ||||||
|  |       std::string connect_to_this_connection_button_name = | ||||||
|  |           connect + "##ConnectionTo" + it.first; | ||||||
|  |       if (ImGui::Button(connect_to_this_connection_button_name.c_str(), | ||||||
|  |                         ImVec2(button_width, button_height))) { | ||||||
|  |         ConnectTo(it.second.remote_id, it.second.password.c_str(), | ||||||
|  |                   it.second.remember_password); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     ImGui::SetWindowFontScale(1.0f); | ||||||
|  |     ImGui::PopStyleColor(3); | ||||||
|  |  | ||||||
|  |     ImGui::EndChild(); | ||||||
|  |  | ||||||
|  |     if (count != recent_connections_count - 1) { | ||||||
|  |       ImVec2 line_start = | ||||||
|  |           ImVec2(image_screen_pos.x + recent_connection_image_width_ + 20.0f, | ||||||
|  |                  image_screen_pos.y); | ||||||
|  |       ImVec2 line_end = ImVec2( | ||||||
|  |           image_screen_pos.x + recent_connection_image_width_ + 20.0f, | ||||||
|  |           image_screen_pos.y + recent_connection_image_height_ + button_height); | ||||||
|  |       ImGui::GetForegroundDrawList()->AddLine(line_start, line_end, | ||||||
|  |                                               IM_COL32(0, 0, 0, 122), 1.0f); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     count++; | ||||||
|  |     ImGui::SameLine(0, count != recent_connections_count ? 26.0f : 0.0f); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |  | ||||||
|  |   if (show_confirm_delete_connection_) { | ||||||
|  |     ConfirmDeleteConnection(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::ConfirmDeleteConnection() { | ||||||
|  |   const ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||||||
|  |   ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||||
|  |                                   connection_status_window_width_) / | ||||||
|  |                                      2, | ||||||
|  |                                  (viewport->WorkSize.y - viewport->WorkPos.y - | ||||||
|  |                                   connection_status_window_height_) / | ||||||
|  |                                      2)); | ||||||
|  |  | ||||||
|  |   ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_, | ||||||
|  |                                   connection_status_window_height_)); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0)); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||||
|  |  | ||||||
|  |   ImGui::Begin("ConfirmDeleteConnectionWindow", nullptr, | ||||||
|  |                ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                    ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                    ImGuiWindowFlags_NoSavedSettings); | ||||||
|  |   ImGui::PopStyleVar(2); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   std::string text = | ||||||
|  |       localization::confirm_delete_connection[localization_language_index_]; | ||||||
|  |   ImGui::SetCursorPosX(connection_status_window_width_ * 6 / 19); | ||||||
|  |   ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3); | ||||||
|  |  | ||||||
|  |   // ok | ||||||
|  |   ImGui::SetWindowFontScale(0.5f); | ||||||
|  |   if (ImGui::Button(localization::ok[localization_language_index_].c_str()) || | ||||||
|  |       ImGui::IsKeyPressed(ImGuiKey_Enter)) { | ||||||
|  |     delete_connection_ = true; | ||||||
|  |     show_confirm_delete_connection_ = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::SameLine(); | ||||||
|  |   // cancel | ||||||
|  |   if (ImGui::Button( | ||||||
|  |           localization::cancel[localization_language_index_].c_str()) || | ||||||
|  |       ImGui::IsKeyPressed(ImGuiKey_Escape)) { | ||||||
|  |     delete_connection_ = false; | ||||||
|  |     show_confirm_delete_connection_ = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto window_width = ImGui::GetWindowSize().x; | ||||||
|  |   auto window_height = ImGui::GetWindowSize().y; | ||||||
|  |  | ||||||
|  |   auto text_width = ImGui::CalcTextSize(text.c_str()).x; | ||||||
|  |   ImGui::SetCursorPosX((window_width - text_width) * 0.5f); | ||||||
|  |   ImGui::SetCursorPosY(window_height * 0.2f); | ||||||
|  |   ImGui::Text("%s", text.c_str()); | ||||||
|  |   ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |   ImGui::End(); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										180
									
								
								src/single_window/remote_peer_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								src/single_window/remote_peer_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | |||||||
|  | #include "layout_style.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | static int InputTextCallback(ImGuiInputTextCallbackData *data); | ||||||
|  |  | ||||||
|  | int Render::RemoteWindow() { | ||||||
|  |   ImGui::SetNextWindowPos(ImVec2(local_window_width_ + 1.0f, title_bar_height_), | ||||||
|  |                           ImGuiCond_Always); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |  | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   ImGui::BeginChild("RemoteDesktopWindow", | ||||||
|  |                     ImVec2(remote_window_width_, remote_window_height_), | ||||||
|  |                     ImGuiChildFlags_None, | ||||||
|  |                     ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                         ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |                         ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_); | ||||||
|  |   ImGui::Indent(main_child_window_x_padding_ - 1.0f); | ||||||
|  |  | ||||||
|  |   ImGui::TextColored( | ||||||
|  |       ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s", | ||||||
|  |       localization::remote_desktop[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |   ImGui::Spacing(); | ||||||
|  |   { | ||||||
|  |     ImGui::SetNextWindowPos( | ||||||
|  |         ImVec2(local_window_width_ + main_child_window_x_padding_ - 1.0f, | ||||||
|  |                title_bar_height_ + main_child_window_y_padding_), | ||||||
|  |         ImGuiCond_Always); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255, | ||||||
|  |                                                    242.0f / 255, 1.0f)); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); | ||||||
|  |  | ||||||
|  |     ImGui::BeginChild( | ||||||
|  |         "RemoteDesktopWindow_1", | ||||||
|  |         ImVec2(remote_child_window_width_, remote_child_window_height_), | ||||||
|  |         ImGuiChildFlags_Border, | ||||||
|  |         ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |             ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | | ||||||
|  |             ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |     ImGui::PopStyleVar(); | ||||||
|  |     ImGui::PopStyleColor(); | ||||||
|  |     { | ||||||
|  |       ImGui::SetWindowFontScale(0.8f); | ||||||
|  |       ImGui::Text( | ||||||
|  |           "%s", localization::remote_id[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |       ImGui::Spacing(); | ||||||
|  |       ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||||
|  |       if (re_enter_remote_id_) { | ||||||
|  |         ImGui::SetKeyboardFocusHere(); | ||||||
|  |         re_enter_remote_id_ = false; | ||||||
|  |         memset(remote_id_display_, 0, sizeof(remote_id_display_)); | ||||||
|  |       } | ||||||
|  |       bool enter_pressed = ImGui::InputText( | ||||||
|  |           "##remote_id_", remote_id_display_, IM_ARRAYSIZE(remote_id_display_), | ||||||
|  |           ImGuiInputTextFlags_CharsDecimal | | ||||||
|  |               ImGuiInputTextFlags_EnterReturnsTrue | | ||||||
|  |               ImGuiInputTextFlags_CallbackEdit, | ||||||
|  |           InputTextCallback); | ||||||
|  |  | ||||||
|  |       ImGui::PopStyleVar(); | ||||||
|  |       ImGui::SameLine(); | ||||||
|  |  | ||||||
|  |       std::string remote_id = remote_id_display_; | ||||||
|  |       remote_id.erase(remove_if(remote_id.begin(), remote_id.end(), | ||||||
|  |                                 static_cast<int (*)(int)>(&isspace)), | ||||||
|  |                       remote_id.end()); | ||||||
|  |       if (ImGui::Button(ICON_FA_ARROW_RIGHT_LONG, ImVec2(55, 38)) || | ||||||
|  |           enter_pressed) { | ||||||
|  |         connect_button_pressed_ = true; | ||||||
|  |         bool found = false; | ||||||
|  |         for (auto &[id, props] : recent_connections_) { | ||||||
|  |           if (id.find(remote_id) != std::string::npos) { | ||||||
|  |             found = true; | ||||||
|  |             if (client_properties_.find(remote_id) != | ||||||
|  |                 client_properties_.end()) { | ||||||
|  |               if (!client_properties_[remote_id]->connection_established_) { | ||||||
|  |                 ConnectTo(props.remote_id, props.password.c_str(), false); | ||||||
|  |               } else { | ||||||
|  |                 // todo: show warning message | ||||||
|  |                 LOG_INFO("Already connected to [{}]", remote_id); | ||||||
|  |               } | ||||||
|  |             } else { | ||||||
|  |               ConnectTo(props.remote_id, props.password.c_str(), false); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!found) { | ||||||
|  |           ConnectTo(remote_id, "", false); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (need_to_rejoin_) { | ||||||
|  |         need_to_rejoin_ = false; | ||||||
|  |         for (const auto &[_, props] : client_properties_) { | ||||||
|  |           if (props->rejoin_) { | ||||||
|  |             ConnectTo(props->remote_id_, props->remote_password_, | ||||||
|  |                       props->remember_password_); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     ImGui::EndChild(); | ||||||
|  |   } | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int InputTextCallback(ImGuiInputTextCallbackData *data) { | ||||||
|  |   if (data->BufTextLen > 3 && data->Buf[3] != ' ') { | ||||||
|  |     data->InsertChars(3, " "); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (data->BufTextLen > 7 && data->Buf[7] != ' ') { | ||||||
|  |     data->InsertChars(7, " "); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::ConnectTo(const std::string &remote_id, const char *password, | ||||||
|  |                       bool remember_password) { | ||||||
|  |   LOG_INFO("Connect to [{}]", remote_id); | ||||||
|  |  | ||||||
|  |   if (client_properties_.find(remote_id) == client_properties_.end()) { | ||||||
|  |     client_properties_[remote_id] = | ||||||
|  |         std::make_shared<SubStreamWindowProperties>(); | ||||||
|  |     auto props = client_properties_[remote_id]; | ||||||
|  |     props->local_id_ = "C-" + std::string(client_id_); | ||||||
|  |     props->remote_id_ = remote_id; | ||||||
|  |     memcpy(&props->params_, ¶ms_, sizeof(Params)); | ||||||
|  |     props->params_.user_id = props->local_id_.c_str(); | ||||||
|  |     props->peer_ = CreatePeer(&props->params_); | ||||||
|  |     AddAudioStream(props->peer_, props->audio_label_.c_str()); | ||||||
|  |     AddDataStream(props->peer_, props->data_label_.c_str()); | ||||||
|  |  | ||||||
|  |     if (props->peer_) { | ||||||
|  |       LOG_INFO("[{}] Create peer instance successful", props->local_id_); | ||||||
|  |       Init(props->peer_); | ||||||
|  |       LOG_INFO("[{}] Peer init finish", props->local_id_); | ||||||
|  |     } else { | ||||||
|  |       LOG_INFO("Create peer [{}] instance failed", props->local_id_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     props->connection_status_ = ConnectionStatus::Connecting; | ||||||
|  |   } | ||||||
|  |   int ret = -1; | ||||||
|  |   auto props = client_properties_[remote_id]; | ||||||
|  |   if (!props->connection_established_) { | ||||||
|  |     props->remember_password_ = remember_password; | ||||||
|  |     if (strcmp(password, "") != 0 && | ||||||
|  |         strcmp(password, props->remote_password_) != 0) { | ||||||
|  |       strncpy(props->remote_password_, password, | ||||||
|  |               sizeof(props->remote_password_) - 1); | ||||||
|  |       props->remote_password_[sizeof(props->remote_password_) - 1] = '\0'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::string remote_id_with_pwd = remote_id + "@" + password; | ||||||
|  |     ret = JoinConnection(props->peer_, remote_id_with_pwd.c_str()); | ||||||
|  |     if (0 == ret) { | ||||||
|  |       props->rejoin_ = false; | ||||||
|  |     } else { | ||||||
|  |       props->rejoin_ = true; | ||||||
|  |       need_to_rejoin_ = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										1379
									
								
								src/single_window/render.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1379
									
								
								src/single_window/render.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										438
									
								
								src/single_window/render.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								src/single_window/render.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,438 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-05-29 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _MAIN_WINDOW_H_ | ||||||
|  | #define _MAIN_WINDOW_H_ | ||||||
|  |  | ||||||
|  | #include <SDL.h> | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <chrono> | ||||||
|  | #include <fstream> | ||||||
|  | #include <mutex> | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <unordered_map> | ||||||
|  |  | ||||||
|  | #include "IconsFontAwesome6.h" | ||||||
|  | #include "config_center.h" | ||||||
|  | #include "device_controller_factory.h" | ||||||
|  | #include "imgui.h" | ||||||
|  | #include "imgui_impl_sdl2.h" | ||||||
|  | #include "imgui_impl_sdlrenderer2.h" | ||||||
|  | #include "imgui_internal.h" | ||||||
|  | #include "minirtc.h" | ||||||
|  | #include "path_manager.h" | ||||||
|  | #include "screen_capturer_factory.h" | ||||||
|  | #include "speaker_capturer_factory.h" | ||||||
|  | #include "thumbnail.h" | ||||||
|  |  | ||||||
|  | class Render { | ||||||
|  |  public: | ||||||
|  |   struct SubStreamWindowProperties { | ||||||
|  |     Params params_; | ||||||
|  |     PeerPtr *peer_ = nullptr; | ||||||
|  |     std::string audio_label_ = "control_audio"; | ||||||
|  |     std::string data_label_ = "control_data"; | ||||||
|  |     std::string local_id_ = ""; | ||||||
|  |     std::string remote_id_ = ""; | ||||||
|  |     bool exit_ = false; | ||||||
|  |     bool signal_connected_ = false; | ||||||
|  |     SignalStatus signal_status_ = SignalStatus::SignalClosed; | ||||||
|  |     bool connection_established_ = false; | ||||||
|  |     bool rejoin_ = false; | ||||||
|  |     bool net_traffic_stats_button_pressed_ = false; | ||||||
|  |     bool mouse_control_button_pressed_ = false; | ||||||
|  |     bool mouse_controller_is_started_ = false; | ||||||
|  |     bool audio_capture_button_pressed_ = false; | ||||||
|  |     bool control_mouse_ = false; | ||||||
|  |     bool streaming_ = false; | ||||||
|  |     bool is_control_bar_in_left_ = true; | ||||||
|  |     bool control_bar_hovered_ = false; | ||||||
|  |     bool display_selectable_hovered_ = false; | ||||||
|  |     bool control_bar_expand_ = true; | ||||||
|  |     bool reset_control_bar_pos_ = false; | ||||||
|  |     bool control_window_width_is_changing_ = false; | ||||||
|  |     bool control_window_height_is_changing_ = false; | ||||||
|  |     bool p2p_mode_ = true; | ||||||
|  |     bool remember_password_ = false; | ||||||
|  |     char remote_password_[7] = ""; | ||||||
|  |     float sub_stream_window_width_ = 1280; | ||||||
|  |     float sub_stream_window_height_ = 720; | ||||||
|  |     float control_window_min_width_ = 20; | ||||||
|  |     float control_window_max_width_ = 230; | ||||||
|  |     float control_window_min_height_ = 40; | ||||||
|  |     float control_window_max_height_ = 150; | ||||||
|  |     float control_window_width_ = 230; | ||||||
|  |     float control_window_height_ = 40; | ||||||
|  |     float control_bar_pos_x_ = 0; | ||||||
|  |     float control_bar_pos_y_ = 30; | ||||||
|  |     float mouse_diff_control_bar_pos_x_ = 0; | ||||||
|  |     float mouse_diff_control_bar_pos_y_ = 0; | ||||||
|  |     double control_bar_button_pressed_time_ = 0; | ||||||
|  |     double net_traffic_stats_button_pressed_time_ = 0; | ||||||
|  |     unsigned char *dst_buffer_ = nullptr; | ||||||
|  |     size_t dst_buffer_capacity_ = 0; | ||||||
|  |     int mouse_pos_x_ = 0; | ||||||
|  |     int mouse_pos_y_ = 0; | ||||||
|  |     int mouse_pos_x_last_ = 0; | ||||||
|  |     int mouse_pos_y_last_ = 0; | ||||||
|  |     int texture_width_ = 1280; | ||||||
|  |     int texture_height_ = 720; | ||||||
|  |     int video_width_ = 0; | ||||||
|  |     int video_height_ = 0; | ||||||
|  |     int video_width_last_ = 0; | ||||||
|  |     int video_height_last_ = 0; | ||||||
|  |     int selected_display_ = 0; | ||||||
|  |     size_t video_size_ = 0; | ||||||
|  |     bool tab_selected_ = false; | ||||||
|  |     bool tab_opened_ = true; | ||||||
|  |     std::optional<float> pos_x_before_docked_; | ||||||
|  |     std::optional<float> pos_y_before_docked_; | ||||||
|  |     float render_window_x_ = 0; | ||||||
|  |     float render_window_y_ = 0; | ||||||
|  |     float render_window_width_ = 0; | ||||||
|  |     float render_window_height_ = 0; | ||||||
|  |     std::string fullscreen_button_label_ = "Fullscreen"; | ||||||
|  |     std::string net_traffic_stats_button_label_ = "Show Net Traffic Stats"; | ||||||
|  |     std::string mouse_control_button_label_ = "Mouse Control"; | ||||||
|  |     std::string audio_capture_button_label_ = "Audio Capture"; | ||||||
|  |     std::string remote_host_name_ = ""; | ||||||
|  |     std::vector<DisplayInfo> display_info_list_; | ||||||
|  |     SDL_Texture *stream_texture_ = nullptr; | ||||||
|  |     SDL_Rect stream_render_rect_; | ||||||
|  |     SDL_Rect stream_render_rect_last_; | ||||||
|  |     ImVec2 control_window_pos_; | ||||||
|  |     ConnectionStatus connection_status_ = ConnectionStatus::Closed; | ||||||
|  |     TraversalMode traversal_mode_ = TraversalMode::UnknownMode; | ||||||
|  |     XNetTrafficStats net_traffic_stats_; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   Render(); | ||||||
|  |   ~Render(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   int Run(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   void InitializeLogger(); | ||||||
|  |   void InitializeSettings(); | ||||||
|  |   void InitializeSDL(); | ||||||
|  |   void InitializeModules(); | ||||||
|  |   void InitializeMainWindow(); | ||||||
|  |   void MainLoop(); | ||||||
|  |   void UpdateLabels(); | ||||||
|  |   void UpdateInteractions(); | ||||||
|  |   void HandleRecentConnections(); | ||||||
|  |   void HandleStreamWindow(); | ||||||
|  |   void Cleanup(); | ||||||
|  |   void CleanupFactories(); | ||||||
|  |   void CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props); | ||||||
|  |   void CleanupPeers(); | ||||||
|  |   void CleanSubStreamWindowProperties( | ||||||
|  |       std::shared_ptr<SubStreamWindowProperties> props); | ||||||
|  |   void UpdateRenderRect(); | ||||||
|  |   void ProcessSdlEvent(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int CreateStreamRenderWindow(); | ||||||
|  |   int TitleBar(bool main_window); | ||||||
|  |   int MainWindow(); | ||||||
|  |   int StreamWindow(); | ||||||
|  |   int LocalWindow(); | ||||||
|  |   int RemoteWindow(); | ||||||
|  |   int RecentConnectionsWindow(); | ||||||
|  |   int SettingWindow(); | ||||||
|  |   int ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props); | ||||||
|  |   int ControlBar(std::shared_ptr<SubStreamWindowProperties> &props); | ||||||
|  |   int AboutWindow(); | ||||||
|  |   int StatusBar(); | ||||||
|  |   int ConnectionStatusWindow(std::shared_ptr<SubStreamWindowProperties> &props); | ||||||
|  |   int ShowRecentConnections(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int ConnectTo(const std::string &remote_id, const char *password, | ||||||
|  |                 bool remember_password); | ||||||
|  |   int CreateMainWindow(); | ||||||
|  |   int DestroyMainWindow(); | ||||||
|  |   int CreateStreamWindow(); | ||||||
|  |   int DestroyStreamWindow(); | ||||||
|  |   int SetupFontAndStyle(); | ||||||
|  |   int SetupMainWindow(); | ||||||
|  |   int DestroyMainWindowContext(); | ||||||
|  |   int SetupStreamWindow(); | ||||||
|  |   int DestroyStreamWindowContext(); | ||||||
|  |   int DrawMainWindow(); | ||||||
|  |   int DrawStreamWindow(); | ||||||
|  |   int ConfirmDeleteConnection(); | ||||||
|  |   int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties> &props); | ||||||
|  |   void DrawConnectionStatusText( | ||||||
|  |       std::shared_ptr<SubStreamWindowProperties> &props); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   static void OnReceiveVideoBufferCb(const XVideoFrame *video_frame, | ||||||
|  |                                      const char *user_id, size_t user_id_size, | ||||||
|  |                                      void *user_data); | ||||||
|  |  | ||||||
|  |   static void OnReceiveAudioBufferCb(const char *data, size_t size, | ||||||
|  |                                      const char *user_id, size_t user_id_size, | ||||||
|  |                                      void *user_data); | ||||||
|  |  | ||||||
|  |   static void OnReceiveDataBufferCb(const char *data, size_t size, | ||||||
|  |                                     const char *user_id, size_t user_id_size, | ||||||
|  |                                     void *user_data); | ||||||
|  |  | ||||||
|  |   static void OnSignalStatusCb(SignalStatus status, const char *user_id, | ||||||
|  |                                size_t user_id_size, void *user_data); | ||||||
|  |  | ||||||
|  |   static void OnConnectionStatusCb(ConnectionStatus status, const char *user_id, | ||||||
|  |                                    size_t user_id_size, void *user_data); | ||||||
|  |  | ||||||
|  |   static void NetStatusReport(const char *client_id, size_t client_id_size, | ||||||
|  |                               TraversalMode mode, | ||||||
|  |                               const XNetTrafficStats *net_traffic_stats, | ||||||
|  |                               const char *user_id, const size_t user_id_size, | ||||||
|  |                               void *user_data); | ||||||
|  |  | ||||||
|  |   static SDL_HitTestResult HitTestCallback(SDL_Window *window, | ||||||
|  |                                            const SDL_Point *area, void *data); | ||||||
|  |  | ||||||
|  |   static std::vector<char> SerializeRemoteAction(const RemoteAction &action); | ||||||
|  |  | ||||||
|  |   static bool DeserializeRemoteAction(const char *data, size_t size, | ||||||
|  |                                       RemoteAction &out); | ||||||
|  |  | ||||||
|  |   static void FreeRemoteAction(RemoteAction &action); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int SendKeyCommand(int key_code, bool is_down); | ||||||
|  |   int ProcessMouseEvent(SDL_Event &event); | ||||||
|  |  | ||||||
|  |   static void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len); | ||||||
|  |   static void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int SaveSettingsIntoCacheFile(); | ||||||
|  |   int LoadSettingsFromCacheFile(); | ||||||
|  |  | ||||||
|  |   int ScreenCapturerInit(); | ||||||
|  |   int StartScreenCapturer(); | ||||||
|  |   int StopScreenCapturer(); | ||||||
|  |  | ||||||
|  |   int StartSpeakerCapturer(); | ||||||
|  |   int StopSpeakerCapturer(); | ||||||
|  |  | ||||||
|  |   int StartMouseController(); | ||||||
|  |   int StopMouseController(); | ||||||
|  |  | ||||||
|  |   int StartKeyboardCapturer(); | ||||||
|  |   int StopKeyboardCapturer(); | ||||||
|  |  | ||||||
|  |   int CreateConnectionPeer(); | ||||||
|  |  | ||||||
|  |   int AudioDeviceInit(); | ||||||
|  |   int AudioDeviceDestroy(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   struct CDCache { | ||||||
|  |     char client_id_with_password[17]; | ||||||
|  |     int language; | ||||||
|  |     int video_quality; | ||||||
|  |     int video_encode_format; | ||||||
|  |     bool enable_hardware_video_codec; | ||||||
|  |     bool enable_turn; | ||||||
|  |  | ||||||
|  |     unsigned char key[16]; | ||||||
|  |     unsigned char iv[16]; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   CDCache cd_cache_; | ||||||
|  |   std::mutex cd_cache_mutex_; | ||||||
|  |   ConfigCenter config_center_; | ||||||
|  |   ConfigCenter::LANGUAGE localization_language_ = | ||||||
|  |       ConfigCenter::LANGUAGE::CHINESE; | ||||||
|  |   std::unique_ptr<PathManager> path_manager_; | ||||||
|  |   std::string cert_path_; | ||||||
|  |   std::string exec_log_path_; | ||||||
|  |   std::string dll_log_path_; | ||||||
|  |   std::string cache_path_; | ||||||
|  |   std::string imgui_cache_path_; | ||||||
|  |   int localization_language_index_ = -1; | ||||||
|  |   int localization_language_index_last_ = -1; | ||||||
|  |   bool modules_inited_ = false; | ||||||
|  |   /* ------ all windows property start ------ */ | ||||||
|  |   float title_bar_width_ = 640; | ||||||
|  |   float title_bar_height_ = 30; | ||||||
|  |   /* ------ all windows property end ------ */ | ||||||
|  |  | ||||||
|  |   /* ------ main window property start ------ */ | ||||||
|  |   // thumbnail | ||||||
|  |   unsigned char aes128_key_[16]; | ||||||
|  |   unsigned char aes128_iv_[16]; | ||||||
|  |   std::unique_ptr<Thumbnail> thumbnail_; | ||||||
|  |  | ||||||
|  |   // recent connections | ||||||
|  |   std::vector<std::pair<std::string, Thumbnail::RecentConnection>> | ||||||
|  |       recent_connections_; | ||||||
|  |   int recent_connection_image_width_ = 160; | ||||||
|  |   int recent_connection_image_height_ = 90; | ||||||
|  |   uint32_t recent_connection_image_save_time_ = 0; | ||||||
|  |  | ||||||
|  |   // main window render | ||||||
|  |   SDL_Window *main_window_ = nullptr; | ||||||
|  |   SDL_Renderer *main_renderer_ = nullptr; | ||||||
|  |   ImGuiContext *main_ctx_ = nullptr; | ||||||
|  |   bool exit_ = false; | ||||||
|  |  | ||||||
|  |   // main window properties | ||||||
|  |   bool start_mouse_controller_ = false; | ||||||
|  |   bool mouse_controller_is_started_ = false; | ||||||
|  |   bool start_screen_capturer_ = false; | ||||||
|  |   bool screen_capturer_is_started_ = false; | ||||||
|  |   bool start_keyboard_capturer_ = false; | ||||||
|  |   bool keyboard_capturer_is_started_ = false; | ||||||
|  |   bool foucs_on_main_window_ = false; | ||||||
|  |   bool foucs_on_stream_window_ = false; | ||||||
|  |   bool audio_capture_ = false; | ||||||
|  |   int main_window_width_real_ = 720; | ||||||
|  |   int main_window_height_real_ = 540; | ||||||
|  |   float main_window_dpi_scaling_w_ = 1.0f; | ||||||
|  |   float main_window_dpi_scaling_h_ = 1.0f; | ||||||
|  |   float main_window_width_default_ = 640; | ||||||
|  |   float main_window_height_default_ = 480; | ||||||
|  |   float main_window_width_ = 640; | ||||||
|  |   float main_window_height_ = 480; | ||||||
|  |   float main_window_width_last_ = 640; | ||||||
|  |   float main_window_height_last_ = 480; | ||||||
|  |   float local_window_width_ = 320; | ||||||
|  |   float local_window_height_ = 235; | ||||||
|  |   float remote_window_width_ = 320; | ||||||
|  |   float remote_window_height_ = 235; | ||||||
|  |   float local_child_window_width_ = 266; | ||||||
|  |   float local_child_window_height_ = 180; | ||||||
|  |   float remote_child_window_width_ = 266; | ||||||
|  |   float remote_child_window_height_ = 180; | ||||||
|  |   float main_window_text_y_padding_ = 10; | ||||||
|  |   float main_child_window_x_padding_ = 27; | ||||||
|  |   float main_child_window_y_padding_ = 45; | ||||||
|  |   float status_bar_height_ = 22; | ||||||
|  |   float connection_status_window_width_ = 200; | ||||||
|  |   float connection_status_window_height_ = 150; | ||||||
|  |   float notification_window_width_ = 200; | ||||||
|  |   float notification_window_height_ = 80; | ||||||
|  |   float about_window_width_ = 200; | ||||||
|  |   float about_window_height_ = 150; | ||||||
|  |   int screen_width_ = 1280; | ||||||
|  |   int screen_height_ = 720; | ||||||
|  |   int selected_display_ = 0; | ||||||
|  |   std::string connect_button_label_ = "Connect"; | ||||||
|  |   char input_password_tmp_[7] = ""; | ||||||
|  |   char input_password_[7] = ""; | ||||||
|  |   std::string random_password_ = ""; | ||||||
|  |   char new_password_[7] = ""; | ||||||
|  |   char remote_id_display_[12] = ""; | ||||||
|  |   unsigned char audio_buffer_[720]; | ||||||
|  |   int audio_len_ = 0; | ||||||
|  |   bool audio_buffer_fresh_ = false; | ||||||
|  |   bool need_to_rejoin_ = false; | ||||||
|  |   bool just_created_ = false; | ||||||
|  |   std::string controlled_remote_id_ = ""; | ||||||
|  |   bool need_to_send_host_info_ = false; | ||||||
|  |   SDL_Event last_mouse_event; | ||||||
|  |  | ||||||
|  |   // stream window render | ||||||
|  |   SDL_Window *stream_window_ = nullptr; | ||||||
|  |   SDL_Renderer *stream_renderer_ = nullptr; | ||||||
|  |   ImGuiContext *stream_ctx_ = nullptr; | ||||||
|  |  | ||||||
|  |   // stream window properties | ||||||
|  |   bool need_to_create_stream_window_ = false; | ||||||
|  |   bool stream_window_created_ = false; | ||||||
|  |   bool stream_window_inited_ = false; | ||||||
|  |   bool window_maximized_ = false; | ||||||
|  |   bool stream_window_grabbed_ = false; | ||||||
|  |   bool control_mouse_ = false; | ||||||
|  |   int stream_window_width_default_ = 1280; | ||||||
|  |   int stream_window_height_default_ = 720; | ||||||
|  |   float stream_window_width_ = 1280; | ||||||
|  |   float stream_window_height_ = 720; | ||||||
|  |   uint32_t stream_pixformat_ = 0; | ||||||
|  |   int stream_window_width_real_ = 1280; | ||||||
|  |   int stream_window_height_real_ = 720; | ||||||
|  |   float stream_window_dpi_scaling_w_ = 1.0f; | ||||||
|  |   float stream_window_dpi_scaling_h_ = 1.0f; | ||||||
|  |  | ||||||
|  |   bool label_inited_ = false; | ||||||
|  |   bool connect_button_pressed_ = false; | ||||||
|  |   bool password_validating_ = false; | ||||||
|  |   uint32_t password_validating_time_ = 0; | ||||||
|  |   bool show_settings_window_ = false; | ||||||
|  |   bool rejoin_ = false; | ||||||
|  |   bool local_id_copied_ = false; | ||||||
|  |   bool show_password_ = true; | ||||||
|  |   bool regenerate_password_ = false; | ||||||
|  |   bool show_about_window_ = false; | ||||||
|  |   bool show_connection_status_window_ = false; | ||||||
|  |   bool show_reset_password_window_ = false; | ||||||
|  |   bool fullscreen_button_pressed_ = false; | ||||||
|  |   bool focus_on_input_widget_ = true; | ||||||
|  |   bool is_client_mode_ = false; | ||||||
|  |   bool reload_recent_connections_ = true; | ||||||
|  |   bool show_confirm_delete_connection_ = false; | ||||||
|  |   bool delete_connection_ = false; | ||||||
|  |   bool is_tab_bar_hovered_ = false; | ||||||
|  |   std::string delete_connection_name_ = ""; | ||||||
|  |   bool re_enter_remote_id_ = false; | ||||||
|  |   double copy_start_time_ = 0; | ||||||
|  |   double regenerate_password_start_time_ = 0; | ||||||
|  |   SignalStatus signal_status_ = SignalStatus::SignalClosed; | ||||||
|  |   std::string signal_status_str_ = ""; | ||||||
|  |   bool signal_connected_ = false; | ||||||
|  |   PeerPtr *peer_ = nullptr; | ||||||
|  |   PeerPtr *peer_reserved_ = nullptr; | ||||||
|  |   std::string video_primary_label_ = "primary_display"; | ||||||
|  |   std::string video_secondary_label_ = "secondary_display"; | ||||||
|  |   std::string audio_label_ = "audio"; | ||||||
|  |   std::string data_label_ = "data"; | ||||||
|  |   Params params_; | ||||||
|  |   SDL_AudioDeviceID input_dev_; | ||||||
|  |   SDL_AudioDeviceID output_dev_; | ||||||
|  |   ScreenCapturerFactory *screen_capturer_factory_ = nullptr; | ||||||
|  |   ScreenCapturer *screen_capturer_ = nullptr; | ||||||
|  |   SpeakerCapturerFactory *speaker_capturer_factory_ = nullptr; | ||||||
|  |   SpeakerCapturer *speaker_capturer_ = nullptr; | ||||||
|  |   DeviceControllerFactory *device_controller_factory_ = nullptr; | ||||||
|  |   MouseController *mouse_controller_ = nullptr; | ||||||
|  |   KeyboardCapturer *keyboard_capturer_ = nullptr; | ||||||
|  |   std::vector<DisplayInfo> display_info_list_; | ||||||
|  |   uint64_t last_frame_time_; | ||||||
|  |   char client_id_[10] = ""; | ||||||
|  |   char client_id_display_[12] = ""; | ||||||
|  |   char client_id_with_password_[17] = ""; | ||||||
|  |   char password_saved_[7] = ""; | ||||||
|  |   int language_button_value_ = 0; | ||||||
|  |   int video_quality_button_value_ = 0; | ||||||
|  |   int video_encode_format_button_value_ = 0; | ||||||
|  |   bool enable_hardware_video_codec_ = false; | ||||||
|  |   bool enable_turn_ = false; | ||||||
|  |   int language_button_value_last_ = 0; | ||||||
|  |   int video_quality_button_value_last_ = 0; | ||||||
|  |   int video_encode_format_button_value_last_ = 0; | ||||||
|  |   bool enable_hardware_video_codec_last_ = false; | ||||||
|  |   bool enable_turn_last_ = false; | ||||||
|  |   bool settings_window_pos_reset_ = true; | ||||||
|  |   /* ------ main window property end ------ */ | ||||||
|  |  | ||||||
|  |   /* ------ sub stream window property start ------ */ | ||||||
|  |   std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>> | ||||||
|  |       client_properties_; | ||||||
|  |   void CloseTab(decltype(client_properties_)::iterator &it); | ||||||
|  |   /* ------ stream window property end ------ */ | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										527
									
								
								src/single_window/render_callback_func.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										527
									
								
								src/single_window/render_callback_func.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,527 @@ | |||||||
|  | #include "device_controller.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "platform.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 | ||||||
|  |  | ||||||
|  | #define STREAM_FRASH (SDL_USEREVENT + 1) | ||||||
|  |  | ||||||
|  | #ifdef DESK_PORT_DEBUG | ||||||
|  | #else | ||||||
|  | #define MOUSE_CONTROL 1 | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | int Render::SendKeyCommand(int key_code, bool is_down) { | ||||||
|  |   RemoteAction remote_action; | ||||||
|  |   remote_action.type = ControlType::keyboard; | ||||||
|  |   if (is_down) { | ||||||
|  |     remote_action.k.flag = KeyFlag::key_down; | ||||||
|  |   } else { | ||||||
|  |     remote_action.k.flag = KeyFlag::key_up; | ||||||
|  |   } | ||||||
|  |   remote_action.k.key_value = key_code; | ||||||
|  |  | ||||||
|  |   if (!controlled_remote_id_.empty()) { | ||||||
|  |     if (client_properties_.find(controlled_remote_id_) != | ||||||
|  |         client_properties_.end()) { | ||||||
|  |       auto props = client_properties_[controlled_remote_id_]; | ||||||
|  |       if (props->connection_status_ == ConnectionStatus::Connected) { | ||||||
|  |         SendDataFrame(props->peer_, (const char *)&remote_action, | ||||||
|  |                       sizeof(remote_action), props->data_label_.c_str()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::ProcessMouseEvent(SDL_Event &event) { | ||||||
|  |   controlled_remote_id_ = ""; | ||||||
|  |   int video_width, video_height = 0; | ||||||
|  |   int render_width, render_height = 0; | ||||||
|  |   float ratio_x, ratio_y = 0; | ||||||
|  |   RemoteAction remote_action; | ||||||
|  |  | ||||||
|  |   for (auto &it : client_properties_) { | ||||||
|  |     auto props = it.second; | ||||||
|  |     if (!props->control_mouse_) { | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (event.button.x >= props->stream_render_rect_.x && | ||||||
|  |         event.button.x <= | ||||||
|  |             props->stream_render_rect_.x + props->stream_render_rect_.w && | ||||||
|  |         event.button.y >= props->stream_render_rect_.y && | ||||||
|  |         event.button.y <= | ||||||
|  |             props->stream_render_rect_.y + props->stream_render_rect_.h) { | ||||||
|  |       controlled_remote_id_ = it.first; | ||||||
|  |       render_width = props->stream_render_rect_.w; | ||||||
|  |       render_height = props->stream_render_rect_.h; | ||||||
|  |       last_mouse_event.button.x = event.button.x; | ||||||
|  |       last_mouse_event.button.y = event.button.y; | ||||||
|  |  | ||||||
|  |       remote_action.m.x = | ||||||
|  |           (float)(event.button.x - props->stream_render_rect_.x) / render_width; | ||||||
|  |       remote_action.m.y = | ||||||
|  |           (float)(event.button.y - props->stream_render_rect_.y) / | ||||||
|  |           render_height; | ||||||
|  |  | ||||||
|  |       if (SDL_MOUSEBUTTONDOWN == event.type) { | ||||||
|  |         remote_action.type = ControlType::mouse; | ||||||
|  |         if (SDL_BUTTON_LEFT == event.button.button) { | ||||||
|  |           remote_action.m.flag = MouseFlag::left_down; | ||||||
|  |         } else if (SDL_BUTTON_RIGHT == event.button.button) { | ||||||
|  |           remote_action.m.flag = MouseFlag::right_down; | ||||||
|  |         } else if (SDL_BUTTON_MIDDLE == event.button.button) { | ||||||
|  |           remote_action.m.flag = MouseFlag::middle_down; | ||||||
|  |         } | ||||||
|  |       } else if (SDL_MOUSEBUTTONUP == event.type) { | ||||||
|  |         remote_action.type = ControlType::mouse; | ||||||
|  |         if (SDL_BUTTON_LEFT == event.button.button) { | ||||||
|  |           remote_action.m.flag = MouseFlag::left_up; | ||||||
|  |         } else if (SDL_BUTTON_RIGHT == event.button.button) { | ||||||
|  |           remote_action.m.flag = MouseFlag::right_up; | ||||||
|  |         } else if (SDL_BUTTON_MIDDLE == event.button.button) { | ||||||
|  |           remote_action.m.flag = MouseFlag::middle_up; | ||||||
|  |         } | ||||||
|  |       } else if (SDL_MOUSEMOTION == event.type) { | ||||||
|  |         remote_action.type = ControlType::mouse; | ||||||
|  |         remote_action.m.flag = MouseFlag::move; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (props->control_bar_hovered_ || props->display_selectable_hovered_) { | ||||||
|  |         remote_action.m.flag = MouseFlag::move; | ||||||
|  |       } | ||||||
|  |       SendDataFrame(props->peer_, (const char *)&remote_action, | ||||||
|  |                     sizeof(remote_action), props->data_label_.c_str()); | ||||||
|  |     } else if (SDL_MOUSEWHEEL == event.type && | ||||||
|  |                last_mouse_event.button.x >= props->stream_render_rect_.x && | ||||||
|  |                last_mouse_event.button.x <= props->stream_render_rect_.x + | ||||||
|  |                                                 props->stream_render_rect_.w && | ||||||
|  |                last_mouse_event.button.y >= props->stream_render_rect_.y && | ||||||
|  |                last_mouse_event.button.y <= props->stream_render_rect_.y + | ||||||
|  |                                                 props->stream_render_rect_.h) { | ||||||
|  |       int scroll_x = event.wheel.x; | ||||||
|  |       int scroll_y = event.wheel.y; | ||||||
|  |       if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { | ||||||
|  |         scroll_x = -scroll_x; | ||||||
|  |         scroll_y = -scroll_y; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       remote_action.type = ControlType::mouse; | ||||||
|  |       if (scroll_x == 0) { | ||||||
|  |         remote_action.m.flag = MouseFlag::wheel_vertical; | ||||||
|  |         remote_action.m.s = scroll_y; | ||||||
|  |       } else if (scroll_y == 0) { | ||||||
|  |         remote_action.m.flag = MouseFlag::wheel_horizontal; | ||||||
|  |         remote_action.m.s = scroll_x; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       render_width = props->stream_render_rect_.w; | ||||||
|  |       render_height = props->stream_render_rect_.h; | ||||||
|  |       remote_action.m.x = | ||||||
|  |           (float)(event.button.x - props->stream_render_rect_.x) / render_width; | ||||||
|  |       remote_action.m.y = | ||||||
|  |           (float)(event.button.y - props->stream_render_rect_.y) / | ||||||
|  |           render_height; | ||||||
|  |  | ||||||
|  |       SendDataFrame(props->peer_, (const char *)&remote_action, | ||||||
|  |                     sizeof(remote_action), props->data_label_.c_str()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) { | ||||||
|  |   Render *render = (Render *)userdata; | ||||||
|  |   if (!render) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (1) { | ||||||
|  |     for (auto it : render->client_properties_) { | ||||||
|  |       auto props = it.second; | ||||||
|  |       if (props->connection_status_ == ConnectionStatus::Connected) { | ||||||
|  |         SendAudioFrame(props->peer_, (const char *)stream, len, | ||||||
|  |                        render->audio_label_.c_str()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |   } else { | ||||||
|  |     memcpy(render->audio_buffer_, stream, len); | ||||||
|  |     render->audio_len_ = len; | ||||||
|  |     SDL_Delay(10); | ||||||
|  |     render->audio_buffer_fresh_ = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::SdlCaptureAudioOut([[maybe_unused]] void *userdata, | ||||||
|  |                                 [[maybe_unused]] Uint8 *stream, | ||||||
|  |                                 [[maybe_unused]] int len) { | ||||||
|  |   // Render *render = (Render *)userdata; | ||||||
|  |   // for (auto it : render->client_properties_) { | ||||||
|  |   //   auto props = it.second; | ||||||
|  |   //   if (props->connection_status_ == SignalStatus::SignalConnected) { | ||||||
|  |   //     SendAudioFrame(props->peer_, (const char *)stream, len); | ||||||
|  |   //   } | ||||||
|  |   // } | ||||||
|  |  | ||||||
|  |   // if (!render->audio_buffer_fresh_) { | ||||||
|  |   //   return; | ||||||
|  |   // } | ||||||
|  |  | ||||||
|  |   // SDL_memset(stream, 0, len); | ||||||
|  |  | ||||||
|  |   // if (render->audio_len_ == 0) { | ||||||
|  |   //   return; | ||||||
|  |   // } else { | ||||||
|  |   // } | ||||||
|  |  | ||||||
|  |   // len = (len > render->audio_len_ ? render->audio_len_ : len); | ||||||
|  |   // SDL_MixAudioFormat(stream, render->audio_buffer_, AUDIO_S16LSB, len, | ||||||
|  |   //                    SDL_MIX_MAXVOLUME); | ||||||
|  |   // render->audio_buffer_fresh_ = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::OnReceiveVideoBufferCb(const XVideoFrame *video_frame, | ||||||
|  |                                     const char *user_id, size_t user_id_size, | ||||||
|  |                                     void *user_data) { | ||||||
|  |   Render *render = (Render *)user_data; | ||||||
|  |   if (!render) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string remote_id(user_id, user_id_size); | ||||||
|  |   if (render->client_properties_.find(remote_id) == | ||||||
|  |       render->client_properties_.end()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   SubStreamWindowProperties *props = | ||||||
|  |       render->client_properties_.find(remote_id)->second.get(); | ||||||
|  |  | ||||||
|  |   if (props->connection_established_) { | ||||||
|  |     if (!props->dst_buffer_) { | ||||||
|  |       props->dst_buffer_capacity_ = video_frame->size; | ||||||
|  |       props->dst_buffer_ = new unsigned char[video_frame->size]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (props->dst_buffer_capacity_ < video_frame->size) { | ||||||
|  |       delete props->dst_buffer_; | ||||||
|  |       props->dst_buffer_capacity_ = video_frame->size; | ||||||
|  |       props->dst_buffer_ = new unsigned char[video_frame->size]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     memcpy(props->dst_buffer_, video_frame->data, video_frame->size); | ||||||
|  |     bool need_to_update_render_rect = false; | ||||||
|  |     if (props->video_width_ != props->video_width_last_ || | ||||||
|  |         props->video_height_ != props->video_height_last_) { | ||||||
|  |       need_to_update_render_rect = true; | ||||||
|  |       props->video_width_last_ = props->video_width_; | ||||||
|  |       props->video_height_last_ = props->video_height_; | ||||||
|  |     } | ||||||
|  |     props->video_width_ = video_frame->width; | ||||||
|  |     props->video_height_ = video_frame->height; | ||||||
|  |     props->video_size_ = video_frame->size; | ||||||
|  |  | ||||||
|  |     if (need_to_update_render_rect) { | ||||||
|  |       render->UpdateRenderRect(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SDL_Event event; | ||||||
|  |     event.type = STREAM_FRASH; | ||||||
|  |     event.user.type = STREAM_FRASH; | ||||||
|  |     event.user.data1 = props; | ||||||
|  |     SDL_PushEvent(&event); | ||||||
|  |     props->streaming_ = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::OnReceiveAudioBufferCb(const char *data, size_t size, | ||||||
|  |                                     const char *user_id, size_t user_id_size, | ||||||
|  |                                     void *user_data) { | ||||||
|  |   Render *render = (Render *)user_data; | ||||||
|  |   if (!render) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   render->audio_buffer_fresh_ = true; | ||||||
|  |   SDL_QueueAudio(render->output_dev_, data, (uint32_t)size); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::OnReceiveDataBufferCb(const char *data, size_t size, | ||||||
|  |                                    const char *user_id, size_t user_id_size, | ||||||
|  |                                    void *user_data) { | ||||||
|  |   Render *render = (Render *)user_data; | ||||||
|  |   if (!render) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   RemoteAction remote_action; | ||||||
|  |   memcpy(&remote_action, data, size); | ||||||
|  |  | ||||||
|  |   std::string remote_id(user_id, user_id_size); | ||||||
|  |   if (render->client_properties_.find(remote_id) != | ||||||
|  |       render->client_properties_.end()) { | ||||||
|  |     // local | ||||||
|  |     auto props = render->client_properties_.find(remote_id)->second; | ||||||
|  |     RemoteAction host_info; | ||||||
|  |     if (DeserializeRemoteAction(data, size, host_info)) { | ||||||
|  |       if (ControlType::host_infomation == host_info.type && | ||||||
|  |           props->remote_host_name_.empty()) { | ||||||
|  |         props->remote_host_name_ = | ||||||
|  |             std::string(host_info.i.host_name, host_info.i.host_name_size); | ||||||
|  |         LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); | ||||||
|  |  | ||||||
|  |         for (int i = 0; i < host_info.i.display_num; i++) { | ||||||
|  |           props->display_info_list_.push_back(DisplayInfo( | ||||||
|  |               std::string(host_info.i.display_list[i]), host_info.i.left[i], | ||||||
|  |               host_info.i.top[i], host_info.i.right[i], host_info.i.bottom[i])); | ||||||
|  |           LOG_INFO("Remote display [{}:{}], bound [({}, {}) ({}, {})]", i + 1, | ||||||
|  |                    props->display_info_list_[i].name, | ||||||
|  |                    props->display_info_list_[i].left, | ||||||
|  |                    props->display_info_list_[i].top, | ||||||
|  |                    props->display_info_list_[i].right, | ||||||
|  |                    props->display_info_list_[i].bottom); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       props->remote_host_name_ = std::string(remote_action.i.host_name, | ||||||
|  |                                              remote_action.i.host_name_size); | ||||||
|  |       LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); | ||||||
|  |       LOG_ERROR("No remote display detected"); | ||||||
|  |     } | ||||||
|  |     FreeRemoteAction(host_info); | ||||||
|  |   } else { | ||||||
|  |     // remote | ||||||
|  |     if (ControlType::mouse == remote_action.type && render->mouse_controller_) { | ||||||
|  |       render->mouse_controller_->SendMouseCommand(remote_action, | ||||||
|  |                                                   render->selected_display_); | ||||||
|  |     } else if (ControlType::audio_capture == remote_action.type) { | ||||||
|  |       if (remote_action.a) { | ||||||
|  |         render->StartSpeakerCapturer(); | ||||||
|  |         render->audio_capture_ = true; | ||||||
|  |       } else { | ||||||
|  |         render->StopSpeakerCapturer(); | ||||||
|  |         render->audio_capture_ = false; | ||||||
|  |       } | ||||||
|  |     } else if (ControlType::keyboard == remote_action.type && | ||||||
|  |                render->keyboard_capturer_) { | ||||||
|  |       render->keyboard_capturer_->SendKeyboardCommand( | ||||||
|  |           (int)remote_action.k.key_value, | ||||||
|  |           remote_action.k.flag == KeyFlag::key_down); | ||||||
|  |     } else if (ControlType::display_id == remote_action.type) { | ||||||
|  |       if (render->screen_capturer_) { | ||||||
|  |         render->selected_display_ = remote_action.d; | ||||||
|  |         render->screen_capturer_->SwitchTo(remote_action.d); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::OnSignalStatusCb(SignalStatus status, const char *user_id, | ||||||
|  |                               size_t user_id_size, void *user_data) { | ||||||
|  |   Render *render = (Render *)user_data; | ||||||
|  |   if (!render) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string client_id(user_id, user_id_size); | ||||||
|  |   if (client_id == render->client_id_) { | ||||||
|  |     render->signal_status_ = status; | ||||||
|  |     if (SignalStatus::SignalConnecting == status) { | ||||||
|  |       render->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalConnected == status) { | ||||||
|  |       render->signal_connected_ = true; | ||||||
|  |       LOG_INFO("[{}] connected to signal server", client_id); | ||||||
|  |     } else if (SignalStatus::SignalFailed == status) { | ||||||
|  |       render->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalClosed == status) { | ||||||
|  |       render->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalReconnecting == status) { | ||||||
|  |       render->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalServerClosed == status) { | ||||||
|  |       render->signal_connected_ = false; | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     if (client_id.rfind("C-", 0) != 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::string remote_id(client_id.begin() + 2, client_id.end()); | ||||||
|  |     if (render->client_properties_.find(remote_id) == | ||||||
|  |         render->client_properties_.end()) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     auto props = render->client_properties_.find(remote_id)->second; | ||||||
|  |     props->signal_status_ = status; | ||||||
|  |     if (SignalStatus::SignalConnecting == status) { | ||||||
|  |       props->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalConnected == status) { | ||||||
|  |       props->signal_connected_ = true; | ||||||
|  |       LOG_INFO("[{}] connected to signal server", remote_id); | ||||||
|  |     } else if (SignalStatus::SignalFailed == status) { | ||||||
|  |       props->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalClosed == status) { | ||||||
|  |       props->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalReconnecting == status) { | ||||||
|  |       props->signal_connected_ = false; | ||||||
|  |     } else if (SignalStatus::SignalServerClosed == status) { | ||||||
|  |       props->signal_connected_ = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::OnConnectionStatusCb(ConnectionStatus status, const char *user_id, | ||||||
|  |                                   const size_t user_id_size, void *user_data) { | ||||||
|  |   Render *render = (Render *)user_data; | ||||||
|  |   if (!render) return; | ||||||
|  |  | ||||||
|  |   std::string remote_id(user_id, user_id_size); | ||||||
|  |   auto it = render->client_properties_.find(remote_id); | ||||||
|  |   auto props = (it != render->client_properties_.end()) ? it->second : nullptr; | ||||||
|  |  | ||||||
|  |   if (props) { | ||||||
|  |     render->is_client_mode_ = true; | ||||||
|  |     render->show_connection_status_window_ = true; | ||||||
|  |     props->connection_status_ = status; | ||||||
|  |  | ||||||
|  |     switch (status) { | ||||||
|  |       case ConnectionStatus::Connected: | ||||||
|  |         if (!render->need_to_create_stream_window_ && | ||||||
|  |             !render->client_properties_.empty()) { | ||||||
|  |           render->need_to_create_stream_window_ = true; | ||||||
|  |         } | ||||||
|  |         props->connection_established_ = true; | ||||||
|  |         props->stream_render_rect_ = { | ||||||
|  |             0, (int)render->title_bar_height_, | ||||||
|  |             (int)render->stream_window_width_, | ||||||
|  |             (int)(render->stream_window_height_ - render->title_bar_height_)}; | ||||||
|  |         break; | ||||||
|  |       case ConnectionStatus::Disconnected: | ||||||
|  |       case ConnectionStatus::Failed: | ||||||
|  |       case ConnectionStatus::Closed: | ||||||
|  |         render->password_validating_time_ = 0; | ||||||
|  |         render->start_screen_capturer_ = false; | ||||||
|  |         render->start_mouse_controller_ = false; | ||||||
|  |         render->start_keyboard_capturer_ = false; | ||||||
|  |         render->control_mouse_ = false; | ||||||
|  |         props->connection_established_ = false; | ||||||
|  |         props->mouse_control_button_pressed_ = false; | ||||||
|  |         if (props->dst_buffer_) { | ||||||
|  |           memset(props->dst_buffer_, 0, props->dst_buffer_capacity_); | ||||||
|  |           SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_, | ||||||
|  |                             props->texture_width_); | ||||||
|  |         } | ||||||
|  |         render->CleanSubStreamWindowProperties(props); | ||||||
|  |         break; | ||||||
|  |       case ConnectionStatus::IncorrectPassword: | ||||||
|  |         render->password_validating_ = false; | ||||||
|  |         render->password_validating_time_++; | ||||||
|  |         if (render->connect_button_pressed_) { | ||||||
|  |           render->connect_button_pressed_ = false; | ||||||
|  |           props->connection_established_ = false; | ||||||
|  |           render->connect_button_label_ = | ||||||
|  |               localization::connect[render->localization_language_index_]; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case ConnectionStatus::NoSuchTransmissionId: | ||||||
|  |         if (render->connect_button_pressed_) { | ||||||
|  |           props->connection_established_ = false; | ||||||
|  |           render->connect_button_label_ = | ||||||
|  |               localization::connect[render->localization_language_index_]; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     render->is_client_mode_ = false; | ||||||
|  |     render->show_connection_status_window_ = true; | ||||||
|  |  | ||||||
|  |     switch (status) { | ||||||
|  |       case ConnectionStatus::Connected: | ||||||
|  |         render->need_to_send_host_info_ = true; | ||||||
|  |         render->start_screen_capturer_ = true; | ||||||
|  |         render->start_mouse_controller_ = true; | ||||||
|  |         break; | ||||||
|  |       case ConnectionStatus::Closed: | ||||||
|  |         render->start_screen_capturer_ = false; | ||||||
|  |         render->start_mouse_controller_ = false; | ||||||
|  |         render->start_keyboard_capturer_ = false; | ||||||
|  |         render->need_to_send_host_info_ = false; | ||||||
|  |         if (props) props->connection_established_ = false; | ||||||
|  |         if (render->audio_capture_) { | ||||||
|  |           render->StopSpeakerCapturer(); | ||||||
|  |           render->audio_capture_ = false; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::NetStatusReport(const char *client_id, size_t client_id_size, | ||||||
|  |                              TraversalMode mode, | ||||||
|  |                              const XNetTrafficStats *net_traffic_stats, | ||||||
|  |                              const char *user_id, const size_t user_id_size, | ||||||
|  |                              void *user_data) { | ||||||
|  |   Render *render = (Render *)user_data; | ||||||
|  |   if (!render) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) { | ||||||
|  |     std::string id, password; | ||||||
|  |     const char *at_pos = strchr(client_id, '@'); | ||||||
|  |     if (at_pos == nullptr) { | ||||||
|  |       id = client_id; | ||||||
|  |       password.clear(); | ||||||
|  |     } else { | ||||||
|  |       id.assign(client_id, at_pos - client_id); | ||||||
|  |       password = at_pos + 1; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     memset(&render->client_id_, 0, sizeof(render->client_id_)); | ||||||
|  |     strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1); | ||||||
|  |     render->client_id_[sizeof(render->client_id_) - 1] = '\0'; | ||||||
|  |  | ||||||
|  |     memset(&render->password_saved_, 0, sizeof(render->password_saved_)); | ||||||
|  |     strncpy(render->password_saved_, password.c_str(), | ||||||
|  |             sizeof(render->password_saved_) - 1); | ||||||
|  |     render->password_saved_[sizeof(render->password_saved_) - 1] = '\0'; | ||||||
|  |  | ||||||
|  |     memset(&render->client_id_with_password_, 0, | ||||||
|  |            sizeof(render->client_id_with_password_)); | ||||||
|  |     strncpy(render->client_id_with_password_, client_id, | ||||||
|  |             sizeof(render->client_id_with_password_) - 1); | ||||||
|  |     render->client_id_with_password_[sizeof(render->client_id_with_password_) - | ||||||
|  |                                      1] = '\0'; | ||||||
|  |  | ||||||
|  |     LOG_INFO("Use client id [{}] and save id into cache file", id); | ||||||
|  |     render->SaveSettingsIntoCacheFile(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string remote_id(user_id, user_id_size); | ||||||
|  |   if (render->client_properties_.find(remote_id) == | ||||||
|  |       render->client_properties_.end()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   auto props = render->client_properties_.find(remote_id)->second; | ||||||
|  |   if (props->traversal_mode_ != mode) { | ||||||
|  |     props->traversal_mode_ = mode; | ||||||
|  |     LOG_INFO("Net mode: [{}]", int(props->traversal_mode_)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!net_traffic_stats) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // only display client side net status if connected to itself | ||||||
|  |   if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) { | ||||||
|  |     props->net_traffic_stats_ = *net_traffic_stats; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										283
									
								
								src/single_window/setting_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								src/single_window/setting_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,283 @@ | |||||||
|  | #include "layout_style.h" | ||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::SettingWindow() { | ||||||
|  |   if (show_settings_window_) { | ||||||
|  |     if (settings_window_pos_reset_) { | ||||||
|  |       const ImGuiViewport *viewport = ImGui::GetMainViewport(); | ||||||
|  |       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |         ImGui::SetNextWindowPos( | ||||||
|  |             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||||
|  |                     SETTINGS_WINDOW_WIDTH_CN) / | ||||||
|  |                        2, | ||||||
|  |                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||||
|  |                     SETTINGS_WINDOW_HEIGHT_CN) / | ||||||
|  |                        2)); | ||||||
|  |  | ||||||
|  |         ImGui::SetNextWindowSize( | ||||||
|  |             ImVec2(SETTINGS_WINDOW_WIDTH_CN, SETTINGS_WINDOW_HEIGHT_CN)); | ||||||
|  |       } else { | ||||||
|  |         ImGui::SetNextWindowPos( | ||||||
|  |             ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - | ||||||
|  |                     SETTINGS_WINDOW_WIDTH_EN) / | ||||||
|  |                        2, | ||||||
|  |                    (viewport->WorkSize.y - viewport->WorkPos.y - | ||||||
|  |                     SETTINGS_WINDOW_HEIGHT_EN) / | ||||||
|  |                        2)); | ||||||
|  |  | ||||||
|  |         ImGui::SetNextWindowSize( | ||||||
|  |             ImVec2(SETTINGS_WINDOW_WIDTH_EN, SETTINGS_WINDOW_HEIGHT_EN)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       settings_window_pos_reset_ = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Settings | ||||||
|  |     { | ||||||
|  |       ImGui::SetWindowFontScale(0.5f); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); | ||||||
|  |  | ||||||
|  |       ImGui::Begin(localization::settings[localization_language_index_].c_str(), | ||||||
|  |                    nullptr, | ||||||
|  |                    ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | | ||||||
|  |                        ImGuiWindowFlags_NoSavedSettings); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::SetWindowFontScale(0.5f); | ||||||
|  |       ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); | ||||||
|  |       { | ||||||
|  |         const char *language_items[] = { | ||||||
|  |             localization::language_zh[localization_language_index_].c_str(), | ||||||
|  |             localization::language_en[localization_language_index_].c_str()}; | ||||||
|  |  | ||||||
|  |         ImGui::SetCursorPosY(32); | ||||||
|  |         ImGui::Text( | ||||||
|  |             "%s", localization::language[localization_language_index_].c_str()); | ||||||
|  |         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |           ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_CN); | ||||||
|  |         } else { | ||||||
|  |           ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN); | ||||||
|  |         } | ||||||
|  |         ImGui::SetCursorPosY(30); | ||||||
|  |         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||||
|  |  | ||||||
|  |         ImGui::Combo("##language", &language_button_value_, language_items, | ||||||
|  |                      IM_ARRAYSIZE(language_items)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::Separator(); | ||||||
|  |  | ||||||
|  |       if (stream_window_inited_) { | ||||||
|  |         ImGui::BeginDisabled(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       { | ||||||
|  |         const char *video_quality_items[] = { | ||||||
|  |             localization::video_quality_high[localization_language_index_] | ||||||
|  |                 .c_str(), | ||||||
|  |             localization::video_quality_medium[localization_language_index_] | ||||||
|  |                 .c_str(), | ||||||
|  |             localization::video_quality_low[localization_language_index_] | ||||||
|  |                 .c_str()}; | ||||||
|  |  | ||||||
|  |         ImGui::SetCursorPosY(62); | ||||||
|  |         ImGui::Text( | ||||||
|  |             "%s", | ||||||
|  |             localization::video_quality[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |           ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN); | ||||||
|  |         } else { | ||||||
|  |           ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN); | ||||||
|  |         } | ||||||
|  |         ImGui::SetCursorPosY(60); | ||||||
|  |         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||||
|  |  | ||||||
|  |         ImGui::Combo("##video_quality", &video_quality_button_value_, | ||||||
|  |                      video_quality_items, IM_ARRAYSIZE(video_quality_items)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::Separator(); | ||||||
|  |  | ||||||
|  |       { | ||||||
|  |         const char *video_encode_format_items[] = { | ||||||
|  |             localization::av1[localization_language_index_].c_str(), | ||||||
|  |             localization::h264[localization_language_index_].c_str()}; | ||||||
|  |  | ||||||
|  |         ImGui::SetCursorPosY(92); | ||||||
|  |         ImGui::Text( | ||||||
|  |             "%s", | ||||||
|  |             localization::video_encode_format[localization_language_index_] | ||||||
|  |                 .c_str()); | ||||||
|  |  | ||||||
|  |         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |           ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN); | ||||||
|  |         } else { | ||||||
|  |           ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN); | ||||||
|  |         } | ||||||
|  |         ImGui::SetCursorPosY(90); | ||||||
|  |         ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH); | ||||||
|  |  | ||||||
|  |         ImGui::Combo( | ||||||
|  |             "##video_encode_format", &video_encode_format_button_value_, | ||||||
|  |             video_encode_format_items, IM_ARRAYSIZE(video_encode_format_items)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::Separator(); | ||||||
|  |  | ||||||
|  |       { | ||||||
|  |         ImGui::SetCursorPosY(122); | ||||||
|  |         ImGui::Text("%s", localization::enable_hardware_video_codec | ||||||
|  |                               [localization_language_index_] | ||||||
|  |                                   .c_str()); | ||||||
|  |  | ||||||
|  |         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |           ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN); | ||||||
|  |         } else { | ||||||
|  |           ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN); | ||||||
|  |         } | ||||||
|  |         ImGui::SetCursorPosY(120); | ||||||
|  |         ImGui::Checkbox("##enable_hardware_video_codec", | ||||||
|  |                         &enable_hardware_video_codec_); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::Separator(); | ||||||
|  |  | ||||||
|  |       { | ||||||
|  |         ImGui::SetCursorPosY(152); | ||||||
|  |         ImGui::Text( | ||||||
|  |             "%s", | ||||||
|  |             localization::enable_turn[localization_language_index_].c_str()); | ||||||
|  |  | ||||||
|  |         if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |           ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_CN); | ||||||
|  |         } else { | ||||||
|  |           ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_EN); | ||||||
|  |         } | ||||||
|  |         ImGui::SetCursorPosY(150); | ||||||
|  |         ImGui::Checkbox("##enable_turn", &enable_turn_); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (stream_window_inited_) { | ||||||
|  |         ImGui::EndDisabled(); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) { | ||||||
|  |         ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_CN); | ||||||
|  |       } else { | ||||||
|  |         ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN); | ||||||
|  |       } | ||||||
|  |       ImGui::SetCursorPosY(190.0f); | ||||||
|  |       ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |       // OK | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::ok[localization_language_index_].c_str())) { | ||||||
|  |         show_settings_window_ = false; | ||||||
|  |  | ||||||
|  |         // Language | ||||||
|  |         if (language_button_value_ == 0) { | ||||||
|  |           config_center_.SetLanguage(ConfigCenter::LANGUAGE::CHINESE); | ||||||
|  |         } else { | ||||||
|  |           config_center_.SetLanguage(ConfigCenter::LANGUAGE::ENGLISH); | ||||||
|  |         } | ||||||
|  |         language_button_value_last_ = language_button_value_; | ||||||
|  |         localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_; | ||||||
|  |         localization_language_index_ = language_button_value_; | ||||||
|  |         LOG_INFO("Set localization language: {}", | ||||||
|  |                  localization_language_index_ == 0 ? "zh" : "en"); | ||||||
|  |  | ||||||
|  |         // Video quality | ||||||
|  |         if (video_quality_button_value_ == 0) { | ||||||
|  |           config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH); | ||||||
|  |         } else if (video_quality_button_value_ == 1) { | ||||||
|  |           config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM); | ||||||
|  |         } else { | ||||||
|  |           config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW); | ||||||
|  |         } | ||||||
|  |         video_quality_button_value_last_ = video_quality_button_value_; | ||||||
|  |  | ||||||
|  |         // Video encode format | ||||||
|  |         if (video_encode_format_button_value_ == 0) { | ||||||
|  |           config_center_.SetVideoEncodeFormat( | ||||||
|  |               ConfigCenter::VIDEO_ENCODE_FORMAT::AV1); | ||||||
|  |         } else if (video_encode_format_button_value_ == 1) { | ||||||
|  |           config_center_.SetVideoEncodeFormat( | ||||||
|  |               ConfigCenter::VIDEO_ENCODE_FORMAT::H264); | ||||||
|  |         } | ||||||
|  |         video_encode_format_button_value_last_ = | ||||||
|  |             video_encode_format_button_value_; | ||||||
|  |  | ||||||
|  |         // Hardware video codec | ||||||
|  |         if (enable_hardware_video_codec_) { | ||||||
|  |           config_center_.SetHardwareVideoCodec(true); | ||||||
|  |         } else { | ||||||
|  |           config_center_.SetHardwareVideoCodec(false); | ||||||
|  |         } | ||||||
|  |         enable_hardware_video_codec_last_ = enable_hardware_video_codec_; | ||||||
|  |  | ||||||
|  |         // TURN mode | ||||||
|  |         if (enable_turn_) { | ||||||
|  |           config_center_.SetTurn(true); | ||||||
|  |         } else { | ||||||
|  |           config_center_.SetTurn(false); | ||||||
|  |         } | ||||||
|  |         enable_turn_last_ = enable_turn_; | ||||||
|  |  | ||||||
|  |         SaveSettingsIntoCacheFile(); | ||||||
|  |         settings_window_pos_reset_ = true; | ||||||
|  |  | ||||||
|  |         // Recreate peer instance | ||||||
|  |         LoadSettingsFromCacheFile(); | ||||||
|  |  | ||||||
|  |         // Recreate peer instance | ||||||
|  |         if (!stream_window_inited_) { | ||||||
|  |           LOG_INFO("Recreate peer instance"); | ||||||
|  |           DestroyPeer(&peer_); | ||||||
|  |           CreateConnectionPeer(); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::SameLine(); | ||||||
|  |       // Cancel | ||||||
|  |       if (ImGui::Button( | ||||||
|  |               localization::cancel[localization_language_index_].c_str())) { | ||||||
|  |         show_settings_window_ = false; | ||||||
|  |         if (language_button_value_ != language_button_value_last_) { | ||||||
|  |           language_button_value_ = language_button_value_last_; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (video_quality_button_value_ != video_quality_button_value_last_) { | ||||||
|  |           video_quality_button_value_ = video_quality_button_value_last_; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (video_encode_format_button_value_ != | ||||||
|  |             video_encode_format_button_value_last_) { | ||||||
|  |           video_encode_format_button_value_ = | ||||||
|  |               video_encode_format_button_value_last_; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (enable_hardware_video_codec_ != enable_hardware_video_codec_last_) { | ||||||
|  |           enable_hardware_video_codec_ = enable_hardware_video_codec_last_; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (enable_turn_ != enable_turn_last_) { | ||||||
|  |           enable_turn_ = enable_turn_last_; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         settings_window_pos_reset_ = true; | ||||||
|  |       } | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |       ImGui::SetWindowFontScale(0.5f); | ||||||
|  |       ImGui::End(); | ||||||
|  |       ImGui::PopStyleVar(2); | ||||||
|  |       ImGui::PopStyleColor(); | ||||||
|  |       ImGui::SetWindowFontScale(1.0f); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/single_window/status_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/single_window/status_bar.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | int Render::StatusBar() { | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   static bool a, b, c, d, e; | ||||||
|  |   ImGui::SetNextWindowPos( | ||||||
|  |       ImVec2(0, main_window_height_default_ - status_bar_height_ - 1), | ||||||
|  |       ImGuiCond_Always); | ||||||
|  |  | ||||||
|  |   ImGui::BeginChild( | ||||||
|  |       "StatusBar", ImVec2(main_window_width_, status_bar_height_ + 1), | ||||||
|  |       ImGuiChildFlags_Border, | ||||||
|  |       ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |  | ||||||
|  |   ImVec2 dot_pos = | ||||||
|  |       ImVec2(13, main_window_height_default_ - status_bar_height_ + 11.0f); | ||||||
|  |   ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||||
|  |   draw_list->AddCircleFilled(dot_pos, 5.0f, | ||||||
|  |                              ImColor(signal_connected_ ? 0.0f : 1.0f, | ||||||
|  |                                      signal_connected_ ? 1.0f : 0.0f, 0.0f), | ||||||
|  |                              100); | ||||||
|  |   draw_list->AddCircle(dot_pos, 6.0f, ImColor(1.0f, 1.0f, 1.0f), 100); | ||||||
|  |  | ||||||
|  |   ImGui::SetWindowFontScale(0.6f); | ||||||
|  |   draw_list->AddText( | ||||||
|  |       ImVec2(25, main_window_height_default_ - status_bar_height_ + 3.0f), | ||||||
|  |       ImColor(0.0f, 0.0f, 0.0f), | ||||||
|  |       signal_connected_ | ||||||
|  |           ? localization::signal_connected[localization_language_index_].c_str() | ||||||
|  |           : localization::signal_disconnected[localization_language_index_] | ||||||
|  |                 .c_str()); | ||||||
|  |   ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										8422
									
								
								src/single_window/stb_image.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8422
									
								
								src/single_window/stb_image.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2020
									
								
								src/single_window/stb_image_write.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2020
									
								
								src/single_window/stb_image_write.h
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										197
									
								
								src/single_window/stream_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/single_window/stream_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | void Render::DrawConnectionStatusText( | ||||||
|  |     std::shared_ptr<SubStreamWindowProperties>& props) { | ||||||
|  |   std::string text; | ||||||
|  |   switch (props->connection_status_) { | ||||||
|  |     case ConnectionStatus::Disconnected: | ||||||
|  |       text = localization::p2p_disconnected[localization_language_index_]; | ||||||
|  |       break; | ||||||
|  |     case ConnectionStatus::Failed: | ||||||
|  |       text = localization::p2p_failed[localization_language_index_]; | ||||||
|  |       break; | ||||||
|  |     case ConnectionStatus::Closed: | ||||||
|  |       text = localization::p2p_closed[localization_language_index_]; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!text.empty()) { | ||||||
|  |     ImVec2 size = ImGui::GetWindowSize(); | ||||||
|  |     ImVec2 text_size = ImGui::CalcTextSize(text.c_str()); | ||||||
|  |     ImGui::SetCursorPos( | ||||||
|  |         ImVec2((size.x - text_size.x) * 0.5f, | ||||||
|  |                (size.y - text_size.y - title_bar_height_) * 0.5f)); | ||||||
|  |     ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Render::CloseTab(decltype(client_properties_)::iterator& it) { | ||||||
|  |   CleanupPeer(it->second); | ||||||
|  |   it = client_properties_.erase(it); | ||||||
|  |   if (client_properties_.empty()) { | ||||||
|  |     SDL_Event event; | ||||||
|  |     event.type = SDL_QUIT; | ||||||
|  |     SDL_PushEvent(&event); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Render::StreamWindow() { | ||||||
|  |   ImGui::SetNextWindowPos( | ||||||
|  |       ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_), | ||||||
|  |       ImGuiCond_Always); | ||||||
|  |   ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_), | ||||||
|  |                            ImGuiCond_Always); | ||||||
|  |   ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); | ||||||
|  |   ImGui::Begin("VideoBg", nullptr, | ||||||
|  |                ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | | ||||||
|  |                    ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||||
|  |                    ImGuiWindowFlags_NoDocking); | ||||||
|  |   ImGui::PopStyleColor(2); | ||||||
|  |   ImGui::PopStyleVar(); | ||||||
|  |  | ||||||
|  |   ImGuiWindowFlags stream_window_flag = | ||||||
|  |       ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration | | ||||||
|  |       ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove; | ||||||
|  |  | ||||||
|  |   if (!fullscreen_button_pressed_) { | ||||||
|  |     ImGui::SetNextWindowPos(ImVec2(20, 0), ImGuiCond_Always); | ||||||
|  |     ImGui::SetNextWindowSize(ImVec2(0, 20), ImGuiCond_Always); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 8.0f)); | ||||||
|  |     ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f)); | ||||||
|  |     ImGui::Begin("TabBar", nullptr, | ||||||
|  |                  ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | | ||||||
|  |                      ImGuiWindowFlags_NoBringToFrontOnFocus | | ||||||
|  |                      ImGuiWindowFlags_NoDocking); | ||||||
|  |     ImGui::PopStyleColor(); | ||||||
|  |     ImGui::PopStyleVar(2); | ||||||
|  |  | ||||||
|  |     if (ImGui::BeginTabBar("StreamTabBar", | ||||||
|  |                            ImGuiTabBarFlags_Reorderable | | ||||||
|  |                                ImGuiTabBarFlags_AutoSelectNewTabs)) { | ||||||
|  |       is_tab_bar_hovered_ = ImGui::IsWindowHovered(); | ||||||
|  |  | ||||||
|  |       for (auto it = client_properties_.begin(); | ||||||
|  |            it != client_properties_.end();) { | ||||||
|  |         auto& props = it->second; | ||||||
|  |         if (!props->tab_opened_) { | ||||||
|  |           CloseTab(it); | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui::SetWindowFontScale(0.6f); | ||||||
|  |         if (ImGui::BeginTabItem(props->remote_id_.c_str(), | ||||||
|  |                                 &props->tab_opened_)) { | ||||||
|  |           props->tab_selected_ = true; | ||||||
|  |           ImGui::SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |           ImGui::SetNextWindowSize( | ||||||
|  |               ImVec2(stream_window_width_, stream_window_height_), | ||||||
|  |               ImGuiCond_Always); | ||||||
|  |           ImGui::SetNextWindowPos( | ||||||
|  |               ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_), | ||||||
|  |               ImGuiCond_Always); | ||||||
|  |           ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||||
|  |           ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||||
|  |           ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f)); | ||||||
|  |           ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag); | ||||||
|  |           ImGui::PopStyleColor(); | ||||||
|  |           ImGui::PopStyleVar(2); | ||||||
|  |  | ||||||
|  |           ImVec2 pos = ImGui::GetWindowPos(); | ||||||
|  |           ImVec2 size = ImGui::GetWindowSize(); | ||||||
|  |           props->render_window_x_ = pos.x; | ||||||
|  |           props->render_window_y_ = pos.y; | ||||||
|  |           props->render_window_width_ = size.x; | ||||||
|  |           props->render_window_height_ = size.y; | ||||||
|  |           UpdateRenderRect(); | ||||||
|  |  | ||||||
|  |           ControlWindow(props); | ||||||
|  |  | ||||||
|  |           if (!props->peer_) { | ||||||
|  |             it = client_properties_.erase(it); | ||||||
|  |             if (client_properties_.empty()) { | ||||||
|  |               SDL_Event event; | ||||||
|  |               event.type = SDL_QUIT; | ||||||
|  |               SDL_PushEvent(&event); | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             DrawConnectionStatusText(props); | ||||||
|  |             ++it; | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           ImGui::End(); | ||||||
|  |           ImGui::EndTabItem(); | ||||||
|  |         } else { | ||||||
|  |           props->tab_selected_ = false; | ||||||
|  |           ImGui::SetWindowFontScale(1.0f); | ||||||
|  |           ++it; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       ImGui::EndTabBar(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::End();  // End TabBar | ||||||
|  |   } else { | ||||||
|  |     for (auto it = client_properties_.begin(); | ||||||
|  |          it != client_properties_.end();) { | ||||||
|  |       auto& props = it->second; | ||||||
|  |       if (!props->tab_opened_) { | ||||||
|  |         CloseTab(it); | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (props->tab_selected_) { | ||||||
|  |         ImGui::SetNextWindowSize( | ||||||
|  |             ImVec2(stream_window_width_, stream_window_height_), | ||||||
|  |             ImGuiCond_Always); | ||||||
|  |         ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); | ||||||
|  |         ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); | ||||||
|  |         ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f)); | ||||||
|  |         ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag); | ||||||
|  |         ImGui::PopStyleColor(); | ||||||
|  |         ImGui::PopStyleVar(2); | ||||||
|  |  | ||||||
|  |         ImVec2 pos = ImGui::GetWindowPos(); | ||||||
|  |         ImVec2 size = ImGui::GetWindowSize(); | ||||||
|  |         props->render_window_x_ = pos.x; | ||||||
|  |         props->render_window_y_ = pos.y; | ||||||
|  |         props->render_window_width_ = size.x; | ||||||
|  |         props->render_window_height_ = size.y; | ||||||
|  |         UpdateRenderRect(); | ||||||
|  |  | ||||||
|  |         ControlWindow(props); | ||||||
|  |         ImGui::End(); | ||||||
|  |  | ||||||
|  |         if (!props->peer_) { | ||||||
|  |           fullscreen_button_pressed_ = false; | ||||||
|  |           SDL_SetWindowFullscreen(stream_window_, SDL_FALSE); | ||||||
|  |           it = client_properties_.erase(it); | ||||||
|  |           if (client_properties_.empty()) { | ||||||
|  |             SDL_Event event; | ||||||
|  |             event.type = SDL_QUIT; | ||||||
|  |             SDL_PushEvent(&event); | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           DrawConnectionStatusText(props); | ||||||
|  |           ++it; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         ++it; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // UpdateRenderRect(); | ||||||
|  |   ImGui::End();  // End VideoBg | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										431
									
								
								src/single_window/thumbnail.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										431
									
								
								src/single_window/thumbnail.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,431 @@ | |||||||
|  | #include "thumbnail.h" | ||||||
|  |  | ||||||
|  | #include <openssl/aes.h> | ||||||
|  | #include <openssl/crypto.h> | ||||||
|  | #include <openssl/evp.h> | ||||||
|  | #include <openssl/rand.h> | ||||||
|  |  | ||||||
|  | #include <chrono> | ||||||
|  | #include <fstream> | ||||||
|  | #include <iomanip> | ||||||
|  | #include <iostream> | ||||||
|  | #include <map> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "libyuv.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | #define STB_IMAGE_IMPLEMENTATION | ||||||
|  | #include "stb_image.h" | ||||||
|  | #define STB_IMAGE_WRITE_IMPLEMENTATION | ||||||
|  | #include "stb_image_write.h" | ||||||
|  |  | ||||||
|  | static std::string test; | ||||||
|  |  | ||||||
|  | bool LoadTextureFromMemory(const void* data, size_t data_size, | ||||||
|  |                            SDL_Renderer* renderer, SDL_Texture** out_texture, | ||||||
|  |                            int* out_width, int* out_height) { | ||||||
|  |   int image_width = 0; | ||||||
|  |   int image_height = 0; | ||||||
|  |   int channels = 4; | ||||||
|  |   unsigned char* image_data = | ||||||
|  |       stbi_load_from_memory((const unsigned char*)data, (int)data_size, | ||||||
|  |                             &image_width, &image_height, NULL, 4); | ||||||
|  |   if (image_data == nullptr) { | ||||||
|  |     LOG_ERROR("Failed to load image: [{}]", stbi_failure_reason()); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ABGR | ||||||
|  |   SDL_Surface* surface = SDL_CreateRGBSurfaceFrom( | ||||||
|  |       (void*)image_data, image_width, image_height, channels * 8, | ||||||
|  |       channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); | ||||||
|  |   if (surface == nullptr) { | ||||||
|  |     LOG_ERROR("Failed to create SDL surface: [{}]", SDL_GetError()); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); | ||||||
|  |   if (texture == nullptr) { | ||||||
|  |     LOG_ERROR("Failed to create SDL texture: [{}]", SDL_GetError()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   *out_texture = texture; | ||||||
|  |   *out_width = image_width; | ||||||
|  |   *out_height = image_height; | ||||||
|  |  | ||||||
|  |   SDL_FreeSurface(surface); | ||||||
|  |   stbi_image_free(image_data); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer, | ||||||
|  |                          SDL_Texture** out_texture, int* out_width, | ||||||
|  |                          int* out_height) { | ||||||
|  |   std::filesystem::path file_path(file_name); | ||||||
|  |   if (!std::filesystem::exists(file_path)) return false; | ||||||
|  |   std::ifstream file(file_path, std::ios::binary); | ||||||
|  |   if (!file) return false; | ||||||
|  |   file.seekg(0, std::ios::end); | ||||||
|  |   size_t file_size = file.tellg(); | ||||||
|  |   file.seekg(0, std::ios::beg); | ||||||
|  |   if (file_size == -1) return false; | ||||||
|  |   char* file_data = new char[file_size]; | ||||||
|  |   if (!file_data) return false; | ||||||
|  |   file.read(file_data, file_size); | ||||||
|  |   bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture, | ||||||
|  |                                    out_width, out_height); | ||||||
|  |   delete[] file_data; | ||||||
|  |  | ||||||
|  |   return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h, | ||||||
|  |                      char* dst_rgba) { | ||||||
|  |   uint8_t* y = reinterpret_cast<uint8_t*>(src); | ||||||
|  |   uint8_t* uv = y + src_w * src_h; | ||||||
|  |  | ||||||
|  |   float src_aspect = float(src_w) / src_h; | ||||||
|  |   float dst_aspect = float(dst_w) / dst_h; | ||||||
|  |   int fit_w = dst_w, fit_h = dst_h; | ||||||
|  |   if (src_aspect > dst_aspect) { | ||||||
|  |     fit_h = int(dst_w / src_aspect); | ||||||
|  |   } else { | ||||||
|  |     fit_w = int(dst_h * src_aspect); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> y_i420(src_w * src_h); | ||||||
|  |   std::vector<uint8_t> u_i420((src_w / 2) * (src_h / 2)); | ||||||
|  |   std::vector<uint8_t> v_i420((src_w / 2) * (src_h / 2)); | ||||||
|  |   libyuv::NV12ToI420(y, src_w, uv, src_w, y_i420.data(), src_w, u_i420.data(), | ||||||
|  |                      src_w / 2, v_i420.data(), src_w / 2, src_w, src_h); | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> y_fit(fit_w * fit_h); | ||||||
|  |   std::vector<uint8_t> u_fit((fit_w + 1) / 2 * (fit_h + 1) / 2); | ||||||
|  |   std::vector<uint8_t> v_fit((fit_w + 1) / 2 * (fit_h + 1) / 2); | ||||||
|  |   libyuv::I420Scale(y_i420.data(), src_w, u_i420.data(), src_w / 2, | ||||||
|  |                     v_i420.data(), src_w / 2, src_w, src_h, y_fit.data(), fit_w, | ||||||
|  |                     u_fit.data(), (fit_w + 1) / 2, v_fit.data(), | ||||||
|  |                     (fit_w + 1) / 2, fit_w, fit_h, libyuv::kFilterBilinear); | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> abgr(fit_w * fit_h * 4); | ||||||
|  |   libyuv::I420ToABGR(y_fit.data(), fit_w, u_fit.data(), (fit_w + 1) / 2, | ||||||
|  |                      v_fit.data(), (fit_w + 1) / 2, abgr.data(), fit_w * 4, | ||||||
|  |                      fit_w, fit_h); | ||||||
|  |  | ||||||
|  |   memset(dst_rgba, 0, dst_w * dst_h * 4); | ||||||
|  |   for (int i = 0; i < dst_w * dst_h; ++i) { | ||||||
|  |     dst_rgba[i * 4 + 3] = 0xFF; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (int y = 0; y < fit_h; ++y) { | ||||||
|  |     int dst_offset = | ||||||
|  |         ((y + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4; | ||||||
|  |     memcpy(dst_rgba + dst_offset, abgr.data() + y * fit_w * 4, fit_w * 4); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Thumbnail::Thumbnail(std::string save_path) { | ||||||
|  |   if (!save_path.empty()) { | ||||||
|  |     save_path_ = save_path; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   RAND_bytes(aes128_key_, sizeof(aes128_key_)); | ||||||
|  |   RAND_bytes(aes128_iv_, sizeof(aes128_iv_)); | ||||||
|  |   std::filesystem::create_directories(save_path_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Thumbnail::Thumbnail(std::string save_path, unsigned char* aes128_key, | ||||||
|  |                      unsigned char* aes128_iv) { | ||||||
|  |   if (!save_path.empty()) { | ||||||
|  |     save_path_ = save_path; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   memcpy(aes128_key_, aes128_key, sizeof(aes128_key_)); | ||||||
|  |   memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_)); | ||||||
|  |   std::filesystem::create_directories(save_path_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Thumbnail::~Thumbnail() { | ||||||
|  |   if (rgba_buffer_) { | ||||||
|  |     delete[] rgba_buffer_; | ||||||
|  |     rgba_buffer_ = nullptr; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height, | ||||||
|  |                                const std::string& remote_id, | ||||||
|  |                                const std::string& host_name, | ||||||
|  |                                const std::string& password) { | ||||||
|  |   if (!rgba_buffer_) { | ||||||
|  |     rgba_buffer_ = new char[thumbnail_width_ * thumbnail_height_ * 4]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (yuv420p) { | ||||||
|  |     ScaleNv12ToABGR((char*)yuv420p, width, height, thumbnail_width_, | ||||||
|  |                     thumbnail_height_, rgba_buffer_); | ||||||
|  |   } else { | ||||||
|  |     // If yuv420p is null, fill the buffer with black pixels | ||||||
|  |     memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4); | ||||||
|  |     for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) { | ||||||
|  |       // Set alpha channel to opaque | ||||||
|  |       rgba_buffer_[i * 4 + 3] = 0xFF; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string image_file_name; | ||||||
|  |   if (password.empty()) { | ||||||
|  |     return 0; | ||||||
|  |   } else { | ||||||
|  |     // delete the old thumbnail | ||||||
|  |     std::string filename_with_remote_id = remote_id; | ||||||
|  |     DeleteThumbnail(filename_with_remote_id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::string cipher_password = AES_encrypt(password, aes128_key_, aes128_iv_); | ||||||
|  |   image_file_name = remote_id + 'Y' + host_name + '@' + cipher_password; | ||||||
|  |   std::string file_path = save_path_ + image_file_name; | ||||||
|  |   stbi_write_png(file_path.data(), thumbnail_width_, thumbnail_height_, 4, | ||||||
|  |                  rgba_buffer_, thumbnail_width_ * 4); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Thumbnail::LoadThumbnail( | ||||||
|  |     SDL_Renderer* renderer, | ||||||
|  |     std::vector<std::pair<std::string, Thumbnail::RecentConnection>>& | ||||||
|  |         recent_connections, | ||||||
|  |     int* width, int* height) { | ||||||
|  |   for (auto& it : recent_connections) { | ||||||
|  |     if (it.second.texture != nullptr) { | ||||||
|  |       SDL_DestroyTexture(it.second.texture); | ||||||
|  |       it.second.texture = nullptr; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   recent_connections.clear(); | ||||||
|  |  | ||||||
|  |   std::vector<std::filesystem::path> image_paths = | ||||||
|  |       FindThumbnailPath(save_path_); | ||||||
|  |  | ||||||
|  |   if (image_paths.size() == 0) { | ||||||
|  |     return -1; | ||||||
|  |   } else { | ||||||
|  |     for (int i = 0; i < image_paths.size(); i++) { | ||||||
|  |       size_t pos1 = image_paths[i].string().find('/') + 1; | ||||||
|  |       std::string cipher_image_name = image_paths[i].filename().string(); | ||||||
|  |       std::string remote_id; | ||||||
|  |       std::string cipher_password; | ||||||
|  |       std::string remote_host_name; | ||||||
|  |       std::string original_image_name; | ||||||
|  |  | ||||||
|  |       if ('Y' == cipher_image_name[9] && cipher_image_name.size() >= 16) { | ||||||
|  |         size_t pos_y = cipher_image_name.find('Y'); | ||||||
|  |         size_t pos_at = cipher_image_name.find('@'); | ||||||
|  |  | ||||||
|  |         if (pos_y == std::string::npos || pos_at == std::string::npos || | ||||||
|  |             pos_y >= pos_at) { | ||||||
|  |           LOG_ERROR("Invalid filename"); | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         remote_id = cipher_image_name.substr(0, pos_y); | ||||||
|  |         remote_host_name = | ||||||
|  |             cipher_image_name.substr(pos_y + 1, pos_at - pos_y - 1); | ||||||
|  |         cipher_password = cipher_image_name.substr(pos_at + 1); | ||||||
|  |  | ||||||
|  |         original_image_name = | ||||||
|  |             remote_id + 'Y' + remote_host_name + "@" + | ||||||
|  |             AES_decrypt(cipher_password, aes128_key_, aes128_iv_); | ||||||
|  |       } else { | ||||||
|  |         size_t pos_n = cipher_image_name.find('N'); | ||||||
|  |         size_t pos_at = cipher_image_name.find('@'); | ||||||
|  |  | ||||||
|  |         if (pos_n == std::string::npos) { | ||||||
|  |           LOG_ERROR("Invalid filename"); | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         remote_id = cipher_image_name.substr(0, pos_n); | ||||||
|  |         remote_host_name = cipher_image_name.substr(pos_n + 1); | ||||||
|  |  | ||||||
|  |         original_image_name = | ||||||
|  |             remote_id + 'N' + remote_host_name + "@" + | ||||||
|  |             AES_decrypt(cipher_password, aes128_key_, aes128_iv_); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       std::string image_path = save_path_ + cipher_image_name; | ||||||
|  |       recent_connections.emplace_back( | ||||||
|  |           std::make_pair(original_image_name, Thumbnail::RecentConnection())); | ||||||
|  |       LoadTextureFromFile(image_path.c_str(), renderer, | ||||||
|  |                           &(recent_connections[i].second.texture), width, | ||||||
|  |                           height); | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Thumbnail::DeleteThumbnail(const std::string& filename_keyword) { | ||||||
|  |   for (const auto& entry : std::filesystem::directory_iterator(save_path_)) { | ||||||
|  |     if (entry.is_regular_file()) { | ||||||
|  |       const std::string filename = entry.path().filename().string(); | ||||||
|  |       std::string id_hostname = filename_keyword.substr(0, filename.find('@')); | ||||||
|  |       if (filename.find(id_hostname) != std::string::npos) { | ||||||
|  |         std::filesystem::remove(entry.path()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::vector<std::filesystem::path> Thumbnail::FindThumbnailPath( | ||||||
|  |     const std::filesystem::path& directory) { | ||||||
|  |   std::vector<std::filesystem::path> thumbnails_path; | ||||||
|  |  | ||||||
|  |   if (!std::filesystem::is_directory(directory)) { | ||||||
|  |     LOG_ERROR("No such directory [{}]", directory.string()); | ||||||
|  |     return thumbnails_path; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto& entry : std::filesystem::directory_iterator(directory)) { | ||||||
|  |     if (entry.is_regular_file()) { | ||||||
|  |       thumbnails_path.push_back(entry.path()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::sort(thumbnails_path.begin(), thumbnails_path.end(), | ||||||
|  |             [](const std::filesystem::path& a, const std::filesystem::path& b) { | ||||||
|  |               return std::filesystem::last_write_time(a) > | ||||||
|  |                      std::filesystem::last_write_time(b); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |   return thumbnails_path; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int Thumbnail::DeleteAllFilesInDirectory() { | ||||||
|  |   if (std::filesystem::exists(save_path_) && | ||||||
|  |       std::filesystem::is_directory(save_path_)) { | ||||||
|  |     for (const auto& entry : std::filesystem::directory_iterator(save_path_)) { | ||||||
|  |       if (std::filesystem::is_regular_file(entry.status())) { | ||||||
|  |         std::filesystem::remove(entry.path()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string Thumbnail::AES_encrypt(const std::string& plaintext, | ||||||
|  |                                    unsigned char* key, unsigned char* iv) { | ||||||
|  |   EVP_CIPHER_CTX* ctx; | ||||||
|  |   int len; | ||||||
|  |   int ciphertext_len; | ||||||
|  |   int ret = 0; | ||||||
|  |   std::vector<unsigned char> ciphertext(plaintext.size() + AES_BLOCK_SIZE); | ||||||
|  |  | ||||||
|  |   ctx = EVP_CIPHER_CTX_new(); | ||||||
|  |   if (!ctx) { | ||||||
|  |     LOG_ERROR("Error in EVP_CIPHER_CTX_new"); | ||||||
|  |     return plaintext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in EVP_EncryptInit_ex"); | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return plaintext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ret = EVP_EncryptUpdate( | ||||||
|  |       ctx, ciphertext.data(), &len, | ||||||
|  |       reinterpret_cast<const unsigned char*>(plaintext.data()), | ||||||
|  |       (int)plaintext.size()); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in EVP_EncryptUpdate"); | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return plaintext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ciphertext_len = len; | ||||||
|  |   ret = EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in EVP_EncryptFinal_ex"); | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return plaintext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ciphertext_len += len; | ||||||
|  |  | ||||||
|  |   unsigned char hex_str[256]; | ||||||
|  |   size_t hex_str_len = 0; | ||||||
|  |   ret = OPENSSL_buf2hexstr_ex((char*)hex_str, sizeof(hex_str), &hex_str_len, | ||||||
|  |                               ciphertext.data(), ciphertext_len, '\0'); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in OPENSSL_buf2hexstr_ex"); | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return plaintext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   EVP_CIPHER_CTX_free(ctx); | ||||||
|  |  | ||||||
|  |   std::string str(reinterpret_cast<char*>(hex_str), hex_str_len); | ||||||
|  |   return str; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string Thumbnail::AES_decrypt(const std::string& ciphertext, | ||||||
|  |                                    unsigned char* key, unsigned char* iv) { | ||||||
|  |   unsigned char ciphertext_buf[256]; | ||||||
|  |   size_t ciphertext_buf_len = 0; | ||||||
|  |   unsigned char plaintext[256]; | ||||||
|  |   int plaintext_len = 0; | ||||||
|  |   int plaintext_final_len = 0; | ||||||
|  |   EVP_CIPHER_CTX* ctx; | ||||||
|  |   int ret = 0; | ||||||
|  |  | ||||||
|  |   ret = OPENSSL_hexstr2buf_ex(ciphertext_buf, sizeof(ciphertext_buf), | ||||||
|  |                               &ciphertext_buf_len, ciphertext.c_str(), '\0'); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in OPENSSL_hexstr2buf_ex"); | ||||||
|  |     return ciphertext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ctx = EVP_CIPHER_CTX_new(); | ||||||
|  |   if (!ctx) { | ||||||
|  |     LOG_ERROR("Error in EVP_CIPHER_CTX_new"); | ||||||
|  |     return ciphertext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ret = EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in EVP_DecryptInit_ex"); | ||||||
|  |  | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return ciphertext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ret = EVP_DecryptUpdate(ctx, plaintext, &plaintext_len, ciphertext_buf, | ||||||
|  |                           (int)ciphertext_buf_len); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in EVP_DecryptUpdate"); | ||||||
|  |  | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return ciphertext; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ret = | ||||||
|  |       EVP_DecryptFinal_ex(ctx, plaintext + plaintext_len, &plaintext_final_len); | ||||||
|  |   if (1 != ret) { | ||||||
|  |     LOG_ERROR("Error in EVP_DecryptFinal_ex"); | ||||||
|  |  | ||||||
|  |     EVP_CIPHER_CTX_free(ctx); | ||||||
|  |     return ciphertext; | ||||||
|  |   } | ||||||
|  |   plaintext_len += plaintext_final_len; | ||||||
|  |  | ||||||
|  |   EVP_CIPHER_CTX_free(ctx); | ||||||
|  |  | ||||||
|  |   return std::string(reinterpret_cast<char*>(plaintext), plaintext_len); | ||||||
|  | } | ||||||
							
								
								
									
										87
									
								
								src/single_window/thumbnail.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/single_window/thumbnail.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-11-07 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _THUMBNAIL_H_ | ||||||
|  | #define _THUMBNAIL_H_ | ||||||
|  |  | ||||||
|  | #include <SDL.h> | ||||||
|  |  | ||||||
|  | #include <filesystem> | ||||||
|  | #include <map> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | class Thumbnail { | ||||||
|  |  public: | ||||||
|  |   struct RecentConnection { | ||||||
|  |     SDL_Texture* texture = nullptr; | ||||||
|  |     std::string remote_id; | ||||||
|  |     std::string remote_host_name; | ||||||
|  |     std::string password; | ||||||
|  |     bool remember_password = false; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   Thumbnail(std::string save_path); | ||||||
|  |   explicit Thumbnail(std::string save_path, unsigned char* aes128_key, | ||||||
|  |                      unsigned char* aes128_iv); | ||||||
|  |   ~Thumbnail(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   int SaveToThumbnail(const char* yuv420p, int width, int height, | ||||||
|  |                       const std::string& remote_id, | ||||||
|  |                       const std::string& host_name, | ||||||
|  |                       const std::string& password); | ||||||
|  |  | ||||||
|  |   int LoadThumbnail( | ||||||
|  |       SDL_Renderer* renderer, | ||||||
|  |       std::vector<std::pair<std::string, Thumbnail::RecentConnection>>& | ||||||
|  |           recent_connections, | ||||||
|  |       int* width, int* height); | ||||||
|  |  | ||||||
|  |   int DeleteThumbnail(const std::string& filename_keyword); | ||||||
|  |  | ||||||
|  |   int DeleteAllFilesInDirectory(); | ||||||
|  |  | ||||||
|  |   int GetKey(unsigned char* aes128_key) { | ||||||
|  |     memcpy(aes128_key, aes128_key_, sizeof(aes128_key_)); | ||||||
|  |     return sizeof(aes128_key_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int GetIv(unsigned char* aes128_iv) { | ||||||
|  |     memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_)); | ||||||
|  |     return sizeof(aes128_iv_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int GetKeyAndIv(unsigned char* aes128_key, unsigned char* aes128_iv) { | ||||||
|  |     memcpy(aes128_key, aes128_key_, sizeof(aes128_key_)); | ||||||
|  |     memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_)); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::vector<std::filesystem::path> FindThumbnailPath( | ||||||
|  |       const std::filesystem::path& directory); | ||||||
|  |  | ||||||
|  |   std::string AES_encrypt(const std::string& plaintext, unsigned char* key, | ||||||
|  |                           unsigned char* iv); | ||||||
|  |  | ||||||
|  |   std::string AES_decrypt(const std::string& ciphertext, unsigned char* key, | ||||||
|  |                           unsigned char* iv); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   int thumbnail_width_ = 160; | ||||||
|  |   int thumbnail_height_ = 90; | ||||||
|  |   char* rgba_buffer_ = nullptr; | ||||||
|  |   std::string save_path_ = "thumbnails/"; | ||||||
|  |  | ||||||
|  |   unsigned char aes128_key_[16]; | ||||||
|  |   unsigned char aes128_iv_[16]; | ||||||
|  |   unsigned char ciphertext_[64]; | ||||||
|  |   unsigned char decryptedtext_[64]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										165
									
								
								src/single_window/title_bar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/single_window/title_bar.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | |||||||
|  | #include "localization.h" | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "render.h" | ||||||
|  |  | ||||||
|  | #define BUTTON_PADDING 36.0f | ||||||
|  |  | ||||||
|  | int Render::TitleBar(bool main_window) { | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); | ||||||
|  |   ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |   ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); | ||||||
|  |   ImGui::SetWindowFontScale(0.8f); | ||||||
|  |   ImGui::BeginChild( | ||||||
|  |       main_window ? "MainTitleBar" : "StreamTitleBar", | ||||||
|  |       ImVec2(main_window ? main_window_width_ : stream_window_width_, | ||||||
|  |              title_bar_height_), | ||||||
|  |       ImGuiChildFlags_Border, | ||||||
|  |       ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration | | ||||||
|  |           ImGuiWindowFlags_NoBringToFrontOnFocus); | ||||||
|  |   ImGui::SetWindowFontScale(1.0f); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |  | ||||||
|  |   ImDrawList* draw_list = ImGui::GetWindowDrawList(); | ||||||
|  |   if (ImGui::BeginMenuBar()) { | ||||||
|  |     ImGui::SetCursorPosX( | ||||||
|  |         (main_window ? main_window_width_ : stream_window_width_) - | ||||||
|  |         (BUTTON_PADDING * 3 - 3)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0.1f)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_HeaderActive, | ||||||
|  |                           ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |     if (main_window) { | ||||||
|  |       float bar_pos_x = ImGui::GetCursorPosX() + 6; | ||||||
|  |       float bar_pos_y = ImGui::GetCursorPosY() + 15; | ||||||
|  |       std::string menu_button = "    ";  // ICON_FA_BARS; | ||||||
|  |       if (ImGui::BeginMenu(menu_button.c_str())) { | ||||||
|  |         ImGui::SetWindowFontScale(0.5f); | ||||||
|  |         if (ImGui::MenuItem( | ||||||
|  |                 localization::settings[localization_language_index_].c_str())) { | ||||||
|  |           show_settings_window_ = true; | ||||||
|  |         } | ||||||
|  |         if (ImGui::MenuItem( | ||||||
|  |                 localization::about[localization_language_index_].c_str())) { | ||||||
|  |           show_about_window_ = true; | ||||||
|  |         } | ||||||
|  |         ImGui::SetWindowFontScale(1.0f); | ||||||
|  |         ImGui::EndMenu(); | ||||||
|  |       } | ||||||
|  |       float menu_bar_line_size = 15.0f; | ||||||
|  |       draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y - 6), | ||||||
|  |                          ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y - 6), | ||||||
|  |                          IM_COL32(0, 0, 0, 255)); | ||||||
|  |       draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y), | ||||||
|  |                          ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y), | ||||||
|  |                          IM_COL32(0, 0, 0, 255)); | ||||||
|  |       draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y + 6), | ||||||
|  |                          ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y + 6), | ||||||
|  |                          IM_COL32(0, 0, 0, 255)); | ||||||
|  |  | ||||||
|  |       { | ||||||
|  |         SettingWindow(); | ||||||
|  |         AboutWindow(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::PopStyleColor(2); | ||||||
|  |  | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); | ||||||
|  |     ImGui::SetCursorPosX(main_window | ||||||
|  |                              ? (main_window_width_ - BUTTON_PADDING * 2) | ||||||
|  |                              : (stream_window_width_ - BUTTON_PADDING * 3)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ButtonActive, | ||||||
|  |                           ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |  | ||||||
|  |     float minimize_pos_x = ImGui::GetCursorPosX() + 12; | ||||||
|  |     float minimize_pos_y = ImGui::GetCursorPosY() + 15; | ||||||
|  |     std::string window_minimize_button = "##minimize";  // ICON_FA_MINUS; | ||||||
|  |     if (ImGui::Button(window_minimize_button.c_str(), | ||||||
|  |                       ImVec2(BUTTON_PADDING, 30))) { | ||||||
|  |       SDL_MinimizeWindow(main_window ? main_window_ : stream_window_); | ||||||
|  |     } | ||||||
|  |     draw_list->AddLine(ImVec2(minimize_pos_x, minimize_pos_y), | ||||||
|  |                        ImVec2(minimize_pos_x + 12, minimize_pos_y), | ||||||
|  |                        IM_COL32(0, 0, 0, 255)); | ||||||
|  |     ImGui::PopStyleColor(2); | ||||||
|  |  | ||||||
|  |     if (!main_window) { | ||||||
|  |       ImGui::SetCursorPosX(stream_window_width_ - BUTTON_PADDING * 2); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f)); | ||||||
|  |       ImGui::PushStyleColor(ImGuiCol_ButtonActive, | ||||||
|  |                             ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); | ||||||
|  |  | ||||||
|  |       if (window_maximized_) { | ||||||
|  |         float pos_x_top = ImGui::GetCursorPosX() + 11; | ||||||
|  |         float pos_y_top = ImGui::GetCursorPosY() + 11; | ||||||
|  |         float pos_x_bottom = ImGui::GetCursorPosX() + 13; | ||||||
|  |         float pos_y_bottom = ImGui::GetCursorPosY() + 9; | ||||||
|  |         std::string window_restore_button = | ||||||
|  |             "##restore";  // ICON_FA_WINDOW_RESTORE; | ||||||
|  |         if (ImGui::Button(window_restore_button.c_str(), | ||||||
|  |                           ImVec2(BUTTON_PADDING, 30))) { | ||||||
|  |           SDL_RestoreWindow(stream_window_); | ||||||
|  |           window_maximized_ = false; | ||||||
|  |         } | ||||||
|  |         draw_list->AddRect(ImVec2(pos_x_top, pos_y_top), | ||||||
|  |                            ImVec2(pos_x_top + 12, pos_y_top + 12), | ||||||
|  |                            IM_COL32(0, 0, 0, 255)); | ||||||
|  |         draw_list->AddRect(ImVec2(pos_x_bottom, pos_y_bottom), | ||||||
|  |                            ImVec2(pos_x_bottom + 12, pos_y_bottom + 12), | ||||||
|  |                            IM_COL32(0, 0, 0, 255)); | ||||||
|  |         draw_list->AddRectFilled(ImVec2(pos_x_top + 1, pos_y_top + 1), | ||||||
|  |                                  ImVec2(pos_x_top + 11, pos_y_top + 11), | ||||||
|  |                                  IM_COL32(255, 255, 255, 255)); | ||||||
|  |       } else { | ||||||
|  |         float maximize_pos_x = ImGui::GetCursorPosX() + 12; | ||||||
|  |         float maximize_pos_y = ImGui::GetCursorPosY() + 10; | ||||||
|  |         std::string window_maximize_button = | ||||||
|  |             "##maximize";  // ICON_FA_SQUARE_FULL; | ||||||
|  |         if (ImGui::Button(window_maximize_button.c_str(), | ||||||
|  |                           ImVec2(BUTTON_PADDING, 30))) { | ||||||
|  |           SDL_MaximizeWindow(stream_window_); | ||||||
|  |           window_maximized_ = !window_maximized_; | ||||||
|  |         } | ||||||
|  |         draw_list->AddRect(ImVec2(maximize_pos_x, maximize_pos_y), | ||||||
|  |                            ImVec2(maximize_pos_x + 12, maximize_pos_y + 12), | ||||||
|  |                            IM_COL32(0, 0, 0, 255)); | ||||||
|  |       } | ||||||
|  |       ImGui::PopStyleColor(2); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ImGui::SetCursorPosX( | ||||||
|  |         (main_window ? main_window_width_ : stream_window_width_) - | ||||||
|  |         BUTTON_PADDING); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0, 0, 1.0f)); | ||||||
|  |     ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0, 0, 0.5f)); | ||||||
|  |  | ||||||
|  |     float xmark_pos_x = ImGui::GetCursorPosX() + 18; | ||||||
|  |     float xmark_pos_y = ImGui::GetCursorPosY() + 16; | ||||||
|  |     float xmark_size = 12.0f; | ||||||
|  |     std::string close_button = "##xmark";  // ICON_FA_XMARK; | ||||||
|  |     if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) { | ||||||
|  |       SDL_Event event; | ||||||
|  |       event.type = SDL_QUIT; | ||||||
|  |       SDL_PushEvent(&event); | ||||||
|  |     } | ||||||
|  |     draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f, | ||||||
|  |                               xmark_pos_y - xmark_size / 2 + 0.75f), | ||||||
|  |                        ImVec2(xmark_pos_x + xmark_size / 2 - 1.5f, | ||||||
|  |                               xmark_pos_y + xmark_size / 2 - 0.5f), | ||||||
|  |                        IM_COL32(0, 0, 0, 255)); | ||||||
|  |     draw_list->AddLine(ImVec2(xmark_pos_x + xmark_size / 2 - 1.75f, | ||||||
|  |                               xmark_pos_y - xmark_size / 2 + 0.75f), | ||||||
|  |                        ImVec2(xmark_pos_x - xmark_size / 2, | ||||||
|  |                               xmark_pos_y + xmark_size / 2 - 1.0f), | ||||||
|  |                        IM_COL32(0, 0, 0, 255)); | ||||||
|  |  | ||||||
|  |     ImGui::PopStyleColor(2); | ||||||
|  |  | ||||||
|  |     ImGui::PopStyleColor(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ImGui::EndMenuBar(); | ||||||
|  |   ImGui::EndChild(); | ||||||
|  |   ImGui::PopStyleColor(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										268
									
								
								src/speaker_capturer/linux/speaker_capturer_linux.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/speaker_capturer/linux/speaker_capturer_linux.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | |||||||
|  | #include "speaker_capturer_linux.h" | ||||||
|  |  | ||||||
|  | #include <pulse/error.h> | ||||||
|  | #include <pulse/introspect.h> | ||||||
|  |  | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <iostream> | ||||||
|  | #include <thread> | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | constexpr int kSampleRate = 48000; | ||||||
|  | constexpr pa_sample_format_t kFormat = PA_SAMPLE_S16LE; | ||||||
|  | constexpr int kChannels = 1; | ||||||
|  | constexpr size_t kFrameSizeBytes = 480 * sizeof(int16_t); | ||||||
|  |  | ||||||
|  | SpeakerCapturerLinux::SpeakerCapturerLinux() | ||||||
|  |     : inited_(false), paused_(false), stop_flag_(false) {} | ||||||
|  | SpeakerCapturerLinux::~SpeakerCapturerLinux() { | ||||||
|  |   Stop(); | ||||||
|  |   Destroy(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerLinux::Init(speaker_data_cb cb) { | ||||||
|  |   if (inited_) return 0; | ||||||
|  |   cb_ = cb; | ||||||
|  |   inited_ = true; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerLinux::Destroy() { | ||||||
|  |   inited_ = false; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string SpeakerCapturerLinux::GetDefaultMonitorSourceName() { | ||||||
|  |   std::string monitor_name; | ||||||
|  |   std::mutex mtx; | ||||||
|  |   std::condition_variable cv; | ||||||
|  |   bool ready = false; | ||||||
|  |  | ||||||
|  |   pa_mainloop* mainloop = pa_mainloop_new(); | ||||||
|  |   pa_mainloop_api* api = pa_mainloop_get_api(mainloop); | ||||||
|  |   pa_context* context = pa_context_new(api, "GetMonitor"); | ||||||
|  |  | ||||||
|  |   struct CallbackState { | ||||||
|  |     std::string* name; | ||||||
|  |     std::mutex* mtx; | ||||||
|  |     std::condition_variable* cv; | ||||||
|  |     bool* ready; | ||||||
|  |   } state{&monitor_name, &mtx, &cv, &ready}; | ||||||
|  |  | ||||||
|  |   pa_context_set_state_callback( | ||||||
|  |       context, | ||||||
|  |       [](pa_context* c, void* userdata) { | ||||||
|  |         auto* state = static_cast<CallbackState*>(userdata); | ||||||
|  |         if (pa_context_get_state(c) == PA_CONTEXT_READY) { | ||||||
|  |           pa_operation* operation = pa_context_get_server_info( | ||||||
|  |               c, | ||||||
|  |               [](pa_context*, const pa_server_info* info, void* userdata) { | ||||||
|  |                 auto* state = static_cast<CallbackState*>(userdata); | ||||||
|  |                 if (info && info->default_sink_name) { | ||||||
|  |                   *(state->name) = | ||||||
|  |                       std::string(info->default_sink_name) + ".monitor"; | ||||||
|  |                 } | ||||||
|  |                 { | ||||||
|  |                   std::lock_guard<std::mutex> lock(*(state->mtx)); | ||||||
|  |                   *(state->ready) = true; | ||||||
|  |                 } | ||||||
|  |                 state->cv->notify_one(); | ||||||
|  |               }, | ||||||
|  |               userdata); | ||||||
|  |           if (operation) { | ||||||
|  |             pa_operation_unref(operation); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       &state); | ||||||
|  |  | ||||||
|  |   pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr); | ||||||
|  |  | ||||||
|  |   std::thread loop_thread([&]() { | ||||||
|  |     int ret = 0; | ||||||
|  |     pa_mainloop_run(mainloop, &ret); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   { | ||||||
|  |     std::unique_lock<std::mutex> lock(mtx); | ||||||
|  |     cv.wait_for(lock, std::chrono::seconds(2), [&] { return ready; }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   pa_context_disconnect(context); | ||||||
|  |   pa_context_unref(context); | ||||||
|  |   pa_mainloop_quit(mainloop, 0); | ||||||
|  |   loop_thread.join(); | ||||||
|  |   pa_mainloop_free(mainloop); | ||||||
|  |  | ||||||
|  |   return monitor_name; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerLinux::Start() { | ||||||
|  |   if (!inited_ || mainloop_thread_.joinable()) return -1; | ||||||
|  |   stop_flag_ = false; | ||||||
|  |  | ||||||
|  |   mainloop_thread_ = std::thread([this]() { | ||||||
|  |     std::string monitor_name = GetDefaultMonitorSourceName(); | ||||||
|  |     if (monitor_name.empty()) { | ||||||
|  |       LOG_ERROR("Failed to get monitor source"); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     mainloop_ = pa_threaded_mainloop_new(); | ||||||
|  |     pa_mainloop_api* api = pa_threaded_mainloop_get_api(mainloop_); | ||||||
|  |     context_ = pa_context_new(api, "SpeakerCapturer"); | ||||||
|  |  | ||||||
|  |     pa_context_set_state_callback( | ||||||
|  |         context_, | ||||||
|  |         [](pa_context* c, void* userdata) { | ||||||
|  |           auto self = static_cast<SpeakerCapturerLinux*>(userdata); | ||||||
|  |           pa_context_state_t state = pa_context_get_state(c); | ||||||
|  |           if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || | ||||||
|  |               state == PA_CONTEXT_TERMINATED) { | ||||||
|  |             pa_threaded_mainloop_signal(self->mainloop_, 0); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         this); | ||||||
|  |  | ||||||
|  |     if (pa_threaded_mainloop_start(mainloop_) < 0) { | ||||||
|  |       LOG_ERROR("Failed to start mainloop"); | ||||||
|  |       Cleanup(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pa_threaded_mainloop_lock(mainloop_); | ||||||
|  |  | ||||||
|  |     if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < | ||||||
|  |         0) { | ||||||
|  |       LOG_ERROR("Failed to connect context"); | ||||||
|  |       pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |       Cleanup(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     while (true) { | ||||||
|  |       pa_context_state_t state = pa_context_get_state(context_); | ||||||
|  |       if (state == PA_CONTEXT_READY) break; | ||||||
|  |       if (!PA_CONTEXT_IS_GOOD(state) || stop_flag_) { | ||||||
|  |         pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |         Cleanup(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       pa_threaded_mainloop_wait(mainloop_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pa_sample_spec ss = {kFormat, kSampleRate, kChannels}; | ||||||
|  |     stream_ = pa_stream_new(context_, "Capture", &ss, nullptr); | ||||||
|  |  | ||||||
|  |     pa_stream_set_read_callback( | ||||||
|  |         stream_, | ||||||
|  |         [](pa_stream* s, size_t len, void* u) { | ||||||
|  |           auto self = static_cast<SpeakerCapturerLinux*>(u); | ||||||
|  |           if (self->paused_ || self->stop_flag_) return; | ||||||
|  |  | ||||||
|  |           const void* data = nullptr; | ||||||
|  |           if (pa_stream_peek(s, &data, &len) < 0 || !data) return; | ||||||
|  |  | ||||||
|  |           const uint8_t* p = static_cast<const uint8_t*>(data); | ||||||
|  |           self->frame_cache_.insert(self->frame_cache_.end(), p, p + len); | ||||||
|  |  | ||||||
|  |           while (self->frame_cache_.size() >= kFrameSizeBytes) { | ||||||
|  |             std::vector<uint8_t> temp_frame( | ||||||
|  |                 self->frame_cache_.begin(), | ||||||
|  |                 self->frame_cache_.begin() + kFrameSizeBytes); | ||||||
|  |             self->cb_(temp_frame.data(), kFrameSizeBytes, "audio"); | ||||||
|  |             self->frame_cache_.erase( | ||||||
|  |                 self->frame_cache_.begin(), | ||||||
|  |                 self->frame_cache_.begin() + kFrameSizeBytes); | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           pa_stream_drop(s); | ||||||
|  |         }, | ||||||
|  |         this); | ||||||
|  |  | ||||||
|  |     pa_buffer_attr attr = {.maxlength = (uint32_t)-1, | ||||||
|  |                            .tlength = 0, | ||||||
|  |                            .prebuf = 0, | ||||||
|  |                            .minreq = 0, | ||||||
|  |                            .fragsize = (uint32_t)kFrameSizeBytes}; | ||||||
|  |  | ||||||
|  |     if (pa_stream_connect_record(stream_, monitor_name.c_str(), &attr, | ||||||
|  |                                  PA_STREAM_ADJUST_LATENCY) < 0) { | ||||||
|  |       LOG_ERROR("Failed to connect stream"); | ||||||
|  |       pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |       Cleanup(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     while (true) { | ||||||
|  |       pa_stream_state_t s = pa_stream_get_state(stream_); | ||||||
|  |       if (s == PA_STREAM_READY) break; | ||||||
|  |       if (!PA_STREAM_IS_GOOD(s) || stop_flag_) { | ||||||
|  |         pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |         Cleanup(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       pa_threaded_mainloop_wait(mainloop_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |  | ||||||
|  |     while (!stop_flag_) | ||||||
|  |       std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerLinux::Stop() { | ||||||
|  |   stop_flag_ = true; | ||||||
|  |  | ||||||
|  |   if (mainloop_) { | ||||||
|  |     pa_threaded_mainloop_lock(mainloop_); | ||||||
|  |     pa_threaded_mainloop_signal(mainloop_, 0); | ||||||
|  |     pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (mainloop_thread_.joinable()) { | ||||||
|  |     mainloop_thread_.join(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Cleanup(); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SpeakerCapturerLinux::Cleanup() { | ||||||
|  |   if (mainloop_) { | ||||||
|  |     pa_threaded_mainloop_stop(mainloop_); | ||||||
|  |     pa_threaded_mainloop_lock(mainloop_); | ||||||
|  |  | ||||||
|  |     if (stream_) { | ||||||
|  |       pa_stream_disconnect(stream_); | ||||||
|  |       pa_stream_unref(stream_); | ||||||
|  |       stream_ = nullptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (context_) { | ||||||
|  |       pa_context_disconnect(context_); | ||||||
|  |       pa_context_unref(context_); | ||||||
|  |       context_ = nullptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pa_threaded_mainloop_unlock(mainloop_); | ||||||
|  |     pa_threaded_mainloop_free(mainloop_); | ||||||
|  |     mainloop_ = nullptr; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   frame_cache_.clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerLinux::Pause() { | ||||||
|  |   paused_ = true; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerLinux::Resume() { | ||||||
|  |   paused_ = false; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								src/speaker_capturer/linux/speaker_capturer_linux.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/speaker_capturer/linux/speaker_capturer_linux.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2025-07-15 | ||||||
|  |  * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _SPEAKER_CAPTURER_LINUX_H_ | ||||||
|  | #define _SPEAKER_CAPTURER_LINUX_H_ | ||||||
|  |  | ||||||
|  | #include <pulse/pulseaudio.h> | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <functional> | ||||||
|  | #include <mutex> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "speaker_capturer.h" | ||||||
|  |  | ||||||
|  | class SpeakerCapturerLinux : public SpeakerCapturer { | ||||||
|  |  public: | ||||||
|  |   SpeakerCapturerLinux(); | ||||||
|  |   ~SpeakerCapturerLinux(); | ||||||
|  |  | ||||||
|  |   int Init(speaker_data_cb cb) override; | ||||||
|  |   int Destroy() override; | ||||||
|  |   int Start() override; | ||||||
|  |   int Stop() override; | ||||||
|  |  | ||||||
|  |   int Pause(); | ||||||
|  |   int Resume(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   std::string GetDefaultMonitorSourceName(); | ||||||
|  |   void Cleanup(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   speaker_data_cb cb_ = nullptr; | ||||||
|  |  | ||||||
|  |   std::atomic<bool> inited_; | ||||||
|  |   std::atomic<bool> paused_; | ||||||
|  |   std::atomic<bool> stop_flag_; | ||||||
|  |  | ||||||
|  |   std::thread mainloop_thread_; | ||||||
|  |   pa_threaded_mainloop* mainloop_ = nullptr; | ||||||
|  |   pa_context* context_ = nullptr; | ||||||
|  |   pa_stream* stream_ = nullptr; | ||||||
|  |  | ||||||
|  |   std::mutex state_mtx_; | ||||||
|  |   std::vector<uint8_t> frame_cache_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										37
									
								
								src/speaker_capturer/macosx/speaker_capturer_macosx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/speaker_capturer/macosx/speaker_capturer_macosx.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-08-02 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _SPEAKER_CAPTURER_MACOSX_H_ | ||||||
|  | #define _SPEAKER_CAPTURER_MACOSX_H_ | ||||||
|  |  | ||||||
|  | #include <thread> | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | #include "speaker_capturer.h" | ||||||
|  |  | ||||||
|  | class SpeakerCapturerMacosx : public SpeakerCapturer { | ||||||
|  |  public: | ||||||
|  |   SpeakerCapturerMacosx(); | ||||||
|  |   ~SpeakerCapturerMacosx(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual int Init(speaker_data_cb cb); | ||||||
|  |   virtual int Destroy(); | ||||||
|  |   virtual int Start(); | ||||||
|  |   virtual int Stop(); | ||||||
|  |  | ||||||
|  |   int Pause(); | ||||||
|  |   int Resume(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   speaker_data_cb cb_ = nullptr; | ||||||
|  |   bool inited_ = false; | ||||||
|  |  | ||||||
|  |   class Impl; | ||||||
|  |   Impl* impl_ = nullptr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										264
									
								
								src/speaker_capturer/macosx/speaker_capturer_macosx.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/speaker_capturer/macosx/speaker_capturer_macosx.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,264 @@ | |||||||
|  | #import <AVFoundation/AVFoundation.h> | ||||||
|  | #import <Foundation/Foundation.h> | ||||||
|  | #import <ScreenCaptureKit/ScreenCaptureKit.h> | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  | #include "speaker_capturer_macosx.h" | ||||||
|  |  | ||||||
|  | @interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput> | ||||||
|  | @property(nonatomic, assign) SpeakerCapturerMacosx* owner; | ||||||
|  | - (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner; | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | @implementation SpeakerCaptureDelegate | ||||||
|  | - (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner { | ||||||
|  |   self = [super init]; | ||||||
|  |   if (self) { | ||||||
|  |     _owner = owner; | ||||||
|  |   } | ||||||
|  |   return self; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | - (void)stream:(SCStream*)stream | ||||||
|  |     didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer | ||||||
|  |                    ofType:(SCStreamOutputType)type { | ||||||
|  |   if (type == SCStreamOutputTypeAudio) { | ||||||
|  |     CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); | ||||||
|  |     size_t length = CMBlockBufferGetDataLength(blockBuffer); | ||||||
|  |     char* dataPtr = NULL; | ||||||
|  |     CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr); | ||||||
|  |     CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer); | ||||||
|  |     const AudioStreamBasicDescription* asbd = | ||||||
|  |         CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc); | ||||||
|  |  | ||||||
|  |     if (_owner->cb_ && dataPtr && length > 0 && asbd) { | ||||||
|  |       std::vector<short> out_pcm16; | ||||||
|  |       if (asbd->mFormatFlags & kAudioFormatFlagIsFloat) { | ||||||
|  |         int channels = asbd->mChannelsPerFrame; | ||||||
|  |         int samples = (int)(length / sizeof(float)); | ||||||
|  |         float* floatData = (float*)dataPtr; | ||||||
|  |         std::vector<short> pcm16(samples); | ||||||
|  |         for (int i = 0; i < samples; ++i) { | ||||||
|  |           float v = floatData[i]; | ||||||
|  |           if (v > 1.0f) v = 1.0f; | ||||||
|  |           if (v < -1.0f) v = -1.0f; | ||||||
|  |           pcm16[i] = (short)(v * 32767.0f); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (channels > 1) { | ||||||
|  |           int mono_samples = samples / channels; | ||||||
|  |           out_pcm16.resize(mono_samples); | ||||||
|  |           for (int i = 0; i < mono_samples; ++i) { | ||||||
|  |             int sum = 0; | ||||||
|  |             for (int c = 0; c < channels; ++c) { | ||||||
|  |               sum += pcm16[i * channels + c]; | ||||||
|  |             } | ||||||
|  |             out_pcm16[i] = sum / channels; | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           out_pcm16 = std::move(pcm16); | ||||||
|  |         } | ||||||
|  |       } else if (asbd->mBitsPerChannel == 16) { | ||||||
|  |         int channels = asbd->mChannelsPerFrame; | ||||||
|  |         int samples = (int)(length / 2); | ||||||
|  |         short* src = (short*)dataPtr; | ||||||
|  |         if (channels > 1) { | ||||||
|  |           int mono_samples = samples / channels; | ||||||
|  |           out_pcm16.resize(mono_samples); | ||||||
|  |           for (int i = 0; i < mono_samples; ++i) { | ||||||
|  |             int sum = 0; | ||||||
|  |             for (int c = 0; c < channels; ++c) { | ||||||
|  |               sum += src[i * channels + c]; | ||||||
|  |             } | ||||||
|  |             out_pcm16[i] = sum / channels; | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           out_pcm16.assign(src, src + samples); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       size_t frame_bytes = 960;  // 480 * 2 | ||||||
|  |       size_t total_bytes = out_pcm16.size() * sizeof(short); | ||||||
|  |       unsigned char* p = (unsigned char*)out_pcm16.data(); | ||||||
|  |       for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) { | ||||||
|  |         _owner->cb_(p + offset, frame_bytes, "audio"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @end | ||||||
|  |  | ||||||
|  | class SpeakerCapturerMacosx::Impl { | ||||||
|  |  public: | ||||||
|  |   SCStreamConfiguration* config = nil; | ||||||
|  |   SCStream* stream = nil; | ||||||
|  |   SpeakerCaptureDelegate* delegate = nil; | ||||||
|  |   dispatch_queue_t queue = nil; | ||||||
|  |   SCShareableContent* content = nil; | ||||||
|  |   SCDisplay* mainDisplay = nil; | ||||||
|  |  | ||||||
|  |   ~Impl() { | ||||||
|  |     if (stream) { | ||||||
|  |       [stream stopCaptureWithCompletionHandler:^(NSError* _Nullable error){ | ||||||
|  |       }]; | ||||||
|  |       stream = nil; | ||||||
|  |     } | ||||||
|  |     delegate = nil; | ||||||
|  |     if (queue) { | ||||||
|  |       queue = nil; | ||||||
|  |     } | ||||||
|  |     content = nil; | ||||||
|  |     mainDisplay = nil; | ||||||
|  |     config = nil; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | SpeakerCapturerMacosx::SpeakerCapturerMacosx() { | ||||||
|  |   impl_ = new Impl(); | ||||||
|  |   inited_ = false; | ||||||
|  |   cb_ = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SpeakerCapturerMacosx::~SpeakerCapturerMacosx() { | ||||||
|  |   Destroy(); | ||||||
|  |   delete impl_; | ||||||
|  |   impl_ = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerMacosx::Init(speaker_data_cb cb) { | ||||||
|  |   if (inited_) { | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   cb_ = cb; | ||||||
|  |  | ||||||
|  |   impl_->config = [[SCStreamConfiguration alloc] init]; | ||||||
|  |   impl_->config.capturesAudio = YES; | ||||||
|  |   impl_->config.sampleRate = 48000; | ||||||
|  |   impl_->config.channelCount = 1; | ||||||
|  |  | ||||||
|  |   dispatch_semaphore_t sema = dispatch_semaphore_create(0); | ||||||
|  |   __block NSError* error = nil; | ||||||
|  |   [SCShareableContent | ||||||
|  |       getShareableContentWithCompletionHandler:^(SCShareableContent* c, NSError* e) { | ||||||
|  |         impl_->content = c; | ||||||
|  |         error = e; | ||||||
|  |         dispatch_semaphore_signal(sema); | ||||||
|  |       }]; | ||||||
|  |   dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | ||||||
|  |  | ||||||
|  |   if (error || !impl_->content) { | ||||||
|  |     LOG_ERROR("Failed to get shareable content: {}", | ||||||
|  |               std::string([error.localizedDescription UTF8String])); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   CGDirectDisplayID mainDisplayId = CGMainDisplayID(); | ||||||
|  |   impl_->mainDisplay = nil; | ||||||
|  |   for (SCDisplay* d in impl_->content.displays) { | ||||||
|  |     if (d.displayID == mainDisplayId) { | ||||||
|  |       impl_->mainDisplay = d; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (!impl_->mainDisplay) { | ||||||
|  |     LOG_ERROR("Main display not found"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!impl_->queue) { | ||||||
|  |     impl_->queue = dispatch_queue_create("SpeakerAudio.Queue", DISPATCH_QUEUE_SERIAL); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   inited_ = true; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerMacosx::Start() { | ||||||
|  |   if (!inited_) { | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (impl_->stream) { | ||||||
|  |     dispatch_semaphore_t semaStop = dispatch_semaphore_create(0); | ||||||
|  |     [impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) { | ||||||
|  |       dispatch_semaphore_signal(semaStop); | ||||||
|  |     }]; | ||||||
|  |     dispatch_semaphore_wait(semaStop, DISPATCH_TIME_FOREVER); | ||||||
|  |     impl_->stream = nil; | ||||||
|  |     impl_->delegate = nil; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   impl_->delegate = [[SpeakerCaptureDelegate alloc] initWithOwner:this]; | ||||||
|  |   SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:impl_->mainDisplay | ||||||
|  |                                                     excludingWindows:@[]]; | ||||||
|  |   impl_->stream = [[SCStream alloc] initWithFilter:filter | ||||||
|  |                                      configuration:impl_->config | ||||||
|  |                                           delegate:impl_->delegate]; | ||||||
|  |  | ||||||
|  |   NSError* addOutputError = nil; | ||||||
|  |   BOOL ok = [impl_->stream addStreamOutput:impl_->delegate | ||||||
|  |                                       type:SCStreamOutputTypeAudio | ||||||
|  |                         sampleHandlerQueue:impl_->queue | ||||||
|  |                                      error:&addOutputError]; | ||||||
|  |   if (!ok || addOutputError) { | ||||||
|  |     LOG_ERROR("addStreamOutput error: {}", | ||||||
|  |               std::string([addOutputError.localizedDescription UTF8String])); | ||||||
|  |     impl_->stream = nil; | ||||||
|  |     impl_->delegate = nil; | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   dispatch_semaphore_t semaStart = dispatch_semaphore_create(0); | ||||||
|  |   __block int ret = 0; | ||||||
|  |   [impl_->stream startCaptureWithCompletionHandler:^(NSError* _Nullable error) { | ||||||
|  |     if (error) { | ||||||
|  |       LOG_ERROR("startCaptureWithCompletionHandler error: {}", | ||||||
|  |                 std::string([error.localizedDescription UTF8String])); | ||||||
|  |       ret = -1; | ||||||
|  |     } | ||||||
|  |     dispatch_semaphore_signal(semaStart); | ||||||
|  |   }]; | ||||||
|  |   dispatch_semaphore_wait(semaStart, DISPATCH_TIME_FOREVER); | ||||||
|  |  | ||||||
|  |   return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerMacosx::Stop() { | ||||||
|  |   if (!inited_) return -1; | ||||||
|  |   if (!impl_->stream) return -1; | ||||||
|  |  | ||||||
|  |   dispatch_semaphore_t sema = dispatch_semaphore_create(0); | ||||||
|  |   [impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) { | ||||||
|  |     if (error) { | ||||||
|  |       LOG_ERROR("stopCaptureWithCompletionHandler error: {}", | ||||||
|  |                 std::string([error.localizedDescription UTF8String])); | ||||||
|  |     } | ||||||
|  |     dispatch_semaphore_signal(sema); | ||||||
|  |   }]; | ||||||
|  |   dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); | ||||||
|  |  | ||||||
|  |   impl_->stream = nil; | ||||||
|  |   impl_->delegate = nil; | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerMacosx::Destroy() { | ||||||
|  |   Stop(); | ||||||
|  |   cb_ = nullptr; | ||||||
|  |  | ||||||
|  |   if (impl_) { | ||||||
|  |     impl_->config = nil; | ||||||
|  |     impl_->content = nil; | ||||||
|  |     impl_->mainDisplay = nil; | ||||||
|  |     if (impl_->queue) { | ||||||
|  |       impl_->queue = nil; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   inited_ = false; | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerMacosx::Pause() { return 0; } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerMacosx::Resume() { return Start(); } | ||||||
							
								
								
									
										27
									
								
								src/speaker_capturer/speaker_capturer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/speaker_capturer/speaker_capturer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-07-22 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _SPEAKER_CAPTURER_H_ | ||||||
|  | #define _SPEAKER_CAPTURER_H_ | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
|  |  | ||||||
|  | class SpeakerCapturer { | ||||||
|  |  public: | ||||||
|  |   typedef std::function<void(unsigned char *, size_t, const char *)> | ||||||
|  |       speaker_data_cb; | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual ~SpeakerCapturer() {} | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual int Init(speaker_data_cb cb) = 0; | ||||||
|  |   virtual int Destroy() = 0; | ||||||
|  |   virtual int Start() = 0; | ||||||
|  |   virtual int Stop() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										36
									
								
								src/speaker_capturer/speaker_capturer_factory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/speaker_capturer/speaker_capturer_factory.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-07-22 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _SPEAKER_CAPTURER_FACTORY_H_ | ||||||
|  | #define _SPEAKER_CAPTURER_FACTORY_H_ | ||||||
|  |  | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include "speaker_capturer_wasapi.h" | ||||||
|  | #elif __linux__ | ||||||
|  | #include "speaker_capturer_linux.h" | ||||||
|  | #elif __APPLE__ | ||||||
|  | #include "speaker_capturer_macosx.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | class SpeakerCapturerFactory { | ||||||
|  |  public: | ||||||
|  |   virtual ~SpeakerCapturerFactory() {} | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   SpeakerCapturer* Create() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     return new SpeakerCapturerWasapi(); | ||||||
|  | #elif __linux__ | ||||||
|  |     return new SpeakerCapturerLinux(); | ||||||
|  | #elif __APPLE__ | ||||||
|  |     return new SpeakerCapturerMacosx(); | ||||||
|  | #else | ||||||
|  |     return nullptr; | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										101
									
								
								src/speaker_capturer/windows/speaker_capturer_wasapi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/speaker_capturer/windows/speaker_capturer_wasapi.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | #include "speaker_capturer_wasapi.h" | ||||||
|  |  | ||||||
|  | #include "rd_log.h" | ||||||
|  |  | ||||||
|  | #define MINIAUDIO_IMPLEMENTATION | ||||||
|  | #include "miniaudio.h" | ||||||
|  |  | ||||||
|  | #define SAVE_AUDIO_FILE 0 | ||||||
|  |  | ||||||
|  | static ma_device_config device_config_; | ||||||
|  | static ma_device device_; | ||||||
|  | static ma_format format_ = ma_format_s16; | ||||||
|  | static ma_uint32 sample_rate_ = ma_standard_sample_rate_48000; | ||||||
|  | static ma_uint32 channels_ = 1; | ||||||
|  | static FILE* fp_ = nullptr; | ||||||
|  |  | ||||||
|  | void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, | ||||||
|  |                    ma_uint32 frameCount) { | ||||||
|  |   SpeakerCapturerWasapi* ptr = (SpeakerCapturerWasapi*)pDevice->pUserData; | ||||||
|  |   if (ptr) { | ||||||
|  |     if (SAVE_AUDIO_FILE) { | ||||||
|  |       fwrite(pInput, frameCount * ma_get_bytes_per_frame(format_, channels_), 1, | ||||||
|  |              fp_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ptr->GetCallback()((unsigned char*)pInput, | ||||||
|  |                        frameCount * ma_get_bytes_per_frame(format_, channels_), | ||||||
|  |                        "audio"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   (void)pOutput; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SpeakerCapturerWasapi::speaker_data_cb SpeakerCapturerWasapi::GetCallback() { | ||||||
|  |   return cb_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SpeakerCapturerWasapi::SpeakerCapturerWasapi() {} | ||||||
|  |  | ||||||
|  | SpeakerCapturerWasapi::~SpeakerCapturerWasapi() { | ||||||
|  |   if (SAVE_AUDIO_FILE) { | ||||||
|  |     fclose(fp_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerWasapi::Init(speaker_data_cb cb) { | ||||||
|  |   if (inited_) { | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   cb_ = cb; | ||||||
|  |  | ||||||
|  |   if (SAVE_AUDIO_FILE) { | ||||||
|  |     fopen_s(&fp_, "system_audio.pcm", "wb"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ma_result result; | ||||||
|  |   ma_backend backends[] = {ma_backend_wasapi}; | ||||||
|  |  | ||||||
|  |   device_config_ = ma_device_config_init(ma_device_type_loopback); | ||||||
|  |   device_config_.capture.pDeviceID = NULL; | ||||||
|  |   device_config_.capture.format = format_; | ||||||
|  |   device_config_.capture.channels = channels_; | ||||||
|  |   device_config_.sampleRate = sample_rate_; | ||||||
|  |   device_config_.dataCallback = data_callback; | ||||||
|  |   device_config_.pUserData = this; | ||||||
|  |  | ||||||
|  |   result = ma_device_init_ex(backends, sizeof(backends) / sizeof(backends[0]), | ||||||
|  |                              NULL, &device_config_, &device_); | ||||||
|  |   if (result != MA_SUCCESS) { | ||||||
|  |     LOG_ERROR("Failed to initialize loopback device"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   inited_ = true; | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerWasapi::Start() { | ||||||
|  |   ma_result result = ma_device_start(&device_); | ||||||
|  |   if (result != MA_SUCCESS) { | ||||||
|  |     ma_device_uninit(&device_); | ||||||
|  |     LOG_ERROR("Failed to start device"); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerWasapi::Stop() { | ||||||
|  |   ma_device_stop(&device_); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerWasapi::Destroy() { | ||||||
|  |   ma_device_uninit(&device_); | ||||||
|  |   return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SpeakerCapturerWasapi::Pause() { return 0; } | ||||||
							
								
								
									
										35
									
								
								src/speaker_capturer/windows/speaker_capturer_wasapi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/speaker_capturer/windows/speaker_capturer_wasapi.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | /* | ||||||
|  |  * @Author: DI JUNKUN | ||||||
|  |  * @Date: 2024-08-15 | ||||||
|  |  * Copyright (c) 2024 by DI JUNKUN, All Rights Reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _SPEAKER_CAPTURER_WASAPI_H_ | ||||||
|  | #define _SPEAKER_CAPTURER_WASAPI_H_ | ||||||
|  |  | ||||||
|  | #include "speaker_capturer.h" | ||||||
|  |  | ||||||
|  | class SpeakerCapturerWasapi : public SpeakerCapturer { | ||||||
|  |  public: | ||||||
|  |   SpeakerCapturerWasapi(); | ||||||
|  |   ~SpeakerCapturerWasapi(); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   virtual int Init(speaker_data_cb cb); | ||||||
|  |   virtual int Destroy(); | ||||||
|  |   virtual int Start(); | ||||||
|  |   virtual int Stop(); | ||||||
|  |  | ||||||
|  |   int Pause(); | ||||||
|  |   int Resume(); | ||||||
|  |  | ||||||
|  |   speaker_data_cb GetCallback(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   speaker_data_cb cb_ = nullptr; | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   bool inited_ = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -1,232 +0,0 @@ | |||||||
| extern "C" { |  | ||||||
| #include <libavcodec/avcodec.h> |  | ||||||
| #include <libavdevice/avdevice.h> |  | ||||||
| #include <libavfilter/avfilter.h> |  | ||||||
| #include <libavformat/avformat.h> |  | ||||||
| #include <libavutil/channel_layout.h> |  | ||||||
| #include <libavutil/imgutils.h> |  | ||||||
| #include <libavutil/opt.h> |  | ||||||
| #include <libavutil/samplefmt.h> |  | ||||||
| #include <libswresample/swresample.h> |  | ||||||
| #include <libswscale/swscale.h> |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| static int get_format_from_sample_fmt(const char **fmt, |  | ||||||
|                                       enum AVSampleFormat sample_fmt) { |  | ||||||
|   int i; |  | ||||||
|   struct sample_fmt_entry { |  | ||||||
|     enum AVSampleFormat sample_fmt; |  | ||||||
|     const char *fmt_be, *fmt_le; |  | ||||||
|   } sample_fmt_entries[] = { |  | ||||||
|       {AV_SAMPLE_FMT_U8, "u8", "u8"}, |  | ||||||
|       {AV_SAMPLE_FMT_S16, "s16be", "s16le"}, |  | ||||||
|       {AV_SAMPLE_FMT_S32, "s32be", "s32le"}, |  | ||||||
|       {AV_SAMPLE_FMT_FLT, "f32be", "f32le"}, |  | ||||||
|       {AV_SAMPLE_FMT_DBL, "f64be", "f64le"}, |  | ||||||
|   }; |  | ||||||
|   *fmt = NULL; |  | ||||||
|  |  | ||||||
|   for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) { |  | ||||||
|     struct sample_fmt_entry *entry = &sample_fmt_entries[i]; |  | ||||||
|     if (sample_fmt == entry->sample_fmt) { |  | ||||||
|       *fmt = AV_NE(entry->fmt_be, entry->fmt_le); |  | ||||||
|       return 0; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fprintf(stderr, "Sample format %s not supported as output format\n", |  | ||||||
|           av_get_sample_fmt_name(sample_fmt)); |  | ||||||
|   return AVERROR(EINVAL); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Fill dst buffer with nb_samples, generated starting from t. <20><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD> |  | ||||||
|  */ |  | ||||||
| static void fill_samples(double *dst, int nb_samples, int nb_channels, |  | ||||||
|                          int sample_rate, double *t) { |  | ||||||
|   int i, j; |  | ||||||
|   double tincr = 1.0 / sample_rate, *dstp = dst; |  | ||||||
|   const double c = 2 * M_PI * 440.0; |  | ||||||
|  |  | ||||||
|   /* generate sin tone with 440Hz frequency and duplicated channels */ |  | ||||||
|   for (i = 0; i < nb_samples; i++) { |  | ||||||
|     *dstp = sin(c * *t); |  | ||||||
|     for (j = 1; j < nb_channels; j++) dstp[j] = dstp[0]; |  | ||||||
|     dstp += nb_channels; |  | ||||||
|     *t += tincr; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| int main(int argc, char **argv) { |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   int64_t src_ch_layout = AV_CH_LAYOUT_MONO; |  | ||||||
|   int src_rate = 44100; |  | ||||||
|   enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_DBL; |  | ||||||
|   int src_nb_channels = 0; |  | ||||||
|   uint8_t **src_data = NULL;  // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8> |  | ||||||
|   int src_linesize; |  | ||||||
|   int src_nb_samples = 1024; |  | ||||||
|  |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO; |  | ||||||
|   int dst_rate = 48000; |  | ||||||
|   enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16; |  | ||||||
|   int dst_nb_channels = 0; |  | ||||||
|   uint8_t **dst_data = NULL;  // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8> |  | ||||||
|   int dst_linesize; |  | ||||||
|   int dst_nb_samples; |  | ||||||
|   int max_dst_nb_samples; |  | ||||||
|  |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD> |  | ||||||
|   const char *dst_filename = NULL;  // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD><EFBFBD><F3B2A5B7><EFBFBD>֤ |  | ||||||
|   FILE *dst_file; |  | ||||||
|  |  | ||||||
|   int dst_bufsize; |  | ||||||
|   const char *fmt; |  | ||||||
|  |  | ||||||
|   // <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5> |  | ||||||
|   struct SwrContext *swr_ctx; |  | ||||||
|  |  | ||||||
|   double t; |  | ||||||
|   int ret; |  | ||||||
|  |  | ||||||
|   dst_filename = "res.pcm"; |  | ||||||
|  |  | ||||||
|   dst_file = fopen(dst_filename, "wb"); |  | ||||||
|   if (!dst_file) { |  | ||||||
|     fprintf(stderr, "Could not open destination file %s\n", dst_filename); |  | ||||||
|     exit(1); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   /* create resampler context */ |  | ||||||
|   swr_ctx = swr_alloc(); |  | ||||||
|   if (!swr_ctx) { |  | ||||||
|     fprintf(stderr, "Could not allocate resampler context\n"); |  | ||||||
|     ret = AVERROR(ENOMEM); |  | ||||||
|     goto end; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   /* set options */ |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0); |  | ||||||
|   av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0); |  | ||||||
|   av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0); |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0); |  | ||||||
|   av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0); |  | ||||||
|   av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0); |  | ||||||
|  |  | ||||||
|   // <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD> |  | ||||||
|   /* initialize the resampling context */ |  | ||||||
|   if ((ret = swr_init(swr_ctx)) < 0) { |  | ||||||
|     fprintf(stderr, "Failed to initialize the resampling context\n"); |  | ||||||
|     goto end; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* allocate source and destination samples buffers */ |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout); |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD> |  | ||||||
|   ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, |  | ||||||
|                                            src_nb_channels, src_nb_samples, |  | ||||||
|                                            src_sample_fmt, 0); |  | ||||||
|   if (ret < 0) { |  | ||||||
|     fprintf(stderr, "Could not allocate source samples\n"); |  | ||||||
|     goto end; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* compute the number of converted samples: buffering is avoided |  | ||||||
|    * ensuring that the output buffer will contain at least all the |  | ||||||
|    * converted input samples */ |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> |  | ||||||
|   max_dst_nb_samples = dst_nb_samples = |  | ||||||
|       av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP); |  | ||||||
|  |  | ||||||
|   /* buffer is going to be directly written to a rawaudio file, no alignment |  | ||||||
|    */ |  | ||||||
|   dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout); |  | ||||||
|   // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD> |  | ||||||
|   ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, |  | ||||||
|                                            dst_nb_channels, dst_nb_samples, |  | ||||||
|                                            dst_sample_fmt, 0); |  | ||||||
|   if (ret < 0) { |  | ||||||
|     fprintf(stderr, "Could not allocate destination samples\n"); |  | ||||||
|     goto end; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   t = 0; |  | ||||||
|   do { |  | ||||||
|     /* generate synthetic audio */ |  | ||||||
|     // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ |  | ||||||
|     fill_samples((double *)src_data[0], src_nb_samples, src_nb_channels, |  | ||||||
|                  src_rate, &t); |  | ||||||
|  |  | ||||||
|     /* compute destination number of samples */ |  | ||||||
|     int64_t delay = swr_get_delay(swr_ctx, src_rate); |  | ||||||
|     dst_nb_samples = |  | ||||||
|         av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP); |  | ||||||
|     if (dst_nb_samples > max_dst_nb_samples) { |  | ||||||
|       av_freep(&dst_data[0]); |  | ||||||
|       ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels, |  | ||||||
|                              dst_nb_samples, dst_sample_fmt, 1); |  | ||||||
|       if (ret < 0) break; |  | ||||||
|       max_dst_nb_samples = dst_nb_samples; |  | ||||||
|     } |  | ||||||
|     //        int fifo_size = swr_get_out_samples(swr_ctx,src_nb_samples); |  | ||||||
|     //        printf("fifo_size:%d\n", fifo_size); |  | ||||||
|     //        if(fifo_size < 1024) |  | ||||||
|     //            continue; |  | ||||||
|  |  | ||||||
|     /* convert to destination format */ |  | ||||||
|     //        ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const |  | ||||||
|     //        uint8_t **)src_data, src_nb_samples); |  | ||||||
|     ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, |  | ||||||
|                       (const uint8_t **)src_data, src_nb_samples); |  | ||||||
|     if (ret < 0) { |  | ||||||
|       fprintf(stderr, "Error while converting\n"); |  | ||||||
|       goto end; |  | ||||||
|     } |  | ||||||
|     dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, |  | ||||||
|                                              ret, dst_sample_fmt, 1); |  | ||||||
|     if (dst_bufsize < 0) { |  | ||||||
|       fprintf(stderr, "Could not get sample buffer size\n"); |  | ||||||
|       goto end; |  | ||||||
|     } |  | ||||||
|     printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret); |  | ||||||
|     fwrite(dst_data[0], 1, dst_bufsize, dst_file); |  | ||||||
|   } while (t < 10); |  | ||||||
|  |  | ||||||
|   ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, NULL, 0); |  | ||||||
|   if (ret < 0) { |  | ||||||
|     fprintf(stderr, "Error while converting\n"); |  | ||||||
|     goto end; |  | ||||||
|   } |  | ||||||
|   dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret, |  | ||||||
|                                            dst_sample_fmt, 1); |  | ||||||
|   if (dst_bufsize < 0) { |  | ||||||
|     fprintf(stderr, "Could not get sample buffer size\n"); |  | ||||||
|     goto end; |  | ||||||
|   } |  | ||||||
|   printf("flush in:%d out:%d\n", 0, ret); |  | ||||||
|   fwrite(dst_data[0], 1, dst_bufsize, dst_file); |  | ||||||
|  |  | ||||||
|   if ((ret = get_format_from_sample_fmt(&fmt, dst_sample_fmt)) < 0) goto end; |  | ||||||
|   fprintf(stderr, |  | ||||||
|           "Resampling succeeded. Play the output file with the command:\n" |  | ||||||
|           "ffplay -f %s -channel_layout %" PRId64 " -channels %d -ar %d %s\n", |  | ||||||
|           fmt, dst_ch_layout, dst_nb_channels, dst_rate, dst_filename); |  | ||||||
|  |  | ||||||
| end: |  | ||||||
|   fclose(dst_file); |  | ||||||
|  |  | ||||||
|   if (src_data) av_freep(&src_data[0]); |  | ||||||
|   av_freep(&src_data); |  | ||||||
|  |  | ||||||
|   if (dst_data) av_freep(&dst_data[0]); |  | ||||||
|   av_freep(&dst_data); |  | ||||||
|  |  | ||||||
|   swr_free(&swr_ctx); |  | ||||||
|   return ret < 0; |  | ||||||
| } |  | ||||||
| @@ -1,53 +0,0 @@ | |||||||
| #include <SDL2/SDL.h> |  | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { |  | ||||||
|   int ret; |  | ||||||
|   SDL_AudioSpec wanted_spec, obtained_spec; |  | ||||||
|  |  | ||||||
|   // Initialize SDL |  | ||||||
|   if (SDL_Init(SDL_INIT_AUDIO) < 0) { |  | ||||||
|     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL: %s", |  | ||||||
|                  SDL_GetError()); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Set audio format |  | ||||||
|   wanted_spec.freq = 44100;  // Sample rate |  | ||||||
|   wanted_spec.format = |  | ||||||
|       AUDIO_F32SYS;          // Sample format (32-bit float, system byte order) |  | ||||||
|   wanted_spec.channels = 2;  // Number of channels (stereo) |  | ||||||
|   wanted_spec.samples = 1024;   // Buffer size (in samples) |  | ||||||
|   wanted_spec.callback = NULL;  // Audio callback function (not used here) |  | ||||||
|  |  | ||||||
|   // Open audio device |  | ||||||
|   ret = SDL_OpenAudio(&wanted_spec, &obtained_spec); |  | ||||||
|   if (ret < 0) { |  | ||||||
|     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, |  | ||||||
|                  "Failed to open audio device: %s", SDL_GetError()); |  | ||||||
|     return -1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Start playing audio |  | ||||||
|   SDL_PauseAudio(0); |  | ||||||
|  |  | ||||||
|   // Write PCM data to audio buffer |  | ||||||
|   float *pcm_data = ...;    // PCM data buffer (float, interleaved) |  | ||||||
|   int pcm_data_size = ...;  // Size of PCM data buffer (in bytes) |  | ||||||
|   int bytes_written = SDL_QueueAudio(0, pcm_data, pcm_data_size); |  | ||||||
|  |  | ||||||
|   // Wait until audio buffer is empty |  | ||||||
|   while (SDL_GetQueuedAudioSize(0) > 0) { |  | ||||||
|     SDL_Delay(100); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Stop playing audio |  | ||||||
|   SDL_PauseAudio(1); |  | ||||||
|  |  | ||||||
|   // Close audio device |  | ||||||
|   SDL_CloseAudio(); |  | ||||||
|  |  | ||||||
|   // Quit SDL |  | ||||||
|   SDL_Quit(); |  | ||||||
|  |  | ||||||
|   return 0; |  | ||||||
| } |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user