393 Commits

Author SHA1 Message Date
dijunkun
9912a88a13 [feat] github actions support linux aarch64 platform 2025-08-22 18:43:07 +08:00
dijunkun
8a8f2cd5d7 [feat] update minirtc to support linux aarch64 platform 2025-08-21 20:16:55 +08:00
dijunkun
e77e16d9c2 [fix] Add validity check for props 2025-08-20 16:42:31 +08:00
dijunkun
1616d0ec33 [fix] Add validity check for props 2025-08-20 16:37:29 +08:00
dijunkun
374453775f [fix] fix connection status window gui display 2025-08-19 17:47:09 +08:00
dijunkun
6f8fd6a030 [fix] fix crash when transmission id does not exist 2025-08-19 16:33:29 +08:00
dijunkun
c7166975b3 [fix] fix version number replace for pages 2025-08-15 20:52:42 +08:00
dijunkun
2ae5e5a969 [feat] do not generate dmg for macOS 2025-08-15 20:34:27 +08:00
dijunkun
85cdc995c5 [fix] fix artifacts name with version number 2025-08-15 20:07:18 +08:00
dijunkun
b051c8a059 [fix] fix artifacts name with version number 2025-08-15 19:55:03 +08:00
dijunkun
508b7dc7a1 [feat] add script to update the version number in github pages 2025-08-15 19:41:04 +08:00
dijunkun
f203566b81 [feat] use dynamic version number 2025-08-15 19:35:16 +08:00
dijunkun
8360c1725f [feat] add release scripts 2025-08-15 15:41:10 +08:00
dijunkun
3b00fdef71 [fix] fix github actions scripts 2025-08-14 16:58:29 +08:00
dijunkun
f2664daaec [feat] add github actions scripts 2025-08-10 00:03:13 +08:00
dijunkun
1a1bbe3e4d [fix] fix cache file and cert file read 2025-08-09 23:59:53 +08:00
dijunkun
4c898c5f07 [fix] save log files into XDG_CACHE_HOME/CrossDesk/logs for Linux platform 2025-07-25 18:11:40 +08:00
dijunkun
5d704edbc8 [fix] use std::filesystem::create_directories instead of std::filesystem::create_directory to create folder 2025-07-24 15:52:52 +08:00
dijunkun
b23038bac6 [feat] update signal server domain 2025-07-23 17:30:30 +08:00
dijunkun
147b9b2ddc [fix] fix thumbnail order by using std::vector instead of std::unordered_map 2025-07-23 16:38:29 +08:00
dijunkun
1327b995f2 [fix] fix compile error on MacOSX 2025-07-23 11:31:32 +08:00
dijunkun
e8d7ec8daf [feat] save log and cache files into user folder 2025-07-22 18:55:12 +08:00
dijunkun
3bf68a396f [feat] add PathManager class in order to manage config/log/cache file path 2025-07-16 18:22:33 +08:00
kunkundi
29077fad5e Merge branch 'multi-stream' of https://github.com/kunkundi/crossdesk into multi-stream 2025-07-16 17:10:18 +08:00
kunkundi
69367bdadf [fix] fix same memory leak issue 2025-07-16 17:09:51 +08:00
dijunkun
f650b5a6ef [feat] use unordered map to store recent connection info so that the most recent connections are shown at the front 2025-07-15 17:34:25 +08:00
kunkundi
75021b74ef [fix] use temp frame to store the audio frame that will be sent 2025-07-15 17:18:00 +08:00
dijunkun
205421621b [fix] fix local id when using as client 2025-07-15 16:35:05 +08:00
dijunkun
4bb5607702 [fix] fix restart audio capturer error on MacOSX 2025-07-15 16:05:57 +08:00
kunkundi
78c54136e2 [fix] fix crash when Stop() called in SpeakerCapturerLinux 2025-07-15 15:38:58 +08:00
kunkundi
8fe8f4fd7e [feat] audio capture supported on Linux 2025-07-11 17:17:05 +08:00
dijunkun
c13e2613b6 [feat] audio capture supported on MacOSX 2025-07-02 19:25:21 +08:00
dijunkun
e52ec6cde2 [fix] use domain name instead of ip address to enable tls connection 2025-07-01 17:38:24 +08:00
dijunkun
ad6a24b230 [feat] add tls cert file path in configure parameters 2025-07-01 13:55:41 +08:00
dijunkun
ad60815d83 [fix] fix thumbnails deletion 2025-07-01 11:13:24 +08:00
dijunkun
c47a90bf9c [feat] update project name 2025-06-25 17:33:54 +08:00
dijunkun
3901e26c37 [fix] fix thumbnail filename which stores remote id, remote host name and remote password 2025-06-20 18:42:29 +08:00
dijunkun
41c27139a0 [feat] password modification supported 2025-06-20 17:50:04 +08:00
dijunkun
a31ca21ef4 [feat] receive id and password when login to server in the first time 2025-06-20 00:57:56 +08:00
dijunkun
57939ccd00 [feat] update submodule info 2025-06-18 00:06:47 +08:00
dijunkun
fc14f7b9c6 [fix] register minirtc as submodule properly 2025-06-17 23:29:02 +08:00
dijunkun
cfcf6dd9cb [feat] replace submodule projectx with minirtc at new path 2025-06-17 17:11:43 +08:00
dijunkun
854c3a72c7 [feat] modify project name to DeskPort 2025-06-17 14:24:41 +08:00
dijunkun
8004d25ca3 [feat] limit screen capturing to 30 fps 2025-06-07 02:46:51 +08:00
dijunkun
b04dfd188a [fix] fix SCStream stop error 2025-06-03 10:44:52 +08:00
dijunkun
383ace2493 [fix] only receive remote host info once 2025-06-03 10:33:32 +08:00
dijunkun
094204361c [feat] enable screen switch on Linux platform 2025-05-29 18:09:34 +08:00
dijunkun
d72c6d9df7 [feat] enable mouse dragging for MacOSx 2025-05-29 17:05:56 +08:00
dijunkun
05d73ebe9a [feat] add notification text in stream windows when connection failed/disconnected/closed 2025-05-29 16:51:18 +08:00
dijunkun
a8b5e934b8 [feat] implementation for ScreenCapturerSck::Destroy() and ScreenCapturerSck::Stop() 2025-05-29 15:49:42 +08:00
dijunkun
920677a433 [fix] use black to fill the blank areas around the thumbnail 2025-05-29 15:02:43 +08:00
dijunkun
b86d3d42ee [fix] fix color space for thumbnail 2025-05-29 14:17:33 +08:00
dijunkun
818dab764f [fix] do not recreate screen capturer when reload configuration file 2025-05-28 19:15:02 +08:00
dijunkun
7ea26854af [fix] fix strncpy error 2025-05-28 17:25:39 +08:00
dijunkun
f34b55b88a [fix] do not need to call SDL_CloseAudioDevice() before SDL_Quit() 2025-05-28 16:02:37 +08:00
dijunkun
67168f7735 [fix] destroy screen capturer only when clean up 2025-05-27 10:17:25 +08:00
dijunkun
6e69c37056 [feat] use display name as video stream id 2025-05-26 15:29:46 +08:00
dijunkun
8f5dd21e75 [fix] remove data length check in OnReceiveDataBufferCb 2025-05-22 18:09:23 +08:00
dijunkun
e2dfeb1186 [fix] fix RemoteAction read from received data 2025-05-22 17:18:06 +08:00
dijunkun
96c7d3174b [feat] improve the performance of ScreenCapturerSck 2025-05-22 17:17:15 +08:00
dijunkun
97f6c18296 [feat] update ScreenCapturerSck 2025-05-20 19:11:17 +08:00
dijunkun
9138ca771f [fix] do not send mouse click event when cursor is on control bar and display selectable 2025-05-16 19:06:25 +08:00
dijunkun
abc6b17a3b [fix] display index shown in control bar should start from 1 2025-05-16 18:51:57 +08:00
dijunkun
8e0524bf60 [feat] display switch supported on MacOSx platform 2025-05-16 18:49:20 +08:00
dijunkun
6ebf583a13 [fix] fix compile error on MacOSx 2025-05-15 19:54:17 +08:00
dijunkun
eca4f614c8 [feat] cursor mapping in different displays supported 2025-05-15 19:47:08 +08:00
dijunkun
11358e0b60 [feat] enable mouse control for multi-display 2025-05-15 18:37:59 +08:00
dijunkun
0a1014dded [fix] use serialize and deserialize function to process RemoteAction::HostInfo 2025-05-15 17:52:40 +08:00
dijunkun
a6beb48b1f [feat] display switch supported on Windows platform 2025-05-15 16:37:03 +08:00
dijunkun
5eff530ab9 [fix] fix ScreenCapturerWgc crash during clean up 2025-05-15 15:27:44 +08:00
dijunkun
e6e237279c [feat] update rtc module 2025-05-13 21:47:05 +08:00
dijunkun
6fe46f6181 [feat] add a=m lines in sdp for multi-stream 2025-05-12 22:09:08 +08:00
dijunkun
9b5023645c [feat] display selection supported on Windows platform 2025-05-09 22:30:56 +08:00
dijunkun
c2da4ebcb7 [feat] add remote display selection button in control bar 2025-05-09 17:23:27 +08:00
dijunkun
dc05f2405f [feat] add display selection method for Windows platform 2025-05-08 17:40:14 +08:00
dijunkun
e2a469ed65 [feat] remove dependence on ffmpeg for MacOSx 2025-05-08 11:33:04 +08:00
dijunkun
e89d385f99 [fix] remove redundant link flags on Linux platform 2025-05-08 11:07:04 +08:00
dijunkun
0a61dcc2be [fix] update keyboard converter 2025-05-07 19:47:09 +08:00
dijunkun
250fd49406 [feat] mouse/keyboard control and screen capture supported by using X11 on Linux platform 2025-05-07 19:37:41 +08:00
dijunkun
93bd5b2660 [fix] fix control bar display error in fullscreen mode when multiple connections established 2025-05-06 19:49:08 +08:00
dijunkun
d05ff9f905 [fix] update rtc module 2025-05-06 18:58:52 +08:00
dijunkun
f3451a5fa1 [fix] fix compile error on Linux 2025-05-06 18:39:30 +08:00
dijunkun
a9084ba98d [fix] fix control bar layout and position in stream window 2025-04-30 18:18:55 +08:00
dijunkun
893051f9b3 [fix] fix crash due to invalid iterator 2025-04-30 15:16:32 +08:00
dijunkun
dfbd4317b7 [feat] use tab bar to manage stream windows 2025-04-29 22:16:10 +08:00
dijunkun
532ad0eb51 [feat] support multi-stream and enable stream windows docking 2025-04-28 20:49:11 +08:00
dijunkun
184983d857 [fix] fix FindThumbnailPath() 2025-04-27 18:46:45 +08:00
dijunkun
c33e4bbe0e [fix] use relative percentage-based mouse mapping instead of absolute positions 2025-04-27 14:02:20 +08:00
dijunkun
4b9e86c424 [fix] fix recent connection deletion 2025-04-27 10:53:44 +08:00
dijunkun
f7f8ddd925 [fix] fix log error 2025-04-18 15:39:31 +08:00
dijunkun
188b1758f2 [feat] remove screen resolution info in HostInfo 2025-04-17 16:58:45 +08:00
dijunkun
9705374b9a [feat] save thumbnail as black picture if have not received a full frame 2025-04-16 16:47:30 +08:00
dijunkun
22aed6ea53 [fix] send host info when connected 2025-04-16 16:13:54 +08:00
dijunkun
44c5fde086 [fix] send host info when connected 2025-04-16 15:36:25 +08:00
dijunkun
2cd5a9dd76 [fix] do not send click event if cursor clicks the button which is on control bar 2025-04-16 14:47:44 +08:00
dijunkun
fa73447f88 [fix] fix mouse wheel mapping on Windows 2025-04-16 14:33:11 +08:00
dijunkun
536fe17055 [fix] fix wheel event grabbing 2025-04-15 17:32:23 +08:00
dijunkun
662cbbc3cc [feat] mouse middle button and wheel supported 2025-04-15 14:26:34 +08:00
dijunkun
69a8503ee1 [fix] need to DestroyPeer if NoSuchTransmissionId 2025-04-15 10:28:19 +08:00
dijunkun
b906bfcafd [fix] fix audio command sending 2025-04-14 17:33:35 +08:00
dijunkun
f891a047d6 [fix] flush STREAM_FRASH event when stream closed 2025-04-14 17:15:00 +08:00
dijunkun
dce32672a6 [fix] fix cursor mapping 2025-04-14 16:59:48 +08:00
dijunkun
2774991107 [fix] only send hostname and resolution before sending first frame 2025-04-14 16:49:31 +08:00
dijunkun
b7ce8b6299 [fix] fix render resolution 2025-04-14 16:31:46 +08:00
dijunkun
700fb2ec14 [feat] send server resolution before sending first frame 2025-04-14 16:12:55 +08:00
dijunkun
62d14587cd [fix] fix compile warning 2025-04-11 16:09:11 +08:00
dijunkun
824d9243cc [fix] fix join problem that when join to a server which does not exist in recent connections 2025-04-10 17:35:21 +08:00
dijunkun
5351b4da0e [fix] do not show stream window if the client is only in server mode 2025-04-10 17:19:19 +08:00
dijunkun
fcd0488624 [fix] fix control bar position when stream window created 2025-04-10 16:46:53 +08:00
dijunkun
193e905b28 [fix] save password only when join the connection successfully 2025-04-09 17:31:59 +08:00
dijunkun
f48085c69a [fix] fix connection error when reinput password 2025-04-09 17:17:30 +08:00
dijunkun
cf078be53f [feat] separate function Run() into several functions 2025-04-09 16:51:33 +08:00
dijunkun
badcd6a05c [fix] fix mouse contorl error 2025-04-09 15:47:24 +08:00
dijunkun
d828bd736d [feat] use peer map to manage multiple client instances 2025-04-08 17:34:33 +08:00
dijunkun
1e014bdae3 [fix] fix crash due to encoder releasing 2025-03-25 17:37:12 +08:00
dijunkun
334ab182db [fix] fix crash when try to close connection 2025-03-24 17:40:44 +08:00
dijunkun
f52de76bfc [feat] update rtc api 2025-03-19 18:38:13 +08:00
dijunkun
6c7d0e8cab [feat] support FIR and NACK 2025-03-07 18:37:26 +08:00
dijunkun
7229ae856e [fix] fix video capture timestamp 2025-02-28 15:53:03 +08:00
dijunkun
597d760d24 [feat] fix crash and update rtc module 2025-02-06 17:37:18 +08:00
dijunkun
bcf61e1ada [fix] fix crash due to sub stream window properties 2025-02-05 23:31:33 +08:00
dijunkun
08e596714b [fix] fix h264 rtp packetization error 2025-02-05 17:29:26 +08:00
dijunkun
bff577ba34 [feat] update rtc module 2025-01-23 17:29:26 +08:00
dijunkun
4df935b9d2 [fix] fix log name conflict 2025-01-22 17:34:35 +08:00
dijunkun
9bb560314b [fix] use reinterpret_cast to convert u8"x" to const char* 2024-12-18 17:29:13 +08:00
dijunkun
1a0c5e8b42 [feat] use client_properties_ and server_properties_ to store streams properties 2024-12-03 17:22:15 +08:00
dijunkun
011919d0e7 [feat] use SubStreamWindowProperties to store sub stream properties 2024-12-02 17:30:51 +08:00
dijunkun
418ab7a1d2 [fix] fix net traffic stats display 2024-12-02 11:03:55 +08:00
dijunkun
6a2c9af316 [feat] loss rate display supported 2024-12-01 17:00:59 +08:00
dijunkun
eaabf478cc [feat] update net statistics module 2024-11-29 17:54:15 +08:00
dijunkun
ffe3ca76af [feat] switch rtc module into new branch 'qos' 2024-11-28 18:27:34 +08:00
dijunkun
c5a6302220 [fix] update rtc module 2024-11-28 15:16:48 +08:00
dijunkun
9b2f81690f [fix] fix stream window scale error 2024-11-27 20:28:42 +08:00
dijunkun
9621e6b570 [fix] fix right shift key mapping on MacOSX 2024-11-27 17:33:54 +08:00
dijunkun
ce3ae03bef [fix] prevent cursor relocation when stream window resized 2024-11-27 17:24:45 +08:00
dijunkun
e0457213ea [fix] fix display errors when stream window resized 2024-11-27 16:14:14 +08:00
dijunkun
10cdc440a0 [fix] fix key press/release event 2024-11-27 14:34:54 +08:00
dijunkun
ddc62c90bb [fix] fix all unused variables and type conversions on Windows 2024-11-27 11:17:43 +08:00
dijunkun
9e70d0e8fc [fix] fix all unused variables and type conversions on MacOSX 2024-11-27 10:57:09 +08:00
dijunkun
4533d53ba8 [fix] fix all unused variables and type conversions 2024-11-26 23:31:06 +08:00
dijunkun
0caa243006 [fix] fix unused variables and type conversions 2024-11-26 17:31:37 +08:00
dijunkun
1e58abdfdd [feat] rewrite log module 2024-11-26 16:05:28 +08:00
dijunkun
370ac08d09 [fix] use new rtc api 2024-11-26 15:06:55 +08:00
dijunkun
31b6b2736c [fix] fix control and shift keys convertion from Win to MacOSX 2024-11-25 17:29:49 +08:00
dijunkun
abd22ab7f1 [fix] fix Win/MacOSX keycodes convertion 2024-11-25 17:15:22 +08:00
dijunkun
5ab68988aa [fix] fix stream window grabbing cannot be released after connection closed 2024-11-22 18:12:36 +08:00
dijunkun
ba3edcc02a [feat] keyboard control supported on MacOSX 2024-11-22 18:05:13 +08:00
dijunkun
8414a57a5b [feat] keyboard control supported on Windows 2024-11-22 17:17:33 +08:00
dijunkun
3f777e4662 [fix] fix compile error 2024-11-22 16:45:14 +08:00
dijunkun
52828183a1 [feat] keyboard capture supported on Windows 2024-11-22 16:39:01 +08:00
dijunkun
df7489f8e2 [fix] update imgui to v1.91.5-docking to fix: inputText cannot input text after add a space in remote id 2024-11-21 18:40:42 +08:00
dijunkun
4fea7d86e1 [feat] cursor will move into remote id input box automaticlly if the last input remote id does not exist 2024-11-21 16:20:25 +08:00
dijunkun
cb17b7c8db [fix] fix setting item [enable turn] 2024-11-21 16:05:10 +08:00
dijunkun
cf7ef89bf2 [fix] fix cursor position when stream window initialization 2024-11-21 10:34:04 +08:00
dijunkun
2d2a578800 [fix] fix control window position when stream window size changed 2024-11-20 19:17:33 +08:00
dijunkun
0ba12f3ccf [fix] only display client side net status if connected to itself 2024-11-19 22:33:59 +08:00
dijunkun
5ac603977d [fix] fix net traffic stats display 2024-11-19 17:31:31 +08:00
dijunkun
25d5a80bee [fix] reset net traffic stats after connection closed 2024-11-19 17:26:01 +08:00
dijunkun
c9d452a025 [feat] disable settings modification during streaming 2024-11-19 17:21:27 +08:00
dijunkun
8132d62c02 [feat] show net traffic stats in control bar 2024-11-19 16:56:30 +08:00
dijunkun
ca32ebeefe [feat] net traffic stats supported 2024-11-18 17:33:09 +08:00
dijunkun
5bf5e9ee25 [feat] change UI layouts 2024-11-18 16:08:54 +08:00
dijunkun
bf097008e7 [feat] use SDL_WINDOW_HIDDEN to delay showing the main window to avoid black window during initialization 2024-11-18 15:10:02 +08:00
dijunkun
59b1208321 [feat] load different size fonts in initialization 2024-11-15 18:19:54 +08:00
dijunkun
84194188f8 [fix] fix redundant recent connection cache file due to remember_password_ flag not being set correctly 2024-11-14 17:26:08 +08:00
dijunkun
a067441fb9 [feat] change recent connections layout 2024-11-14 17:13:38 +08:00
dijunkun
6eac8380b6 [feat] generate random AES128 key and iv during initialization, save them in cache file and load when program starts 2024-11-14 00:49:30 +08:00
dijunkun
5aed8317ca [feat] quick connecting supported 2024-11-13 23:27:06 +08:00
dijunkun
9aed7a19cf [fix] fix AES encrypt and decrypt 2024-11-13 17:58:59 +08:00
dijunkun
c0154be1aa [fix] use APIs in evp.h to encrypt and decrypt 2024-11-13 00:47:54 +08:00
dijunkun
f9d024e971 [feat] use AES to encrypt image names 2024-11-12 17:30:28 +08:00
dijunkun
526eb4bb31 [feat] add trash and connect buttons on recent connection images 2024-11-11 17:05:47 +08:00
dijunkun
f9347cbd49 [feat] use confirm window to confirm recent connections deletion 2024-11-11 10:49:07 +08:00
dijunkun
b6671bdbe7 [feat] delete recent connection supported 2024-11-08 17:52:19 +08:00
dijunkun
edcf5d408c [feat] use horizontal scroll bar to show all recent connections 2024-11-08 14:51:58 +08:00
dijunkun
8c8731909e [feat] remote host names will be shown below thumbnails 2024-11-07 17:30:55 +08:00
dijunkun
de721ac6e3 [feat] use a list to show thumbnails of recent connections 2024-11-07 16:58:25 +08:00
dijunkun
963f1da1d8 [fix] fix redefinition error on MacOSX 2024-11-07 16:32:35 +08:00
dijunkun
4c6159e4d4 [feat] write and load thumbnails supported 2024-11-07 16:29:02 +08:00
dijunkun
e3c2e9ec6d [fix] fix png images write and read 2024-11-07 02:38:35 +08:00
dijunkun
02022bdcdf [feat] add recent connections window 2024-11-06 17:28:11 +08:00
dijunkun
19ea426efc [feat] change UI layouts 2024-11-05 17:29:39 +08:00
dijunkun
863070a8a7 [feat] enable window grab when mouse control enabled 2024-11-04 17:29:26 +08:00
dijunkun
44f9e6a8c9 [fix] fix crash due to multi-context fonts release 2024-11-04 16:13:20 +08:00
dijunkun
087d5d7e52 [feat] use an additional window to show video streams 2024-11-01 20:30:06 +08:00
dijunkun
26fa53f867 [fix] fix imgui layout error 2024-11-01 15:57:15 +08:00
dijunkun
d18af6cbc6 [fix] fix client id generation 2024-10-30 17:25:41 +08:00
dijunkun
b5bb62bd22 [feat] support new screen capture method by using ScreenCaptureKit on MacOSX 2024-10-18 17:20:52 +08:00
dijunkun
9ed3ab9929 [fix] fix the fullscreen error when closing the connection 2024-09-19 16:41:59 +08:00
dijunkun
dca18762e0 [fix] use original screen render resolution to capturing and fix cursor mapping error 2024-09-13 17:08:58 +08:00
dijunkun
fed7c3b103 [fix] fix cursor mapping error 2024-09-13 16:04:19 +08:00
dijunkun
d246b7a04d [fix] fix the issue where the title bar is displayed incorrectly when in fullscreen mode 2024-09-13 15:10:23 +08:00
dijunkun
a49ca813e0 [fix] fix black screen after close the connection 2024-09-13 10:35:53 +08:00
dijunkun
0c688efaee [fix] fix buttons position when control bar in the right 2024-09-12 17:22:13 +08:00
dijunkun
be3561d46f [fix] optimize the first graph rendering time when open this program 2024-09-12 17:17:07 +08:00
dijunkun
c3af40a3f0 [feat] add close button in control bar 2024-09-12 16:22:02 +08:00
dijunkun
d493b9a131 [feat] make the window centered on the screen after closed 2024-09-12 13:57:51 +08:00
dijunkun
4e4e84ae4d [fix] fix window size when closed after resized 2024-09-12 13:51:11 +08:00
dijunkun
fea545e5e7 [fix] do not forget to destroy the texture 2024-09-11 17:35:27 +08:00
dijunkun
9096769a85 [fix] fix render stream blurry problem 2024-09-11 11:22:17 +08:00
dijunkun
04ab157ecb [fix] fix crash due to invalid pointer 2024-09-10 17:33:55 +08:00
dijunkun
2331f08283 [fix] fix cursor mapping error due to the client render aspect ratio different from the server screen aspect ratio 2024-09-06 19:36:33 +08:00
dijunkun
9f8f99f21b [fix] fix cursor mapping error due to ffmpeg default screen capture resolution different from the real screen resolution 2024-09-06 19:32:30 +08:00
dijunkun
56dadb6a49 Merge branch 'rt_desk' of github.com:dijunkun/continuous-desk into rt_desk 2024-09-06 17:38:43 +08:00
dijunkun
59c9ca8d53 [fix] fix render area cannot fit the resolution of the receiving video stream 2024-09-06 17:38:09 +08:00
dijunkun
f16a4e8aa2 [feat] support original resolution screen capture on MacOSX 2024-09-06 15:20:06 +08:00
dijunkun
890615e13a [fix] fix crash during termination 2024-09-06 13:07:20 +08:00
dijunkun
2f72e3957e [feat] support dynamic resolution codec 2024-09-05 17:29:27 +08:00
dijunkun
1292018f51 [fix] fix crash when signal server close the connection actively 2024-09-04 17:03:40 +08:00
dijunkun
8ae9513104 [feat] only a six-char password will be accepted 2024-09-03 17:29:46 +08:00
dijunkun
c1efe2f4ac [feat] do not capture cursor 2024-09-03 17:00:57 +08:00
dijunkun
1210a0b631 [fix] fix crash caused by accessing invalid memory 2024-09-03 16:24:36 +08:00
dijunkun
39863c597e [feat] allow user to set customized password 2024-09-03 15:50:38 +08:00
dijunkun
8a964f0030 [feat] add option 'enable TURN service' in settings menu 2024-09-02 17:29:03 +08:00
dijunkun
74e29f25bf [fix] do not use smart pointer to manage std::thread objects 2024-09-02 16:33:01 +08:00
dijunkun
1e5bea2b1e [feat] put ice agent into ice worker thread and use message queue to handle events 2024-08-28 17:31:27 +08:00
dijunkun
d8297ebb74 [feat] use fix random password otherwise user regenerates one 2024-08-28 10:28:11 +08:00
dijunkun
93d7f71cf2 [feat] support use param to control enable TURN or not 2024-08-27 17:06:56 +08:00
dijunkun
887a217828 [fix] fix fonts missing 2024-08-27 17:04:11 +08:00
dijunkun
89b12136e4 [fix] Optimizing video encoding speed for software encoders 2024-08-20 17:18:29 +08:00
dijunkun
def7025abf [fix] fix connection status display 2024-08-20 10:21:34 +08:00
dijunkun
35af5aab43 [fix] fix control bar border color 2024-08-19 17:09:19 +08:00
dijunkun
9ea67df0fd [fix] fix title bar icon error when leaves maximized state 2024-08-19 14:16:21 +08:00
dijunkun
72fda8a728 [feat] let keyboard focus on input widget when needs to input password 2024-08-19 11:05:08 +08:00
dijunkun
070b48d7a7 [fix] fix mouse and audio capture buttons cannot be disabled once enabled 2024-08-16 17:30:44 +08:00
dijunkun
6168009cef [feat] support building xcode app on MacOSX 2024-08-16 17:19:17 +08:00
dijunkun
06a7243ac1 [fix] fix mouse click event sending when control bar hovered 2024-08-16 15:06:42 +08:00
dijunkun
c8602b0d89 [fix] fix cursor position error 2024-08-16 14:52:44 +08:00
dijunkun
e3c730fd5f [fix] fix screen capture error in client mode 2024-08-16 14:22:08 +08:00
dijunkun
b252cb6ddd [style] use self-draw icon for mouse and audio_capture disable buttons 2024-08-16 13:55:48 +08:00
dijunkun
8ca1e8e5a1 [style] reset menu bar line size 2024-08-16 09:35:09 +08:00
dijunkun
a7d45b78c8 [feat] use self-draw icon for title bar 2024-08-15 17:38:18 +08:00
dijunkun
018231eee4 [feat] add protocol to control audio capture 2024-08-15 14:50:47 +08:00
dijunkun
4704d494ec [feat] add audio capture button in control bar 2024-08-15 11:25:19 +08:00
dijunkun
65927c2091 [feat] support speaker capture on Windows 2024-08-15 11:04:06 +08:00
dijunkun
574b9d10ab [fix] let temproary setting values equal to setting values when loading from cache file 2024-08-14 16:50:49 +08:00
dijunkun
ff510a3b44 [fix] fix peer object delete 2024-08-14 16:39:08 +08:00
dijunkun
4d3c864950 [fix] fix client id cannot read from cache file 2024-08-13 17:17:35 +08:00
dijunkun
2cde54cf30 [fix] call memset() before using strncpy() 2024-08-13 17:07:19 +08:00
dijunkun
d1f3d11318 [fix] use SDL_RestoreWindow() to reset the window when closed during streaming 2024-08-13 16:32:17 +08:00
dijunkun
436228946b [feat] do not show menu button during streaming 2024-08-13 16:21:31 +08:00
dijunkun
2b4083ee10 [feat] expand control bar by default 2024-08-13 11:26:18 +08:00
dijunkun
e3abb4e3de [fix] fix recreate peer failed due to is_create_connection_ flag not reset 2024-08-13 11:15:28 +08:00
dijunkun
4b7cd1005b [fix] fix send error when ice state change from ready to connected 2024-08-09 16:54:14 +08:00
dijunkun
03ea96096d [feat] add space automaticlly for remote id input box 2024-08-09 11:33:13 +08:00
dijunkun
0ea8916426 [fix] fix settings error when load from cache file 2024-08-09 10:56:16 +08:00
dijunkun
43b36eb893 [fix] use proper std::chrono clock 2024-08-09 10:23:51 +08:00
dijunkun
03b6a187b3 [feat] split remote id into chunks of three characters separated by space 2024-08-08 16:34:55 +08:00
dijunkun
664412dd4e [feat] enable Enter key pressing for ImGui::InputText() method 2024-08-08 15:56:20 +08:00
dijunkun
b37e08a202 [fix] fix client id empty error when run the program firstly 2024-08-08 15:26:21 +08:00
dijunkun
a05d72ec67 [feat] lock the cache file when write/read it 2024-08-08 15:24:54 +08:00
dijunkun
f77e9fe6a8 [feat] remove 'I/l/O/o/0' from password generator 2024-08-08 15:22:51 +08:00
dijunkun
1f9614e060 [feat] use h264 codec by default 2024-08-08 15:15:58 +08:00
dijunkun
50d92a763a [fix] fix leave transmission error when exit program 2024-08-07 17:33:05 +08:00
dijunkun
ec23656334 [fix] fix client id error when connect to itself 2024-08-07 09:55:59 +08:00
dijunkun
880c2949c3 [feat] Use server to generate transmission id and client id 2024-08-06 17:27:40 +08:00
dijunkun
07f5fe81c8 [fix] fix control bar cannot stick to right side when out of y-axis region 2024-08-06 11:24:10 +08:00
dijunkun
5a992b6589 [fix] fix control bar width 2024-08-06 10:35:25 +08:00
dijunkun
8e03e8e79b [feat] make control bar stick to left/right border 2024-08-05 17:28:39 +08:00
dijunkun
ceb3d9fe20 [feat] make control bar can only move along left border of the main window 2024-08-02 17:31:01 +08:00
dijunkun
0dc0b87bc4 [fix] fix control bar initial postion 2024-08-02 16:46:39 +08:00
dijunkun
3a4284fece [fix] fix read cache file failed 2024-08-02 16:15:18 +08:00
dijunkun
502a90f121 [fix] fix program cannot exit when click close button due to screen capture thread is running 2024-08-02 14:38:31 +08:00
dijunkun
88cd4aca4a [fix] fix title bar display error when streaming 2024-08-02 13:57:12 +08:00
dijunkun
3395004f93 [fix] fix compile error on Macosx and Linux 2024-08-02 13:56:22 +08:00
dijunkun
e4c05e1f4d [feat] enable movement for control bar 2024-08-02 12:30:50 +08:00
dijunkun
d17c70c2c8 [feat] remove menu bar and move menu button to title bar 2024-08-01 17:28:39 +08:00
dijunkun
7b42923418 [feat] use self designed title bar instead of system default title bar 2024-07-31 17:43:02 +08:00
dijunkun
5b6bdee25a [feat] add callback to notify the travsesal mode 2024-07-30 17:32:12 +08:00
dijunkun
05deb73c29 [feat] Support trickle ice 2024-07-29 16:48:22 +08:00
dijunkun
3685acc549 [feat] set window resizable only in streaming state 2024-07-25 10:10:37 +08:00
dijunkun
8f5a53937a [fix] fix control bar display and button postion 2024-07-24 17:10:05 +08:00
dijunkun
b9c5db41ab [fix] show connection status windows when connection status changed 2024-07-24 16:27:58 +08:00
dijunkun
a99a4230af Add module: speaker capture 2024-07-24 16:16:13 +08:00
dijunkun
f446154747 1.Fix texture update crash; 2.Do not show control window when in server mode 2024-07-19 14:54:53 +08:00
dijunkun
5a1e2c5ed9 Use speaker as audio input 2024-07-19 14:10:35 +08:00
dijunkun
ff6f295fac Do not use rc file on MacOS 2024-07-18 16:06:35 +08:00
dijunkun
3111b3a641 Add icon 2024-07-18 15:58:28 +08:00
dijunkun
20bb13ce85 Chinese support in connection status window 2024-07-18 14:46:19 +08:00
dijunkun
5aa05f3a13 Redesign elements styles 2024-07-18 11:02:35 +08:00
dijunkun
c911aa2eb1 Add about window 2024-07-17 16:22:45 +08:00
dijunkun
d0cd2fe9ab Fix log instance mismatch 2024-07-17 14:45:30 +08:00
dijunkun
9702805331 Make control bar scrollable 2024-07-05 16:05:50 +08:00
dijunkun
872152f1be 1.Enable random password; 2.Request user to input password when password is incorrect 2024-07-03 15:07:40 +08:00
dijunkun
b822221d7f Add a button which can copy local id to clipboard 2024-07-03 11:03:04 +08:00
dijunkun
95ad605b36 Add signal and p2p connection status windows 2024-07-02 17:29:39 +08:00
dijunkun
af32e25149 Use minimized compressed fonts header file 2024-07-02 11:19:50 +08:00
dijunkun
e63b384d1e Set dpi scaling to solve display errot when using high dpi displayer 2024-07-01 16:31:39 +08:00
dijunkun
7f25f7426c Fix remote window height size error 2024-07-01 11:24:58 +08:00
dijunkun
eed93ea953 Set utf-8 encoding flag in xmake.lua 2024-07-01 10:20:32 +08:00
dijunkun
b5f8e92526 Solve the warning of method ImGui::Text() 2024-06-28 15:11:40 +08:00
dijunkun
af04b0571e Use UTF-8 to save files 2024-06-28 12:49:56 +08:00
dijunkun
75452a3e76 1.Use compressed OPPOSans-Regular[3500 chinese characters]; 2.Use same layout style on different platforms 2024-06-28 12:30:09 +08:00
dijunkun
3f717f1df2 Use binary font headerfile instead of ttf file 2024-06-28 10:01:34 +08:00
dijunkun
ad6f2c2c70 Implementation for menu bar 2024-06-27 17:36:13 +08:00
dijunkun
8076e7f662 Fix stream render with menu bar 2024-06-26 14:58:15 +08:00
dijunkun
be78496992 Separate stream window from main window 2024-06-25 14:47:54 +08:00
dijunkun
a3f745d441 Use fix ratio to render frame in window 2024-06-25 11:12:10 +08:00
dijunkun
e693d920d3 Use sub windows to render main window 2024-06-24 17:35:25 +08:00
dijunkun
0f1b89eda9 Test tabbar in ImGui 2024-06-19 17:33:44 +08:00
dijunkun
172b8836fd Use FontAwesome6 to render icons 2024-06-18 17:28:02 +08:00
dijunkun
71178ffa33 Separate render window from main window 2024-06-17 17:31:57 +08:00
dijunkun
95a014a601 Not allow clicking connect button if remote id is empty 2024-06-14 17:01:58 +08:00
dijunkun
34d6bac345 Update Windows platform layout style 2024-06-14 16:02:55 +08:00
dijunkun
399785409c Use element style header file to control layout 2024-06-14 15:37:16 +08:00
dijunkun
5f1d9b6912 Fix language localization error 2024-06-14 13:55:05 +08:00
dijunkun
d963a0cf38 1.The mouse control button allowed to be clicked Only when connection established; 2.Fix display resolution error after exit fullscreen 2024-06-14 10:48:35 +08:00
dijunkun
f9c1bc48b4 Add control/release mouse button 2024-06-13 17:36:02 +08:00
dijunkun
2906d05a4b Do not send mouse click event when cursor hovers over subwindows 2024-06-13 16:56:03 +08:00
dijunkun
053a0f86ad Add mouse control flag 2024-06-13 16:36:11 +08:00
dijunkun
6c2363b239 Clear render buffer when connection closed 2024-06-13 16:30:25 +08:00
dijunkun
167514fed8 Do not collapse menu window when connection established 2024-06-13 16:07:22 +08:00
dijunkun
342eb0c386 Reset connection_established_ flag when connection closed 2024-06-13 16:06:02 +08:00
dijunkun
52c7099dbe Fix crash when connecting to local desk 2024-06-13 15:49:26 +08:00
dijunkun
12faf7cd2d Do not reset is_create_connection_ when click disconnect button 2024-06-07 18:13:09 +08:00
dijunkun
6d921a3309 Fix server mode screen capture error 2024-06-07 16:30:18 +08:00
dijunkun
5a690ebbb6 Do not use 'S-' or 'C-' as the prefix for the user id of a peer 2024-06-07 16:27:05 +08:00
dijunkun
4b3839aa34 Only server can capture screen and control mouse 2024-06-07 14:07:22 +08:00
dijunkun
efb165b56f 1.Add CreateConnectionPeer method in order to recreate peer instance; 2.Fix settings OK/Cancel button position 2024-06-06 17:19:16 +08:00
dijunkun
0047b4ecc5 Recreate peer instance after settings changed 2024-06-06 15:11:02 +08:00
dijunkun
844710af7c Fix settings button value mismatch 2024-06-06 14:34:16 +08:00
dijunkun
562d54090a Use 'ImGuiWindowFlags_NoSavedSettings' for settings window 2024-06-06 09:57:37 +08:00
dijunkun
f7fd37651e Reset settings window position before it is opened 2024-06-06 09:55:08 +08:00
dijunkun
280f59f97d Enable movement of settings window 2024-06-05 17:33:42 +08:00
dijunkun
0683ad9d27 Use Combo instead of RadioButton for settings 2024-06-05 17:30:23 +08:00
dijunkun
e061e3b4d7 Support read configure params from input directly 2024-06-04 17:38:55 +08:00
dijunkun
eaedcb8d06 Fix crash caused by the release of screen capturer 2024-06-04 16:27:34 +08:00
dijunkun
e7e6380adc Start capturing screen when connection established 2024-06-03 23:48:53 +08:00
dijunkun
1f50483b50 Save settings into cache file 2024-06-03 17:02:20 +08:00
dijunkun
6f703c8267 Use OOP to refactor main function 2024-06-03 15:23:37 +08:00
dijunkun
d150c374b5 Reset main window width/height ratio automatically when width/height is changed 2024-06-03 11:30:55 +08:00
dijunkun
f29b2ee09d Test user date in peer instance and callback functions 2024-05-30 17:27:49 +08:00
dijunkun
0a934e8c01 Fix LNK1561 error on Winodws 2024-05-30 16:33:50 +08:00
dijunkun
2163aa87d4 The connection can use only one peer to realize server and client 2024-05-30 16:12:53 +08:00
dijunkun
5d8408d892 Use abstraction to refactor remote desk gui 2024-05-29 17:33:41 +08:00
dijunkun
93d0e3a5d0 Auto collapse menu bar when connection established 2024-05-28 17:37:06 +08:00
dijunkun
b4a5e91bc9 Support fullscreen 2024-05-28 16:27:04 +08:00
dijunkun
759078ef7f 1.Use PingFang.ttc as Chinese default fonts on MacOS;2.Fix link error for FFmpeg 2024-05-28 15:25:16 +08:00
dijunkun
905539a6eb 1.Use Windows font file 'simhei.ttf'; 2.Add test button for fullscreen 2024-05-27 17:06:09 +08:00
dijunkun
f1512812ad Support localization(Simplified Chinese[source-han-sans-regular]) 2024-05-24 17:15:44 +08:00
dijunkun
5f1cf89649 Fix crash during termination on windows 2024-05-24 15:48:37 +08:00
dijunkun
f291ad189a 1.Remove sdl2 from thirdparty since it is already required by imgui; 2.Update imgui to v1.90.6; 3.Update spdlog to v 1.14.1 2024-05-24 15:10:58 +08:00
dijunkun
8807636372 Fix crash caused by screen capturer or mouse controller init failed on Linux and MacOS 2024-05-23 15:48:10 +08:00
dijunkun
70be1d8afc Update submodule projectx 2024-05-22 16:39:27 +08:00
dijunkun
1f76aa427d Change submodule projectx url 2024-05-22 16:35:30 +08:00
dijunkun
134cbf8b75 Restore ffmpeg dependency for Linux and MacOS due to screen capture needs ffmpeg on these platforms 2024-05-22 14:06:12 +08:00
dijunkun
669b944cfd Remove resampling process during SDL2 capture audio stream 2024-05-21 17:01:08 +08:00
dijunkun
9962829885 Fix ARGB to NV12 error caused by uv stride 2024-05-20 10:47:44 +08:00
dijunkun
1393615f01 Remove dependency on FFmpeg 2024-05-17 17:55:57 +08:00
dijunkun
d58ae3a6b1 Fix AV1 codec on MacOS 2024-05-10 14:45:12 +08:00
dijunkun
a188729af6 Support AV1 stream transmitting over RTP 2024-05-09 17:05:38 +08:00
dijunkun
422478bd9a Support AV1 codec 2024-04-19 17:37:00 +08:00
dijunkun
d8980f0082 WGC screen capturer needs c++17 or above 2023-12-22 16:07:32 +08:00
dijunkun
e88bb017fa 1.Using c++14; 2.Using {} to initialize std::atomic 2023-12-22 15:46:54 +08:00
dijunkun
87466d6074 Update thirdparty library 2023-12-22 14:14:08 +08:00
Di Junkun
fbbbfc5e6a Update README_CN.md 2023-12-21 15:02:12 +08:00
Di Junkun
d2cefd1827 Update README.md 2023-12-21 14:59:44 +08:00
Di Junkun
a350e06529 Update README.md 2023-12-21 14:56:18 +08:00
Di Junkun
8c742ffa08 Update README.md 2023-12-21 14:34:11 +08:00
Di Junkun
475005b8a4 Update README.md 2023-12-20 15:54:54 +08:00
dijunkun
4da5188759 Update README.md 2023-12-20 15:19:44 +08:00
Di Junkun
d8df4df5ae Update README.md 2023-12-20 15:17:53 +08:00
dijunkun
fa05bbc8f8 Update README.md 2023-12-20 13:00:39 +08:00
Di Junkun
927f1a6d49 Update README.md 2023-12-20 09:34:43 +08:00
dijunkun
1d87f61d9f Update README.md 2023-12-19 17:33:16 +08:00
dijunkun
ce546b77f5 Hide cursor when remote peer connected 2023-12-18 16:52:49 +08:00
dijunkun
b9e69cde51 Remove unused variables 2023-12-18 16:17:55 +08:00
dijunkun
e3987b4a42 Fix remote action type undefined error 2023-12-18 16:06:53 +08:00
dijunkun
0034359431 Update submodule 2023-12-18 15:27:11 +08:00
dijunkun
cec275bbe0 Add 1 second time interval for retry join transmission 2023-12-18 15:10:41 +08:00
dijunkun
fe68464cd2 Fix remote id hint error 2023-12-18 13:43:08 +08:00
dijunkun
181c473625 Use SDL to get screen resolution 2023-12-18 11:15:55 +08:00
dijunkun
2fc89923ae Use factory method to create screen capturer 2023-12-15 17:29:17 +08:00
dijunkun
9276d5bfec Fix compile error 2023-12-14 17:14:52 +08:00
dijunkun
95ef8fe8b9 Use factory method to create mouse controller on MacOS 2023-12-14 17:02:43 +08:00
dijunkun
3ab0e0136e Use factory method to create mouse controller on Windows 2023-12-14 16:40:10 +08:00
dijunkun
daecc0d1e9 Use factory method to create mouse controller on Linux 2023-12-14 16:27:13 +08:00
dijunkun
bfecf47226 Fix cross platform compile error 2023-12-13 17:32:02 +08:00
dijunkun
09c42dd7ed 1.Add linux mouse control test;2.Fix unused variables warnning 2023-12-13 17:26:08 +08:00
dijunkun
a8a3b88514 Use uinput to control mouse and keyboard on Linux 2023-12-07 19:00:34 -08:00
dijunkun
2d6cfb5c76 Support cursor control on MacOS 2023-12-06 15:24:01 +08:00
dijunkun
ed8b536ac0 Add signal connection status 'NoSuchTransmissionId' 2023-12-04 14:04:36 +08:00
dijunkun
5e2d27e9d2 Fix ffmpeg link error on MacOS 2023-12-04 11:20:30 +08:00
dijunkun
47a2dc85f9 Fix screen capture on MacOS 2023-12-01 17:16:38 +08:00
dijunkun
5febe99bc2 Support screen capture on MacOS 2023-12-01 16:54:17 +08:00
dijunkun
97bed80088 Fix link flag for MacOS 2023-12-01 10:12:06 +08:00
dijunkun
070f7bd21e Disable ffmpeg openh264 codec 2023-11-29 22:57:29 -08:00
dijunkun
4a65a59803 Enable audio transmission 2023-11-29 22:04:53 -08:00
dijunkun
733434f9b3 Switch rtc core to branch opus 2023-11-21 22:36:36 -08:00
dijunkun
7b7787f638 Add audio capture test 2023-11-21 22:32:06 -08:00
dijunkun
b64d399967 Capture full screen and resize to 1280x720 for Linux platform 2023-11-20 23:11:21 -08:00
dijunkun
65f9862929 Enable password checking when login 2023-11-20 22:48:18 -08:00
dijunkun
3065c15631 Save password in cache 2023-11-20 22:24:27 -08:00
107 changed files with 65763 additions and 2182 deletions

343
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,343 @@
name: Build and Release CrossDesk
on:
push:
branches:
- "**"
tags:
- "*"
workflow_dispatch:
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
# Linux x86_64
build-linux-x86_64:
name: Build on Ubuntu 22.04 x86_64
runs-on: ubuntu-22.04
container:
image: crossdesk/ubuntu22.04:latest
options: --user root
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
- name: Set legal Debian version
shell: bash
id: set_deb_version
run: |
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}"
else
LEGAL_VERSION="${VERSION_NUM}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
ls -la $XMAKE_GLOBALDIR
xmake 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 ${LEGAL_VERSION}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-x86_64-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-x86_64-${{ env.LEGAL_VERSION }}.deb
# Linux arm64
build-linux-arm64:
name: Build on Ubuntu 22.04 arm64
runs-on: ubuntu-22.04-arm
strategy:
matrix:
include:
- arch: arm64
image: crossdesk/ubuntu20.04-arm64v8:latest
package_script: ./scripts/linux/pkg_arm64.sh
container:
image: ${{ matrix.image }}
options: --user root
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
- name: Set legal Debian version
shell: bash
id: set_deb_version
run: |
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}"
else
LEGAL_VERSION="${VERSION_NUM}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
xmake b -vy --root crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${LEGAL_VERSION}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}.deb
# macOS
build-macos:
name: Build on macOS
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: x86_64
runner: macos-13
cache-key: intel
out-dir: ./build/macosx/x86_64/release/crossdesk
package_script: ./scripts/macosx/pkg_x86_64.sh
- arch: arm64
runner: macos-14
cache-key: arm
out-dir: ./build/macosx/arm64/release/crossdesk
package_script: ./scripts/macosx/pkg_arm64.sh
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}"
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: ~/.xmake/packages
key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
- name: Install xmake
run: brew install xmake
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Build CrossDesk
run: xmake b -vy crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package CrossDesk app
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${VERSION_NUM}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
- name: Move files to release dir
run: |
mkdir -p release
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
# Windows
build-windows:
name: Build on Windows
runs-on: windows-2022
env:
XMAKE_GLOBALDIR: D:\xmake_global
steps:
- name: Extract version number
shell: pwsh
run: |
$ref = $env:GITHUB_REF
$version = $ref -replace '^refs/(tags|heads)/', ''
$version = $version -replace '^v', ''
$version = $version -replace '/', '-'
echo "VERSION_NUM=$version" >> $env:GITHUB_ENV
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: D:\xmake_global\.xmake\packages
key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-intel-
- name: Install xmake
run: |
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
xmake create cuda
Set-Location cuda
xmake g --theme=plain
$cudaPath = ""
$packagesPath = "D:\xmake_global\.xmake\packages"
if (Test-Path $packagesPath) {
Write-Host "Packages directory exists: $packagesPath"
try {
$info = xmake require --info "cuda 12.6.3" 2>$null
if ($null -ne $info -and $info -ne "") {
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
} catch {}
} else {
Write-Host "Packages directory not found: $packagesPath"
Write-Host "Installing CUDA package..."
xmake require -vy "cuda 12.6.3"
$info = xmake require --info "cuda 12.6.3"
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
Write-Host "Resolved CUDA_PATH = $cudaPath"
Pop-Location
- name: 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
shell: pwsh
run: |
cd "${{ github.workspace }}\scripts\windows"
makensis /DVERSION=$env:VERSION_NUM nsis_script.nsi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-win-x86_64-${{ env.VERSION_NUM }}
path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x86_64-${{ env.VERSION_NUM }}.exe
release:
name: Publish Release
if: startsWith(github.ref, 'refs/tags/v')
needs: [build-linux-x86_64, build-linux-arm64, build-macos, build-windows]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
- name: Rename artifacts
run: |
mkdir -p release
cp artifacts/crossdesk-macos-x86_64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x86_64-${{ steps.version.outputs.VERSION_NUM }}.pkg
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}.pkg
cp artifacts/crossdesk-linux-x86_64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-x86_64-${{ steps.version.outputs.VERSION_NUM }}.deb
cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}.deb
cp artifacts/crossdesk-win-x86_64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-win-x86_64-${{ steps.version.outputs.VERSION_NUM }}.exe
- name: Upload to Versioned GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.VERSION_NUM }}
name: Release v${{ steps.version.outputs.VERSION_NUM }}
draft: false
prerelease: false
files: release/*
generate_release_notes: false
body: |
Binary release only. Source code is not included.
- name: Create or update 'latest' tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -f latest
git push origin latest --force
- name: Upload to GitHub Release (latest)
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: Latest Release
draft: false
prerelease: false
files: release/*
generate_release_notes: false

48
.github/workflows/update-pages.yaml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Update GitHub Pages Downloads
on:
push:
tags:
- "v*"
jobs:
update-pages:
runs-on: ubuntu-latest
steps:
- name: Checkout CrossDesk repo
uses: actions/checkout@v4
- name: Set version number
id: version
run: |
VERSION_NUM="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION_NUM#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
- name: Checkout Pages repo
uses: actions/checkout@v4
with:
repository: kunkundi/kunkundi.github.io
token: ${{ secrets.GH_PAGES_PAT }}
path: pages
- name: Update download links
run: |
cd pages
sed -E -i "s/crossdesk-win-x86_64-[0-9]+\.[0-9]+\.[0-9]+\.exe/crossdesk-win-x86_64-${VERSION_NUM}.exe/g" index.html
sed -E -i "s/crossdesk-macos-x86_64-[0-9]+\.[0-9]+\.[0-9]+\.pkg/crossdesk-macos-x86_64-${VERSION_NUM}.pkg/g" index.html
sed -E -i "s/crossdesk-macos-arm64-[0-9]+\.[0-9]+\.[0-9]+\.pkg/crossdesk-macos-arm64-${VERSION_NUM}.pkg/g" index.html
sed -E -i "s/crossdesk-linux-x86_64-[0-9]+\.[0-9]+\.[0-9]+\.deb/crossdesk-linux-x86_64-${VERSION_NUM}.deb/g" index.html
sed -E -i "s/crossdesk-linux-arm64-[0-9]+\.[0-9]+\.[0-9]+\.deb/crossdesk-linux-arm64-${VERSION_NUM}.deb/g" index.html
- name: Commit & Push changes
run: |
cd pages
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add index.html
git commit -m "Update download links to v${VERSION_NUM}" || echo "No changes to commit"
git push origin main
env:
VERSION_NUM: ${{ env.VERSION_NUM }}

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "thirdparty/projectx"]
path = thirdparty/projectx
url = git@github.com:dijunkun/projectx.git
[submodule "thirdparty/minirtc"]
path = thirdparty/minirtc
url = https://github.com/kunkundi/minirtc.git

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2023 The Continuous Desk Authors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

67
README.md Normal file
View File

@@ -0,0 +1,67 @@
# CrossDesk
#### More than remote desktop
----
[中文](README_CN.md) / [English](README.md)
![sup_example](https://github.com/dijunkun/continuous-desk/assets/29698109/46536bc8-3ddd-438d-bf52-dccf143f1c20)
# Intro
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.
CrossDesk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)).
## Usage
Enter the remote desktop ID in the 'REMOTE ID' field on the menu bar, and click 'Connect' button to initiate the remote connection.
![usage1](https://github.com/dijunkun/continuous-desk/assets/29698109/2ad59e6d-bdba-46d0-90cf-cbc9c06c2278)
If the remote desktop is set with a connection password, the local end needs to enter the correct password to initiate the remote connection. If the password is incorrect, an "Incorrect password" alert will appear in the status bar.
![incorrect password](https://github.com/dijunkun/continuous-desk/assets/29698109/cb05501c-ec4e-4adf-952d-7a55ef770a97)
After connection successfully established, the status bar will display the message "ClientConnected."
![success](https://github.com/dijunkun/continuous-desk/assets/29698109/0cca21f7-48fe-44a5-b83d-eafeb8a81eb1)
## How to build
Requirements:
- [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/)
- [vcpkg](https://vcpkg.io/en/getting-started)
Following packages need to be installed on Linux:
```
sudo apt-get install -y nvidia-cuda-toolkit libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev libxv-dev libasound2-dev libsndio-dev libasound2-dev libpulse-dev
```
Commands:
```
git clone https://github.com/dijunkun/continuous-desk
cd continuous-desk
git submodule init
git submodule update
xmake b remote_desk
```
Run:
```
# Windows/MacOS
xmake r remote_desk
# root privileges are required on Linux
./remote_desk
```
## LICENSE
Continuous Desk is licenced under MIT, and some third-party libraries are distributed under their licenses.

67
README_CN.md Normal file
View File

@@ -0,0 +1,67 @@
# Continuous Desk
#### 不止远程桌面
----
[English](README.md) / [中文](README_CN.md)
![sup_example](https://github.com/dijunkun/continuous-desk/assets/29698109/46536bc8-3ddd-438d-bf52-dccf143f1c20)
## 简介
Continuous Desk 是一个轻量级的跨平台远程桌面软件。它允许多个用户在同一时间远程操控同一台电脑。除桌面图像传输外,它还支持端到端的语音传输,在远程桌面基础上提供额外的协作能力。
Continuous Desk 是 [Projectx](https://github.com/dijunkun/projectx) 实时音视频传输库的实验性应用。Projectx 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)视频软硬编解码H264音频编解码[Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制([TCP over UDP](https://libnice.freedesktop.org/))等基础能力。
## 使用
在菜单栏“REMOTE ID”处输入远端桌面的ID点击“Connect”即可发起远程连接。
![usage1](https://github.com/dijunkun/continuous-desk/assets/29698109/2ad59e6d-bdba-46d0-90cf-cbc9c06c2278)
如果远端桌面设置了连接密码则本端需填写正确的连接密码才能成功发起远程连接。密码错误时状态栏会出现“Incorrect password”告警提示。
![incorrect password](https://github.com/dijunkun/continuous-desk/assets/29698109/cb05501c-ec4e-4adf-952d-7a55ef770a97)
连接成功建立后状态栏会有“ClientConnected”相关字样。
![success](https://github.com/dijunkun/continuous-desk/assets/29698109/0cca21f7-48fe-44a5-b83d-eafeb8a81eb1)
## 编译
依赖:
- [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/)
- [vcpkg](https://vcpkg.io/en/getting-started)
Linux环境下需安装以下包
```
sudo apt-get install -y nvidia-cuda-toolkit libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev libxv-dev libasound2-dev libsndio-dev libasound2-dev libpulse-dev
```
编译命令
```
git clone https://github.com/dijunkun/continuous-desk
cd continuous-desk
git submodule init
git submodule update
xmake b remote_desk
```
运行
```
# Windows/MacOS
xmake r remote_desk
# Linux下需使用root权限运行
./remote_desk
```
## 许可证
Continuous Desk 使用 MIT 许可证,其中使用到的第三方库根据自身许可证进行分发。

View File

@@ -1,16 +0,0 @@
[signal server]
ip = 120.77.216.215
port = 9099
[stun server]
ip = 120.77.216.215
port = 3478
[turn server]
ip = 120.77.216.215
port = 3478
username = dijunkun
password = dijunkunpw
[hardware acceleration]
turn_on = true

2348
fonts/OPPOSans_Regular.h Normal file

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

1
icons/app.rc Normal file
View File

@@ -0,0 +1 @@
IDI_ICON1 ICON "app_icon.ico"

BIN
icons/app_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

BIN
icons/crossdesk.icns Normal file

Binary file not shown.

BIN
icons/crossdesk.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

BIN
icons/crossdesk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

111
scripts/linux/pkg_arm64.sh Normal file
View File

@@ -0,0 +1,111 @@
#!/bin/bash
set -e
APP_NAME="CrossDesk"
APP_VERSION="$1"
ARCHITECTURE="arm64" # 改为 arm64
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/arm64/release/crossdesk "$BIN_DIR"
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"
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
Priority: optional
Section: utils
EOF
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
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"
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
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"
dpkg-deb --build "$DEB_DIR"
OUTPUT_FILE="crossdesk-linux-arm64-$APP_VERSION.deb"
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
rm -rf "$DEB_DIR"
echo "✅ Deb package for $APP_NAME (ARM64) created successfully."

111
scripts/linux/pkg_x86_64.sh Normal file
View File

@@ -0,0 +1,111 @@
#!/bin/bash
set -e
APP_NAME="CrossDesk"
APP_VERSION="$1"
ARCHITECTURE="amd64"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client."
DEB_DIR="$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"
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"
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
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
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"
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
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"
dpkg-deb --build "$DEB_DIR"
OUTPUT_FILE="crossdesk-linux-x86_64-$APP_VERSION.deb"
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
rm -rf "$DEB_DIR"
echo "✅ Deb package for $APP_NAME created successfully."

152
scripts/macosx/pkg_arm64.sh Normal file
View 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="$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}-${APP_VERSION}.pkg" # 保持安装包名称小写
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${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 build_pkg_temp scripts ${APP_BUNDLE}
echo "🎉 所有打包完成:"
echo " ✔️ 应用:${APP_BUNDLE}"
echo " ✔️ 安装包:${PKG_NAME}"
# echo " ✔️ 镜像包(可选):${DMG_NAME}"

View 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="$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}-${APP_VERSION}.pkg" # 保持安装包名称小写
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${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 build_pkg_temp scripts ${APP_BUNDLE}
echo "🎉 所有打包完成:"
echo " ✔️ 应用:${APP_BUNDLE}"
echo " ✔️ 安装包:${PKG_NAME}"
# echo " ✔️ 镜像包(可选):${DMG_NAME}"

View 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 "${VERSION}"
!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-win-x86_64-${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
View File

@@ -0,0 +1,44 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-05-15
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DISPLAY_INFO_H_
#define _DISPLAY_INFO_H_
#include <string>
class DisplayInfo {
public:
DisplayInfo(std::string name, int left, int top, int right, int bottom)
: name(name), left(left), top(top), right(right), bottom(bottom) {
width = right - left;
height = bottom - top;
}
DisplayInfo(void* handle, std::string name, bool is_primary, int left,
int top, int right, int bottom)
: handle(handle),
name(name),
is_primary(is_primary),
left(left),
top(top),
right(right),
bottom(bottom) {
width = right - left;
height = bottom - top;
}
~DisplayInfo() {}
void* handle = nullptr;
std::string name = "";
bool is_primary = false;
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
int width = 0;
int height = 0;
};
#endif

125
src/common/platform.cpp Normal file
View File

@@ -0,0 +1,125 @@
#include "platform.h"
#include "rd_log.h"
#ifdef _WIN32
#include <Winsock2.h>
#include <iphlpapi.h>
#elif __APPLE__
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <sys/socket.h>
#include <sys/types.h>
#elif __linux__
#include <fcntl.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
std::string GetMac() {
char mac_addr[16];
int len = 0;
#ifdef _WIN32
IP_ADAPTER_INFO adapterInfo[16];
DWORD bufferSize = sizeof(adapterInfo);
DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize);
if (result == ERROR_SUCCESS) {
PIP_ADAPTER_INFO adapter = adapterInfo;
while (adapter) {
for (UINT i = 0; i < adapter->AddressLength; i++) {
len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
adapter->Address[i]);
}
break;
}
}
#elif __APPLE__
std::string if_name = "en0";
struct ifaddrs *addrs;
struct ifaddrs *cursor;
const struct sockaddr_dl *dlAddr;
if (!getifaddrs(&addrs)) {
cursor = addrs;
while (cursor != 0) {
const struct sockaddr_dl *socAddr =
(const struct sockaddr_dl *)cursor->ifa_addr;
if ((cursor->ifa_addr->sa_family == AF_LINK) &&
(socAddr->sdl_type == IFT_ETHER) &&
strcmp(if_name.c_str(), cursor->ifa_name) == 0) {
dlAddr = (const struct sockaddr_dl *)cursor->ifa_addr;
const unsigned char *base =
(const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen];
for (int i = 0; i < dlAddr->sdl_alen; i++) {
len +=
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
#elif __linux__
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
return "";
}
struct ifreq ifr;
struct ifconf ifc;
char buf[1024];
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
close(sock);
return "";
}
struct ifreq *it = ifc.ifc_req;
const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq));
for (; it != end; ++it) {
std::strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
continue;
}
if (ifr.ifr_flags & IFF_LOOPBACK) {
continue;
}
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
continue;
}
std::string mac_address;
for (int i = 0; i < 6; ++i) {
len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff);
}
break;
}
close(sock);
#endif
return mac_addr;
}
std::string GetHostName() {
char hostname[256];
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed." << std::endl;
return "";
}
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
WSACleanup();
return "";
}
WSACleanup();
#else
if (gethostname(hostname, sizeof(hostname)) == -1) {
LOG_ERROR("gethostname failed");
return "";
}
#endif
return hostname;
}

15
src/common/platform.h Normal file
View File

@@ -0,0 +1,15 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-18
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _PLATFORM_H_
#define _PLATFORM_H_
#include <iostream>
std::string GetMac();
std::string GetHostName();
#endif

View File

@@ -0,0 +1,45 @@
#include "config_center.h"
ConfigCenter::ConfigCenter() {}
ConfigCenter::~ConfigCenter() {}
int ConfigCenter::SetLanguage(LANGUAGE language) {
language_ = language;
return 0;
}
int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) {
video_quality_ = video_quality;
return 0;
}
int ConfigCenter::SetVideoEncodeFormat(
VIDEO_ENCODE_FORMAT video_encode_format) {
video_encode_format_ = video_encode_format;
return 0;
}
int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) {
hardware_video_codec_ = hardware_video_codec;
return 0;
}
int ConfigCenter::SetTurn(bool enable_turn) {
enable_turn_ = enable_turn;
return 0;
}
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; }
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() {
return video_quality_;
}
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() {
return video_encode_format_;
}
bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; }
bool ConfigCenter::IsEnableTurn() { return enable_turn_; }

View File

@@ -0,0 +1,43 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-05-29
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _CONFIG_CENTER_H_
#define _CONFIG_CENTER_H_
class ConfigCenter {
public:
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 };
enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 };
enum class VIDEO_ENCODE_FORMAT { AV1 = 0, H264 = 1 };
public:
ConfigCenter();
~ConfigCenter();
public:
int SetLanguage(LANGUAGE language);
int SetVideoQuality(VIDEO_QUALITY video_quality);
int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format);
int SetHardwareVideoCodec(bool hardware_video_codec);
int SetTurn(bool enable_turn);
public:
LANGUAGE GetLanguage();
VIDEO_QUALITY GetVideoQuality();
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat();
bool IsHardwareVideoCodec();
bool IsEnableTurn();
private:
// Default value should be same with parameters in localization.h
LANGUAGE language_ = LANGUAGE::CHINESE;
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1;
bool hardware_video_codec_ = false;
bool enable_turn_ = false;
};
#endif

View File

@@ -0,0 +1,83 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-14
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DEVICE_CONTROLLER_H_
#define _DEVICE_CONTROLLER_H_
#include <stdio.h>
#include "display_info.h"
typedef enum {
mouse = 0,
keyboard,
audio_capture,
host_infomation,
display_id,
} ControlType;
typedef enum {
move = 0,
left_down,
left_up,
right_down,
right_up,
middle_down,
middle_up,
wheel_vertical,
wheel_horizontal
} MouseFlag;
typedef enum { key_down = 0, key_up } KeyFlag;
typedef struct {
float x;
float y;
int s;
MouseFlag flag;
} Mouse;
typedef struct {
size_t key_value;
KeyFlag flag;
} Key;
typedef struct {
char host_name[64];
size_t host_name_size;
char** display_list;
size_t display_num;
int* left;
int* top;
int* right;
int* bottom;
} HostInfo;
typedef struct {
ControlType type;
union {
Mouse m;
Key k;
HostInfo i;
bool a;
int d;
};
} RemoteAction;
// int key_code, bool is_down
typedef void (*OnKeyAction)(int, bool, void*);
class DeviceController {
public:
virtual ~DeviceController() {}
public:
// virtual int Init(int screen_width, int screen_height);
// virtual int Destroy();
// virtual int SendMouseCommand(RemoteAction remote_action);
// virtual int Hook();
// virtual int Unhook();
};
#endif

View File

@@ -0,0 +1,34 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-14
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DEVICE_CONTROLLER_FACTORY_H_
#define _DEVICE_CONTROLLER_FACTORY_H_
#include "device_controller.h"
#include "keyboard_capturer.h"
#include "mouse_controller.h"
class DeviceControllerFactory {
public:
enum Device { Mouse = 0, Keyboard };
public:
virtual ~DeviceControllerFactory() {}
public:
DeviceController* Create(Device device) {
switch (device) {
case Mouse:
return new MouseController();
case Keyboard:
return new KeyboardCapturer();
default:
return nullptr;
}
}
};
#endif

View 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;
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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

View File

@@ -0,0 +1,124 @@
#include "mouse_controller.h"
#include <X11/extensions/XTest.h>
#include "rd_log.h"
MouseController::MouseController() {}
MouseController::~MouseController() { Destroy(); }
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
display_info_list_ = display_info_list;
display_ = XOpenDisplay(NULL);
if (!display_) {
LOG_ERROR("Cannot connect to X server");
return -1;
}
root_ = DefaultRootWindow(display_);
int event_base, error_base, major_version, minor_version;
if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version,
&minor_version)) {
LOG_ERROR("XTest extension not available");
XCloseDisplay(display_);
return -2;
}
return 0;
}
int MouseController::Destroy() {
if (display_) {
XCloseDisplay(display_);
display_ = nullptr;
}
return 0;
}
int MouseController::SendMouseCommand(RemoteAction remote_action,
int display_index) {
switch (remote_action.type) {
case mouse:
switch (remote_action.m.flag) {
case MouseFlag::move:
SetMousePosition(
static_cast<int>(remote_action.m.x *
display_info_list_[display_index].width +
display_info_list_[display_index].left),
static_cast<int>(remote_action.m.y *
display_info_list_[display_index].height +
display_info_list_[display_index].top));
break;
case MouseFlag::left_down:
XTestFakeButtonEvent(display_, 1, True, CurrentTime);
XFlush(display_);
break;
case MouseFlag::left_up:
XTestFakeButtonEvent(display_, 1, False, CurrentTime);
XFlush(display_);
break;
case MouseFlag::right_down:
XTestFakeButtonEvent(display_, 3, True, CurrentTime);
XFlush(display_);
break;
case MouseFlag::right_up:
XTestFakeButtonEvent(display_, 3, False, CurrentTime);
XFlush(display_);
break;
case MouseFlag::middle_down:
XTestFakeButtonEvent(display_, 2, True, CurrentTime);
XFlush(display_);
break;
case MouseFlag::middle_up:
XTestFakeButtonEvent(display_, 2, False, CurrentTime);
XFlush(display_);
break;
case MouseFlag::wheel_vertical: {
if (remote_action.m.s > 0) {
SimulateMouseWheel(4, remote_action.m.s);
} else if (remote_action.m.s < 0) {
SimulateMouseWheel(5, -remote_action.m.s);
}
break;
}
case MouseFlag::wheel_horizontal: {
if (remote_action.m.s > 0) {
SimulateMouseWheel(6, remote_action.m.s);
} else if (remote_action.m.s < 0) {
SimulateMouseWheel(7, -remote_action.m.s);
}
break;
}
}
break;
default:
break;
}
return 0;
}
void MouseController::SetMousePosition(int x, int y) {
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
XFlush(display_);
}
void MouseController::SimulateKeyDown(int kval) {
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
XFlush(display_);
}
void MouseController::SimulateKeyUp(int kval) {
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
XFlush(display_);
}
void MouseController::SimulateMouseWheel(int direction_button, int count) {
for (int i = 0; i < count; ++i) {
XTestFakeButtonEvent(display_, direction_button, True, CurrentTime);
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
}
XFlush(display_);
}

View File

@@ -0,0 +1,41 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-05-07
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <unistd.h>
#include <vector>
#include "device_controller.h"
class MouseController : public DeviceController {
public:
MouseController();
virtual ~MouseController();
public:
virtual int Init(std::vector<DisplayInfo> display_info_list);
virtual int Destroy();
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
private:
void SimulateKeyDown(int kval);
void SimulateKeyUp(int kval);
void SetMousePosition(int x, int y);
void SimulateMouseWheel(int direction_button, int count);
Display* display_ = nullptr;
Window root_ = 0;
std::vector<DisplayInfo> display_info_list_;
int screen_width_ = 0;
int screen_height_ = 0;
};
#endif

View File

@@ -0,0 +1,101 @@
#include "mouse_controller.h"
#include <ApplicationServices/ApplicationServices.h>
#include "rd_log.h"
MouseController::MouseController() {}
MouseController::~MouseController() {}
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
display_info_list_ = display_info_list;
return 0;
}
int MouseController::Destroy() { return 0; }
int MouseController::SendMouseCommand(RemoteAction remote_action,
int display_index) {
int mouse_pos_x =
remote_action.m.x * display_info_list_[display_index].width +
display_info_list_[display_index].left;
int mouse_pos_y =
remote_action.m.y * display_info_list_[display_index].height +
display_info_list_[display_index].top;
if (remote_action.type == ControlType::mouse) {
CGEventRef mouse_event = nullptr;
CGEventType mouse_type;
CGMouseButton mouse_button;
CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y);
switch (remote_action.m.flag) {
case MouseFlag::left_down:
mouse_type = kCGEventLeftMouseDown;
left_dragging_ = true;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonLeft);
break;
case MouseFlag::left_up:
mouse_type = kCGEventLeftMouseUp;
left_dragging_ = false;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonLeft);
break;
case MouseFlag::right_down:
mouse_type = kCGEventRightMouseDown;
right_dragging_ = true;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonRight);
break;
case MouseFlag::right_up:
mouse_type = kCGEventRightMouseUp;
right_dragging_ = false;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonRight);
break;
case MouseFlag::middle_down:
mouse_type = kCGEventOtherMouseDown;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonCenter);
break;
case MouseFlag::middle_up:
mouse_type = kCGEventOtherMouseUp;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonCenter);
break;
case MouseFlag::wheel_vertical:
mouse_event = CGEventCreateScrollWheelEvent(
NULL, kCGScrollEventUnitLine, 2, remote_action.m.s, 0);
break;
case MouseFlag::wheel_horizontal:
mouse_event = CGEventCreateScrollWheelEvent(
NULL, kCGScrollEventUnitLine, 2, 0, remote_action.m.s);
break;
default:
if (left_dragging_) {
mouse_type = kCGEventLeftMouseDragged;
mouse_button = kCGMouseButtonLeft;
} else if (right_dragging_) {
mouse_type = kCGEventRightMouseDragged;
mouse_button = kCGMouseButtonRight;
} else {
mouse_type = kCGEventMouseMoved;
mouse_button = kCGMouseButtonLeft;
}
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
mouse_button);
break;
}
if (mouse_event) {
CGEventPost(kCGHIDEventTap, mouse_event);
CFRelease(mouse_event);
}
}
return 0;
}

View File

@@ -0,0 +1,30 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-14
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <vector>
#include "device_controller.h"
class MouseController : public DeviceController {
public:
MouseController();
virtual ~MouseController();
public:
virtual int Init(std::vector<DisplayInfo> display_info_list);
virtual int Destroy();
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
private:
std::vector<DisplayInfo> display_info_list_;
bool left_dragging_ = false;
bool right_dragging_ = false;
};
#endif

View File

@@ -0,0 +1,72 @@
#include "mouse_controller.h"
#include "rd_log.h"
MouseController::MouseController() {}
MouseController::~MouseController() {}
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
display_info_list_ = display_info_list;
return 0;
}
int MouseController::Destroy() { return 0; }
int MouseController::SendMouseCommand(RemoteAction remote_action,
int display_index) {
INPUT ip;
if (remote_action.type == ControlType::mouse) {
ip.type = INPUT_MOUSE;
ip.mi.dx =
(LONG)(remote_action.m.x * display_info_list_[display_index].width) +
display_info_list_[display_index].left;
ip.mi.dy =
(LONG)(remote_action.m.y * display_info_list_[display_index].height) +
display_info_list_[display_index].top;
switch (remote_action.m.flag) {
case MouseFlag::left_down:
ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::left_up:
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::right_down:
ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::right_up:
ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::middle_down:
ip.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::middle_up:
ip.mi.dwFlags = MOUSEEVENTF_MIDDLEUP | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::wheel_vertical:
ip.mi.dwFlags = MOUSEEVENTF_WHEEL;
ip.mi.mouseData = remote_action.m.s * 120;
break;
case MouseFlag::wheel_horizontal:
ip.mi.dwFlags = MOUSEEVENTF_HWHEEL;
ip.mi.mouseData = remote_action.m.s * 120;
break;
default:
ip.mi.dwFlags = MOUSEEVENTF_MOVE;
break;
}
ip.mi.time = 0;
SetCursorPos(ip.mi.dx, ip.mi.dy);
if (ip.mi.dwFlags != MOUSEEVENTF_MOVE) {
SendInput(1, &ip, sizeof(INPUT));
}
}
return 0;
}

View File

@@ -0,0 +1,28 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-14
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <vector>
#include "device_controller.h"
class MouseController : public DeviceController {
public:
MouseController();
virtual ~MouseController();
public:
virtual int Init(std::vector<DisplayInfo> display_info_list);
virtual int Destroy();
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
private:
std::vector<DisplayInfo> display_info_list_;
};
#endif

View File

@@ -1,818 +1,17 @@
#include <SDL.h>
#include <stdio.h>
#ifdef _WIN32
// #pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#ifdef DESK_PORT_DEBUG
#pragma comment(linker, "/subsystem:\"console\"")
#include <Winsock2.h>
#include <iphlpapi.h>
#elif __APPLE__
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <sys/socket.h>
#include <sys/types.h>
#elif __linux__
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
};
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_sdlrenderer2.h"
#include "log.h"
#ifdef _WIN32
#include "screen_capture_wgc.h"
#elif __linux__
#include "screen_capture_x11.h"
#endif
#include "../../thirdparty/projectx/src/interface/x.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#ifdef REMOTE_DESK_DEBUG
#define MOUSE_CONTROL 0
#else
#define MOUSE_CONTROL 1
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#endif
#endif
int screen_w = 1280, screen_h = 720;
int window_w = 1280, window_h = 720;
const int pixel_w = 1280, pixel_h = 720;
#include "rd_log.h"
#include "render.h"
unsigned char dst_buffer[pixel_w * pixel_h * 3 / 2];
SDL_Texture *sdlTexture = nullptr;
SDL_Renderer *sdlRenderer = nullptr;
SDL_Rect sdlRect;
SDL_Window *window;
uint32_t start_time, end_time, elapsed_time;
uint32_t frame_count = 0;
int fps = 0;
std::string window_title = "Remote Desk Client";
std::string connection_status = "-";
// Refresh Event
#define REFRESH_EVENT (SDL_USEREVENT + 1)
#define QUIT_EVENT (SDL_USEREVENT + 2)
int thread_exit = 0;
PeerPtr *peer_server = nullptr;
PeerPtr *peer_client = nullptr;
bool joined = false;
bool received_frame = false;
static bool connect_button_pressed = false;
static const char *connect_label = "Connect";
#ifdef _WIN32
ScreenCaptureWgc *screen_capture = nullptr;
#elif __linux__
ScreenCaptureX11 *screen_capture = nullptr;
#endif
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
typedef enum { mouse = 0, keyboard } ControlType;
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
typedef enum { key_down = 0, key_up } KeyFlag;
typedef struct {
size_t x;
size_t y;
MouseFlag flag;
} Mouse;
typedef struct {
size_t key_value;
KeyFlag flag;
} Key;
typedef struct {
ControlType type;
union {
Mouse m;
Key k;
};
} RemoteAction;
inline int ProcessMouseKeyEven(SDL_Event &ev) {
float ratio = 1280.0 / window_w;
RemoteAction remote_action;
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) {
if (SDL_BUTTON_LEFT == ev.button.button) {
int px = ev.button.x;
int py = ev.button.y;
// printf("SDL_MOUSEBUTTONDOWN x, y %d %d \n", px, py);
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::left_down;
remote_action.m.x = ev.button.x * ratio;
remote_action.m.y = ev.button.y * ratio;
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
} else if (SDL_BUTTON_RIGHT == ev.button.button) {
int px = ev.button.x;
int py = ev.button.y;
// printf("SDL_BUTTON_RIGHT x, y %d %d \n", px, py);
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::right_down;
remote_action.m.x = ev.button.x * ratio;
remote_action.m.y = ev.button.y * ratio;
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
}
} else if (SDL_MOUSEBUTTONUP == ev.type) {
if (SDL_BUTTON_LEFT == ev.button.button) {
int px = ev.button.x;
int py = ev.button.y;
// printf("SDL_MOUSEBUTTONUP x, y %d %d \n", px, py);
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::left_up;
remote_action.m.x = ev.button.x * ratio;
remote_action.m.y = ev.button.y * ratio;
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
} else if (SDL_BUTTON_RIGHT == ev.button.button) {
int px = ev.button.x;
int py = ev.button.y;
// printf("SDL_MOUSEBUTTONUP x, y %d %d \n", px, py);
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::right_up;
remote_action.m.x = ev.button.x * ratio;
remote_action.m.y = ev.button.y * ratio;
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
}
} else if (SDL_MOUSEMOTION == ev.type) {
int px = ev.motion.x;
int py = ev.motion.y;
// printf("SDL_MOUSEMOTION x, y %d %d \n", px, py);
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::move;
remote_action.m.x = ev.button.x * ratio;
remote_action.m.y = ev.button.y * ratio;
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
} else if (SDL_QUIT == ev.type) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
printf("SDL_QUIT\n");
return 0;
}
return 0;
}
void ReceiveVideoBuffer(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 ReceiveAudioBuffer(const char *data, size_t size, const char *user_id,
size_t user_id_size) {
std::cout << "Receive audio, size " << size << ", user [" << user_id << "] "
<< std::endl;
}
void ReceiveDataBuffer(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;
#ifdef _WIN32
INPUT ip;
if (remote_action.type == ControlType::mouse) {
ip.type = INPUT_MOUSE;
ip.mi.dx = remote_action.m.x * screen_w / 1280;
ip.mi.dy = remote_action.m.y * screen_h / 720;
if (remote_action.m.flag == MouseFlag::left_down) {
ip.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
} else if (remote_action.m.flag == MouseFlag::left_up) {
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
} else if (remote_action.m.flag == MouseFlag::right_down) {
ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE;
} else if (remote_action.m.flag == MouseFlag::right_up) {
ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE;
} else {
ip.mi.dwFlags = MOUSEEVENTF_MOVE;
}
ip.mi.mouseData = 0;
ip.mi.time = 0;
#if MOUSE_CONTROL
// Set cursor pos
SetCursorPos(ip.mi.dx, ip.mi.dy);
// Send the press
if (ip.mi.dwFlags != MOUSEEVENTF_MOVE) {
SendInput(1, &ip, sizeof(INPUT));
}
#endif
// std::cout << "Receive data from [" << user << "], " << ip.type << " "
// << ip.mi.dwFlags << " " << ip.mi.dx << " " << ip.mi.dy
// << std::endl;
}
#endif
}
void ConnectionStatus(ConnectionStatus status) {
if (ConnectionStatus::Connecting == status) {
connection_status = "Connecting";
} else if (ConnectionStatus::Connected == status) {
connection_status = "Connected";
} else if (ConnectionStatus::Failed == status) {
connection_status = "Failed";
} else if (ConnectionStatus::Closed == status) {
connection_status = "Closed";
} else if (ConnectionStatus::IncorrectPassword == status) {
connection_status = "Incorrect password";
if (connect_button_pressed) {
connect_button_pressed = false;
joined = false;
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
}
}
}
std::string GetMac(char *mac_addr) {
int len = 0;
#ifdef _WIN32
IP_ADAPTER_INFO adapterInfo[16];
DWORD bufferSize = sizeof(adapterInfo);
DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize);
if (result == ERROR_SUCCESS) {
PIP_ADAPTER_INFO adapter = adapterInfo;
while (adapter) {
for (UINT i = 0; i < adapter->AddressLength; i++) {
len += sprintf(mac_addr + len, "%.2X", adapter->Address[i]);
}
break;
}
}
#elif __APPLE__
std::string if_name = "en0";
struct ifaddrs *addrs;
struct ifaddrs *cursor;
const struct sockaddr_dl *dlAddr;
if (!getifaddrs(&addrs)) {
cursor = addrs;
while (cursor != 0) {
const struct sockaddr_dl *socAddr =
(const struct sockaddr_dl *)cursor->ifa_addr;
if ((cursor->ifa_addr->sa_family == AF_LINK) &&
(socAddr->sdl_type == IFT_ETHER) &&
strcmp(if_name.c_str(), cursor->ifa_name) == 0) {
dlAddr = (const struct sockaddr_dl *)cursor->ifa_addr;
const unsigned char *base =
(const unsigned char *)&dlAddr->sdl_data[dlAddr->sdl_nlen];
for (int i = 0; i < dlAddr->sdl_alen; i++) {
len += sprintf(mac_addr + len, "%.2X", base[i]);
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
#elif __linux__
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
return "";
}
struct ifreq ifr;
struct ifconf ifc;
char buf[1024];
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
close(sock);
return "";
}
struct ifreq *it = ifc.ifc_req;
const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq));
for (; it != end; ++it) {
std::strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
continue;
}
if (ifr.ifr_flags & IFF_LOOPBACK) {
continue;
}
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
continue;
}
std::string mac_address;
for (int i = 0; i < 6; ++i) {
len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff);
}
break;
}
close(sock);
#endif
return mac_addr;
}
int BGRAToNV12FFmpeg(unsigned char *src_buffer, int width, int height,
unsigned char *dst_buffer) {
AVFrame *Input_pFrame = av_frame_alloc();
AVFrame *Output_pFrame = av_frame_alloc();
struct SwsContext *img_convert_ctx =
sws_getContext(width, height, AV_PIX_FMT_BGRA, 1280, 720, AV_PIX_FMT_NV12,
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
av_image_fill_arrays(Input_pFrame->data, Input_pFrame->linesize, src_buffer,
AV_PIX_FMT_BGRA, width, height, 1);
av_image_fill_arrays(Output_pFrame->data, Output_pFrame->linesize, dst_buffer,
AV_PIX_FMT_NV12, 1280, 720, 1);
sws_scale(img_convert_ctx, (uint8_t const **)Input_pFrame->data,
Input_pFrame->linesize, 0, height, Output_pFrame->data,
Output_pFrame->linesize);
if (Input_pFrame) av_free(Input_pFrame);
if (Output_pFrame) av_free(Output_pFrame);
if (img_convert_ctx) sws_freeContext(img_convert_ctx);
return 0;
}
int main() {
LOG_INFO("Remote desk");
std::string default_cfg_path = "../../../../config/config.ini";
std::ifstream f(default_cfg_path.c_str());
Params params;
params.cfg_path = f.good() ? "../../../../config/config.ini" : "config.ini";
params.on_receive_video_buffer = ReceiveVideoBuffer;
params.on_receive_audio_buffer = ReceiveAudioBuffer;
params.on_receive_data_buffer = ReceiveDataBuffer;
params.on_connection_status = ConnectionStatus;
std::string transmission_id = "000001";
char mac_addr[10];
GetMac(mac_addr);
peer_server = CreatePeer(&params);
std::string server_user_id = "S-" + std::string(GetMac(mac_addr));
Init(peer_server, server_user_id.c_str());
peer_client = CreatePeer(&params);
std::string client_user_id = "C-" + std::string(GetMac(mac_addr));
Init(peer_client, client_user_id.c_str());
{
static char server_password[20] = "";
std::string user_id = "S-" + std::string(GetMac(mac_addr));
CreateConnection(peer_server, mac_addr, server_password);
nv12_buffer = new char[NV12_BUFFER_SIZE];
#ifdef _WIN32
screen_capture = new ScreenCaptureWgc();
RECORD_DESKTOP_RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = GetSystemMetrics(SM_CXSCREEN);
rect.bottom = GetSystemMetrics(SM_CYSCREEN);
last_frame_time_ = std::chrono::high_resolution_clock::now();
screen_capture->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) {
BGRAToNV12FFmpeg(data, width, height, (unsigned char *)nv12_buffer);
SendData(peer_server, DATA_TYPE::VIDEO, (const char *)nv12_buffer,
NV12_BUFFER_SIZE);
// std::cout << "Send" << std::endl;
last_frame_time_ = now_time;
}
});
screen_capture->Start();
#elif __linux__
screen_capture = new ScreenCaptureX11();
RECORD_DESKTOP_RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = 0;
rect.bottom = 0;
last_frame_time_ = std::chrono::high_resolution_clock::now();
screen_capture->Init(
rect, 60,
[](unsigned char *data, int size, int width, int height) -> void {
auto now_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = now_time - last_frame_time_;
auto tc = duration.count() * 1000;
if (tc >= 0) {
SendData(peer_server, DATA_TYPE::VIDEO, (const char *)data,
NV12_BUFFER_SIZE);
last_frame_time_ = now_time;
}
});
screen_capture->Start();
#endif
}
// Setup SDL
if (SDL_Init(SDL_INIT_VIDEO | 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);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForSDLRenderer(window, sdlRenderer);
ImGui_ImplSDLRenderer2_Init(sdlRenderer);
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Main loop
bool done = false;
while (!done) {
// Start the Dear ImGui frame
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
{
static float f = 0.0f;
static int counter = 0;
const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Once);
ImGui::SetNextWindowSize(ImVec2(180, 130));
ImGui::Begin("Menu", nullptr, ImGuiWindowFlags_NoResize);
{
// ImGui::Text("LOCAL ID: ");
// ImGui::SameLine();
// ImGui::Selectable(mac_addr, false,
// ImGuiSelectableFlags_AllowDoubleClick);
// if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
// ImGui::SetClipboardText(mac_addr);
// }
// ImGui::Spacing();
// ImGui::Text("PASSWORD: ");
// ImGui::SameLine();
// ImGui::SetNextItemWidth(110);
// static char server_password[20] = "";
// ImGui::InputTextWithHint("server_password", "000001",
// server_password,
// IM_ARRAYSIZE(server_password),
// ImGuiInputTextFlags_AllowTabInput);
// ImGui::Spacing();
// {
// static bool online_button_pressed = false;
// static const char *online_label = "Online";
// if (ImGui::Button(online_label)) {
// std::string user_id = "S-" +
// std::string(GetMac(mac_addr));
// if (strcmp(online_label, "Online") == 0) {
// CreateConnection(peer_server, mac_addr,
// server_password);
// nv12_buffer = new char[NV12_BUFFER_SIZE];
// #ifdef _WIN32
// screen_capture = new ScreenCaptureWgc();
// RECORD_DESKTOP_RECT rect;
// rect.left = 0;
// rect.top = 0;
// rect.right = GetSystemMetrics(SM_CXSCREEN);
// rect.bottom = GetSystemMetrics(SM_CYSCREEN);
// last_frame_time_ =
// std::chrono::high_resolution_clock::now();
// screen_capture->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) {
// BGRAToNV12FFmpeg(data, width, height,
// (unsigned char *)nv12_buffer);
// SendData(peer_server, DATA_TYPE::VIDEO,
// (const char *)nv12_buffer,
// NV12_BUFFER_SIZE);
// // std::cout << "Send" << std::endl;
// last_frame_time_ = now_time;
// }
// });
// screen_capture->Start();
// #elif __linux__
// screen_capture = new ScreenCaptureX11();
// RECORD_DESKTOP_RECT rect;
// rect.left = 0;
// rect.top = 0;
// rect.right = 0;
// rect.bottom = 0;
// last_frame_time_ =
// std::chrono::high_resolution_clock::now();
// screen_capture->Init(
// rect, 60,
// [](unsigned char *data, int size, int width,
// int height) -> void {
// auto now_time =
// std::chrono::high_resolution_clock::now();
// std::chrono::duration<double> duration =
// now_time - last_frame_time_;
// auto tc = duration.count() * 1000;
// if (tc >= 0) {
// SendData(peer_server, DATA_TYPE::VIDEO,
// (const char *)data, NV12_BUFFER_SIZE);
// last_frame_time_ = now_time;
// }
// });
// screen_capture->Start();
// #endif
// } else {
// LeaveConnection(peer_server);
// }
// online_button_pressed = !online_button_pressed;
// online_label = online_button_pressed ? "Offline" :
// "Online";
// }
// }
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
{
{
static char remote_id[20] = "";
if (strcmp(remote_id, "") == 0) {
strcpy(remote_id, GetMac(mac_addr).c_str());
}
ImGui::Text("REMOTE ID:");
ImGui::SameLine();
ImGui::SetNextItemWidth(110);
ImGui::InputTextWithHint("id_buf", "000002", remote_id,
IM_ARRAYSIZE(remote_id),
ImGuiInputTextFlags_AllowTabInput);
ImGui::Spacing();
// ImGui::Text("PASSWORD: ");
// ImGui::SameLine();
// ImGui::SetNextItemWidth(110);
static char client_password[20] = "";
// ImGui::InputTextWithHint("client_password", "000003",
// client_password,
// IM_ARRAYSIZE(client_password),
// ImGuiInputTextFlags_AllowTabInput);
if (ImGui::Button(connect_label)) {
if (strcmp(connect_label, "Connect") == 0 && !joined) {
std::string user_id = "C-" + std::string(GetMac(mac_addr));
JoinConnection(peer_client, remote_id, client_password);
joined = true;
} else if (strcmp(connect_label, "Disconnect") == 0 && joined) {
LeaveConnection(peer_client);
joined = false;
received_frame = false;
}
connect_button_pressed = !connect_button_pressed;
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
}
}
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
{
if (ImGui::Button("Fix ratio")) {
SDL_GetWindowSize(window, &window_w, &window_h);
if (window_h != window_w * 9 / 16) {
window_w = window_h * 16 / 9;
}
SDL_SetWindowSize(window, window_w, window_h);
}
}
ImGui::End();
}
// Rendering
ImGui::Render();
SDL_RenderSetScale(sdlRenderer, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_Event event;
while (SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT) {
done = true;
} else if (event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_RESIZED) {
SDL_GetWindowSize(window, &window_w, &window_h);
SDL_SetWindowSize(window, window_w, window_h);
// printf("Resize windows: %dx%d\n", 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 [" + connection_status + "]";
// For MacOS, UI frameworks can only be called from the main thread
SDL_SetWindowTitle(window, window_title.c_str());
start_time = end_time;
}
}
// Cleanup
LeaveConnection(peer_server);
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(window);
SDL_Quit();
int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) {
Render render;
render.Run();
return 0;
}

View File

@@ -0,0 +1,142 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-05-29
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _LOCALIZATION_H_
#define _LOCALIZATION_H_
#include <string>
#include <vector>
namespace localization {
static std::vector<std::string> local_desktop = {
reinterpret_cast<const char*>(u8"本桌面"), "Local Desktop"};
static std::vector<std::string> local_id = {
reinterpret_cast<const char*>(u8"本机ID"), "Local ID"};
static std::vector<std::string> local_id_copied_to_clipboard = {
reinterpret_cast<const char*>(u8"已复制到剪贴板"), "Copied to clipboard"};
static std::vector<std::string> password = {
reinterpret_cast<const char*>(u8"密码"), "Password"};
static std::vector<std::string> max_password_len = {
reinterpret_cast<const char*>(u8"最大6个字符"), "Max 6 chars"};
static std::vector<std::string> remote_desktop = {
reinterpret_cast<const char*>(u8"控制远程桌面"), "Control Remote Desktop"};
static std::vector<std::string> remote_id = {
reinterpret_cast<const char*>(u8"对端ID"), "Remote ID"};
static std::vector<std::string> connect = {
reinterpret_cast<const char*>(u8"连接"), "Connect"};
static std::vector<std::string> recent_connections = {
reinterpret_cast<const char*>(u8"近期连接"), "Recent Connections"};
static std::vector<std::string> disconnect = {
reinterpret_cast<const char*>(u8"断开连接"), "Disconnect"};
static std::vector<std::string> fullscreen = {
reinterpret_cast<const char*>(u8"全屏"), " Fullscreen"};
static std::vector<std::string> show_net_traffic_stats = {
reinterpret_cast<const char*>(u8"显示流量统计"), "Show Net Traffic Stats"};
static std::vector<std::string> hide_net_traffic_stats = {
reinterpret_cast<const char*>(u8"隐藏流量统计"), "Hide Net Traffic Stats"};
static std::vector<std::string> video = {
reinterpret_cast<const char*>(u8"视频"), "Video"};
static std::vector<std::string> audio = {
reinterpret_cast<const char*>(u8"音频"), "Audio"};
static std::vector<std::string> data = {reinterpret_cast<const char*>(u8"数据"),
"Data"};
static std::vector<std::string> total = {
reinterpret_cast<const char*>(u8"总计"), "Total"};
static std::vector<std::string> in = {reinterpret_cast<const char*>(u8"输入"),
"In"};
static std::vector<std::string> out = {reinterpret_cast<const char*>(u8"输出"),
"Out"};
static std::vector<std::string> loss_rate = {
reinterpret_cast<const char*>(u8"丢包率"), "Loss Rate"};
static std::vector<std::string> exit_fullscreen = {
reinterpret_cast<const char*>(u8"退出全屏"), "Exit fullscreen"};
static std::vector<std::string> control_mouse = {
reinterpret_cast<const char*>(u8"控制"), "Control"};
static std::vector<std::string> release_mouse = {
reinterpret_cast<const char*>(u8"释放"), "Release"};
static std::vector<std::string> audio_capture = {
reinterpret_cast<const char*>(u8"声音"), "Audio"};
static std::vector<std::string> mute = {
reinterpret_cast<const char*>(u8" 静音"), " Mute"};
static std::vector<std::string> settings = {
reinterpret_cast<const char*>(u8"设置"), "Settings"};
static std::vector<std::string> language = {
reinterpret_cast<const char*>(u8"语言:"), "Language:"};
static std::vector<std::string> language_zh = {
reinterpret_cast<const char*>(u8"中文"), "Chinese"};
static std::vector<std::string> language_en = {
reinterpret_cast<const char*>(u8"英文"), "English"};
static std::vector<std::string> video_quality = {
reinterpret_cast<const char*>(u8"视频质量:"), "Video Quality:"};
static std::vector<std::string> video_quality_high = {
reinterpret_cast<const char*>(u8""), "High"};
static std::vector<std::string> video_quality_medium = {
reinterpret_cast<const char*>(u8""), "Medium"};
static std::vector<std::string> video_quality_low = {
reinterpret_cast<const char*>(u8""), "Low"};
static std::vector<std::string> video_encode_format = {
reinterpret_cast<const char*>(u8"视频编码格式:"), "Video Encode Format:"};
static std::vector<std::string> av1 = {reinterpret_cast<const char*>(u8"AV1"),
"AV1"};
static std::vector<std::string> h264 = {
reinterpret_cast<const char*>(u8"H.264"), "H.264"};
static std::vector<std::string> enable_hardware_video_codec = {
reinterpret_cast<const char*>(u8"启用硬件编解码器:"),
"Enable Hardware Video Codec:"};
static std::vector<std::string> enable_turn = {
reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"};
static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"),
"OK"};
static std::vector<std::string> cancel = {
reinterpret_cast<const char*>(u8"取消"), "Cancel"};
static std::vector<std::string> new_password = {
reinterpret_cast<const char*>(u8"请输入六位密码:"),
"Please input a six-char password:"};
static std::vector<std::string> input_password = {
reinterpret_cast<const char*>(u8"请输入密码:"), "Please input password:"};
static std::vector<std::string> validate_password = {
reinterpret_cast<const char*>(u8"验证密码中..."), "Validate password ..."};
static std::vector<std::string> reinput_password = {
reinterpret_cast<const char*>(u8"请重新输入密码"),
"Please input password again"};
static std::vector<std::string> remember_password = {
reinterpret_cast<const char*>(u8"记住密码"), "Remember password"};
static std::vector<std::string> signal_connected = {
reinterpret_cast<const char*>(u8"已连接服务器"), "Connected"};
static std::vector<std::string> signal_disconnected = {
reinterpret_cast<const char*>(u8"未连接服务器"), "Disconnected"};
static std::vector<std::string> p2p_connected = {
reinterpret_cast<const char*>(u8"对等连接已建立"), "P2P Connected"};
static std::vector<std::string> p2p_disconnected = {
reinterpret_cast<const char*>(u8"对等连接已断开"), "P2P Disconnected"};
static std::vector<std::string> p2p_connecting = {
reinterpret_cast<const char*>(u8"正在建立对等连接..."),
"P2P Connecting ..."};
static std::vector<std::string> p2p_failed = {
reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"};
static std::vector<std::string> p2p_closed = {
reinterpret_cast<const char*>(u8"对等连接已关闭"), "P2P closed"};
static std::vector<std::string> no_such_id = {
reinterpret_cast<const char*>(u8"无此ID"), "No such ID"};
static std::vector<std::string> about = {
reinterpret_cast<const char*>(u8"关于"), "About"};
static std::vector<std::string> version = {
reinterpret_cast<const char*>(u8"版本"), "Version"};
static std::vector<std::string> confirm_delete_connection = {
reinterpret_cast<const char*>(u8"确认删除此连接"),
"Confirm to delete this connection"};
} // namespace localization
#endif

View File

@@ -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
View 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
View 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

View 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);
}

View 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

View File

@@ -1,131 +0,0 @@
#include "screen_capture_x11.h"
#include <iostream>
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
ScreenCaptureX11::ScreenCaptureX11() {}
ScreenCaptureX11::~ScreenCaptureX11() {
if (capture_thread_->joinable()) {
capture_thread_->join();
}
}
int ScreenCaptureX11::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
if (cb) {
_on_data = cb;
}
pFormatCtx_ = avformat_alloc_context();
avdevice_register_all();
// grabbing frame rate
av_dict_set(&options_, "framerate", "30", 0);
// Make the grabbed area follow the mouse
av_dict_set(&options_, "follow_mouse", "centered", 0);
// Video frame size. The default is to capture the full screen
av_dict_set(&options_, "video_size", "1280x720", 0);
ifmt_ = (AVInputFormat *)av_find_input_format("x11grab");
if (!ifmt_) {
printf("Couldn't find_input_format\n");
}
// Grab at position 10,20
if (avformat_open_input(&pFormatCtx_, ":0.0", ifmt_, &options_) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex_ = -1;
for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++)
if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex_ = i_;
break;
}
if (videoindex_ == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar;
pCodecCtx_ = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx_, pCodecParam_);
pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id));
if (pCodec_ == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
pFrame_ = av_frame_alloc();
pFrameNv12_ = av_frame_alloc();
const int pixel_w = 1280, pixel_h = 720;
int screen_w = 1280, screen_h = 720;
screen_w = 1280;
screen_h = 720;
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
img_convert_ctx_ =
sws_getContext(pCodecCtx_->width, pCodecCtx_->height, pCodecCtx_->pix_fmt,
pCodecCtx_->width, pCodecCtx_->height, AV_PIX_FMT_NV12,
SWS_BICUBIC, NULL, NULL, NULL);
return 0;
}
int ScreenCaptureX11::Start() {
capture_thread_.reset(new std::thread([this]() {
while (1) {
if (av_read_frame(pFormatCtx_, packet_) >= 0) {
if (packet_->stream_index == videoindex_) {
avcodec_send_packet(pCodecCtx_, packet_);
av_packet_unref(packet_);
got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_);
if (!got_picture_) {
av_image_fill_arrays(pFrameNv12_->data, pFrameNv12_->linesize,
nv12_buffer_, AV_PIX_FMT_NV12, pFrame_->width,
pFrame_->height, 1);
sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0,
pFrame_->height, pFrameNv12_->data,
pFrameNv12_->linesize);
_on_data((unsigned char *)nv12_buffer_,
pFrame_->width * pFrame_->height * 3 / 2, pFrame_->width,
pFrame_->height);
}
}
}
}
}));
return 0;
}
int ScreenCaptureX11::Pause() { return 0; }
int ScreenCaptureX11::Resume() { return 0; }
int ScreenCaptureX11::Stop() { return 0; }
void ScreenCaptureX11::OnFrame() {}
void ScreenCaptureX11::CleanUp() {}

View File

@@ -1,85 +0,0 @@
#ifndef _SCREEN_CAPTURE_X11_H_
#define _SCREEN_CAPTURE_X11_H_
#include <atomic>
#include <functional>
#include <string>
#include <thread>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
typedef struct {
int left;
int top;
int right;
int bottom;
} RECORD_DESKTOP_RECT;
typedef std::function<void(unsigned char *, int, int, int)> cb_desktop_data;
typedef std::function<void(int)> cb_desktop_error;
class ScreenCaptureX11 {
public:
ScreenCaptureX11();
~ScreenCaptureX11();
public:
int Init(const RECORD_DESKTOP_RECT &rect, const int fps, cb_desktop_data cb);
int Start();
int Pause();
int Resume();
int Stop();
void OnFrame();
protected:
void CleanUp();
private:
std::atomic_bool _running;
std::atomic_bool _paused;
std::atomic_bool _inited;
std::thread _thread;
std::string _device_name;
RECORD_DESKTOP_RECT _rect;
int _fps;
cb_desktop_data _on_data;
cb_desktop_error _on_error;
private:
int i_ = 0;
int videoindex_ = 0;
int got_picture_ = 0;
// ffmpeg
AVFormatContext *pFormatCtx_ = nullptr;
AVCodecContext *pCodecCtx_ = nullptr;
AVCodec *pCodec_ = nullptr;
AVCodecParameters *pCodecParam_ = nullptr;
AVDictionary *options_ = nullptr;
AVInputFormat *ifmt_ = nullptr;
AVFrame *pFrame_ = nullptr;
AVFrame *pFrameNv12_ = nullptr;
AVPacket *packet_ = nullptr;
struct SwsContext *img_convert_ctx_ = nullptr;
// thread
std::unique_ptr<std::thread> capture_thread_ = nullptr;
};
#endif

View File

@@ -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

View File

@@ -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() {}

View File

@@ -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

View File

@@ -1,26 +0,0 @@
#include "screen_capture_wgc.h"
#include <iostream>
ScreenCaptureWgc::ScreenCaptureWgc() {}
ScreenCaptureWgc::~ScreenCaptureWgc() {}
bool ScreenCaptureWgc::IsWgcSupported() { return false; }
int ScreenCaptureWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
return 0;
}
int ScreenCaptureWgc::Start() { return 0; }
int ScreenCaptureWgc::Pause() { return 0; }
int ScreenCaptureWgc::Resume() { return 0; }
int ScreenCaptureWgc::Stop() { return 0; }
void ScreenCaptureWgc::OnFrame() {}
void ScreenCaptureWgc::CleanUp() {}

View File

@@ -1,56 +0,0 @@
#ifndef _SCREEN_CAPTURE_WGC_H_
#define _SCREEN_CAPTURE_WGC_H_
#include <atomic>
#include <functional>
#include <string>
#include <thread>
typedef struct {
int left;
int top;
int right;
int bottom;
} RECORD_DESKTOP_RECT;
typedef std::function<void(unsigned char *, int, int, int)> cb_desktop_data;
typedef std::function<void(int)> cb_desktop_error;
class ScreenCaptureWgc {
public:
ScreenCaptureWgc();
~ScreenCaptureWgc();
public:
bool IsWgcSupported();
int Init(const RECORD_DESKTOP_RECT &rect, const int fps, cb_desktop_data cb);
int Start();
int Pause();
int Resume();
int Stop();
void OnFrame();
protected:
void CleanUp();
private:
std::atomic_bool _running;
std::atomic_bool _paused;
std::atomic_bool _inited;
std::thread _thread;
std::string _device_name;
RECORD_DESKTOP_RECT _rect;
int _fps;
cb_desktop_data _on_data;
cb_desktop_error _on_error;
};
#endif

View File

@@ -1,138 +0,0 @@
#include "screen_capture_wgc.h"
#include <Windows.h>
#include <d3d11_4.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <iostream>
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, HDC hdc, LPRECT lprc,
LPARAM data) {
MONITORINFOEX info_ex;
info_ex.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(hmonitor, &info_ex);
if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
if (info_ex.dwFlags & MONITORINFOF_PRIMARY) {
*(HMONITOR *)data = hmonitor;
}
return true;
}
HMONITOR GetPrimaryMonitor() {
HMONITOR hmonitor = nullptr;
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor);
return hmonitor;
}
ScreenCaptureWgc::ScreenCaptureWgc() {}
ScreenCaptureWgc::~ScreenCaptureWgc() {
Stop();
CleanUp();
}
bool ScreenCaptureWgc::IsWgcSupported() {
try {
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
return winrt::Windows::Foundation::Metadata::ApiInformation::
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
} catch (const winrt::hresult_error &) {
return false;
} catch (...) {
return false;
}
}
int ScreenCaptureWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
int error = 0;
if (_inited == true) return error;
_fps = fps;
_on_data = cb;
do {
if (!IsWgcSupported()) {
std::cout << "AE_UNSUPPORT" << std::endl;
error = 2;
break;
}
session_ = new WgcSessionImpl();
if (!session_) {
error = -1;
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
break;
}
session_->RegisterObserver(this);
error = session_->Initialize(GetPrimaryMonitor());
_inited = true;
} while (0);
if (error != 0) {
}
return error;
}
int ScreenCaptureWgc::Start() {
if (_running == true) {
std::cout << "record desktop duplication is already running" << std::endl;
return 0;
}
if (_inited == false) {
std::cout << "AE_NEED_INIT" << std::endl;
return 4;
}
_running = true;
session_->Start();
return 0;
}
int ScreenCaptureWgc::Pause() {
_paused = true;
if (session_) session_->Pause();
return 0;
}
int ScreenCaptureWgc::Resume() {
_paused = false;
if (session_) session_->Resume();
return 0;
}
int ScreenCaptureWgc::Stop() {
_running = false;
if (session_) session_->Stop();
return 0;
}
void ScreenCaptureWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
if (_on_data)
_on_data((unsigned char *)frame.data, frame.width * frame.height * 4,
frame.width, frame.height);
}
void ScreenCaptureWgc::CleanUp() {
_inited = false;
if (session_) session_->Release();
session_ = nullptr;
}

View File

@@ -1,61 +0,0 @@
#ifndef _SCREEN_CAPTURE_WGC_H_
#define _SCREEN_CAPTURE_WGC_H_
#include <atomic>
#include <functional>
#include <string>
#include <thread>
#include "wgc_session.h"
#include "wgc_session_impl.h"
typedef struct {
int left;
int top;
int right;
int bottom;
} RECORD_DESKTOP_RECT;
typedef std::function<void(unsigned char *, int, int, int)> cb_desktop_data;
typedef std::function<void(int)> cb_desktop_error;
class ScreenCaptureWgc : public WgcSession::wgc_session_observer {
public:
ScreenCaptureWgc();
~ScreenCaptureWgc();
public:
bool IsWgcSupported();
int Init(const RECORD_DESKTOP_RECT &rect, const int fps, cb_desktop_data cb);
int Start();
int Pause();
int Resume();
int Stop();
void OnFrame(const WgcSession::wgc_session_frame &frame);
protected:
void CleanUp();
private:
WgcSession *session_ = nullptr;
std::atomic_bool _running;
std::atomic_bool _paused;
std::atomic_bool _inited;
std::thread _thread;
std::string _device_name;
RECORD_DESKTOP_RECT _rect;
int _fps;
cb_desktop_data _on_data;
cb_desktop_error _on_error;
};
#endif

View File

@@ -0,0 +1,174 @@
#include "screen_capturer_x11.h"
#include <chrono>
#include <thread>
#include "libyuv.h"
#include "rd_log.h"
ScreenCapturerX11::ScreenCapturerX11() {}
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
display_ = XOpenDisplay(nullptr);
if (!display_) {
LOG_ERROR("Cannot connect to X server");
return -1;
}
root_ = DefaultRootWindow(display_);
screen_res_ = XRRGetScreenResources(display_, root_);
if (!screen_res_) {
LOG_ERROR("Failed to get screen resources");
XCloseDisplay(display_);
return 1;
}
for (int i = 0; i < screen_res_->noutput; ++i) {
RROutput output = screen_res_->outputs[i];
XRROutputInfo* output_info =
XRRGetOutputInfo(display_, screen_res_, output);
if (output_info->connection == RR_Connected && output_info->crtc != 0) {
XRRCrtcInfo* crtc_info =
XRRGetCrtcInfo(display_, screen_res_, output_info->crtc);
display_info_list_.push_back(
DisplayInfo((void*)display_, output_info->name, true, crtc_info->x,
crtc_info->y, crtc_info->width, crtc_info->height));
XRRFreeCrtcInfo(crtc_info);
}
if (output_info) {
XRRFreeOutputInfo(output_info);
}
}
XWindowAttributes attr;
XGetWindowAttributes(display_, root_, &attr);
width_ = attr.width;
height_ = attr.height;
if (width_ % 2 != 0 || height_ % 2 != 0) {
LOG_ERROR("Width and height must be even numbers");
return -2;
}
fps_ = fps;
callback_ = cb;
y_plane_.resize(width_ * height_);
uv_plane_.resize((width_ / 2) * (height_ / 2) * 2);
return 0;
}
int ScreenCapturerX11::Destroy() {
Stop();
y_plane_.clear();
uv_plane_.clear();
if (screen_res_) {
XRRFreeScreenResources(screen_res_);
screen_res_ = nullptr;
}
if (display_) {
XCloseDisplay(display_);
display_ = nullptr;
}
return 0;
}
int ScreenCapturerX11::Start() {
if (running_) return 0;
running_ = true;
paused_ = false;
thread_ = std::thread([this]() {
while (running_) {
if (!paused_) OnFrame();
}
});
return 0;
}
int ScreenCapturerX11::Stop() {
if (!running_) return 0;
running_ = false;
if (thread_.joinable()) thread_.join();
return 0;
}
int ScreenCapturerX11::Pause(int monitor_index) {
paused_ = true;
return 0;
}
int ScreenCapturerX11::Resume(int monitor_index) {
paused_ = false;
return 0;
}
int ScreenCapturerX11::SwitchTo(int monitor_index) {
monitor_index_ = monitor_index;
return 0;
}
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
return display_info_list_;
}
void ScreenCapturerX11::OnFrame() {
if (!display_) {
LOG_ERROR("Display is not initialized");
return;
}
if (monitor_index_ < 0 || monitor_index_ >= display_info_list_.size()) {
LOG_ERROR("Invalid monitor index: {}", monitor_index_.load());
return;
}
left_ = display_info_list_[monitor_index_].left;
top_ = display_info_list_[monitor_index_].top;
width_ = display_info_list_[monitor_index_].width;
height_ = display_info_list_[monitor_index_].height;
XImage* image = XGetImage(display_, root_, left_, top_, width_, height_,
AllPlanes, ZPixmap);
if (!image) return;
bool needs_copy = image->bytes_per_line != width_ * 4;
std::vector<uint8_t> argb_buf;
uint8_t* src_argb = nullptr;
if (needs_copy) {
argb_buf.resize(width_ * height_ * 4);
for (int y = 0; y < height_; ++y) {
memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line,
width_ * 4);
}
src_argb = argb_buf.data();
} else {
src_argb = reinterpret_cast<uint8_t*>(image->data);
}
libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_,
uv_plane_.data(), width_, width_, height_);
std::vector<uint8_t> nv12;
nv12.reserve(y_plane_.size() + uv_plane_.size());
nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end());
nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end());
if (callback_) {
callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_,
display_info_list_[monitor_index_].name.c_str());
}
XDestroyImage(image);
}

View File

@@ -0,0 +1,64 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-05-07
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_X11_H_
#define _SCREEN_CAPTURER_X11_H_
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrandr.h>
#include <atomic>
#include <cstring>
#include <functional>
#include <iostream>
#include <thread>
#include <vector>
#include "screen_capturer.h"
class ScreenCapturerX11 : public ScreenCapturer {
public:
ScreenCapturerX11();
~ScreenCapturerX11();
public:
int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
int SwitchTo(int monitor_index) override;
std::vector<DisplayInfo> GetDisplayInfoList() override;
void OnFrame();
private:
Display* display_ = nullptr;
Window root_ = 0;
XRRScreenResources* screen_res_ = nullptr;
int left_ = 0;
int top_ = 0;
int width_ = 0;
int height_ = 0;
std::thread thread_;
std::atomic<bool> running_{false};
std::atomic<bool> paused_{false};
std::atomic<int> monitor_index_{0};
int fps_ = 30;
cb_desktop_data callback_;
std::vector<DisplayInfo> display_info_list_;
// 缓冲区
std::vector<uint8_t> y_plane_;
std::vector<uint8_t> uv_plane_;
};
#endif

View 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() {}

View 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

View 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

View File

@@ -0,0 +1,34 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-15
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_H_
#define _SCREEN_CAPTURER_H_
#include <functional>
#include "display_info.h"
class ScreenCapturer {
public:
typedef std::function<void(unsigned char*, int, int, int, const char*)>
cb_desktop_data;
public:
virtual ~ScreenCapturer() {}
public:
virtual int Init(const int fps, cb_desktop_data cb) = 0;
virtual int Destroy() = 0;
virtual int Start() = 0;
virtual int Stop() = 0;
virtual int Pause(int monitor_index) = 0;
virtual int Resume(int monitor_index) = 0;
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
virtual int SwitchTo(int monitor_index) = 0;
};
#endif

View File

@@ -0,0 +1,38 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-15
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_FACTORY_H_
#define _SCREEN_CAPTURER_FACTORY_H_
#ifdef _WIN32
#include "screen_capturer_wgc.h"
#elif __linux__
#include "screen_capturer_x11.h"
#elif __APPLE__
// #include "screen_capturer_avf.h"
#include "screen_capturer_sck.h"
#endif
class ScreenCapturerFactory {
public:
virtual ~ScreenCapturerFactory() {}
public:
ScreenCapturer* Create() {
#ifdef _WIN32
return new ScreenCapturerWgc();
#elif __linux__
return new ScreenCapturerX11();
#elif __APPLE__
// return new ScreenCapturerAvf();
return new ScreenCapturerSck();
#else
return nullptr;
#endif
}
};
#endif

View File

@@ -0,0 +1,272 @@
#include "screen_capturer_wgc.h"
#include <Windows.h>
#include <d3d11_4.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <iostream>
#include "libyuv.h"
#include "rd_log.h"
static std::vector<DisplayInfo> gs_display_list;
std::string WideToUtf8(const wchar_t *wideStr) {
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
nullptr, nullptr);
std::string result(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr,
nullptr);
result.pop_back();
return result;
}
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
[[maybe_unused]] LPRECT lprc, LPARAM data) {
MONITORINFOEX monitor_info_;
monitor_info_.cbSize = sizeof(MONITORINFOEX);
if (GetMonitorInfo(hmonitor, &monitor_info_)) {
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
gs_display_list.insert(
gs_display_list.begin(),
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR *)data = hmonitor;
} else {
gs_display_list.push_back(DisplayInfo(
(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
}
}
if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
return true;
}
HMONITOR GetPrimaryMonitor() {
HMONITOR hmonitor = nullptr;
gs_display_list.clear();
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor);
return hmonitor;
}
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
ScreenCapturerWgc::~ScreenCapturerWgc() {
Stop();
CleanUp();
if (nv12_frame_) {
delete nv12_frame_;
nv12_frame_ = nullptr;
}
if (nv12_frame_scaled_) {
delete nv12_frame_scaled_;
nv12_frame_scaled_ = nullptr;
}
}
bool ScreenCapturerWgc::IsWgcSupported() {
try {
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
return winrt::Windows::Foundation::Metadata::ApiInformation::
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
} catch (const winrt::hresult_error &) {
return false;
} catch (...) {
return false;
}
}
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
int error = 0;
if (inited_ == true) return error;
// nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2];
// nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2];
fps_ = fps;
on_data_ = cb;
if (!IsWgcSupported()) {
LOG_ERROR("WGC not supported");
error = 2;
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::Start() {
if (running_ == true) {
LOG_ERROR("Screen capturer already running");
return 0;
}
if (inited_ == false) {
LOG_ERROR("Screen capturer not inited");
return 4;
}
for (int i = 0; i < sessions_.size(); i++) {
if (sessions_[i].inited_ == false) {
LOG_ERROR("Session {} not inited", i);
continue;
}
if (sessions_[i].running_) {
LOG_ERROR("Session {} is already running", i);
} else {
sessions_[i].session_->Start();
if (i != 0) {
sessions_[i].session_->Pause();
sessions_[i].paused_ = true;
}
sessions_[i].running_ = true;
}
running_ = true;
}
return 0;
}
int ScreenCapturerWgc::Pause(int monitor_index) {
if (monitor_index >= sessions_.size() || monitor_index < 0) {
LOG_ERROR("Invalid session index: {}", monitor_index);
return -1;
}
if (!sessions_[monitor_index].paused_) {
sessions_[monitor_index].session_->Pause();
sessions_[monitor_index].paused_ = true;
LOG_INFO("Pausing session {}", monitor_index);
}
return 0;
}
int ScreenCapturerWgc::Resume(int monitor_index) {
if (monitor_index >= sessions_.size() || monitor_index < 0) {
LOG_ERROR("Invalid session index: {}", monitor_index);
return -1;
}
if (sessions_[monitor_index].paused_) {
sessions_[monitor_index].session_->Resume();
sessions_[monitor_index].paused_ = false;
LOG_INFO("Resuming session {}", monitor_index);
}
return 0;
}
int ScreenCapturerWgc::Stop() {
for (int i = 0; i < sessions_.size(); i++) {
if (sessions_[i].running_) {
sessions_[i].session_->Stop();
sessions_[i].running_ = false;
}
}
running_ = false;
return 0;
}
int ScreenCapturerWgc::SwitchTo(int monitor_index) {
if (monitor_index_ == monitor_index) {
LOG_INFO("Already on monitor {}:{}", monitor_index_ + 1,
display_info_list_[monitor_index_].name);
return 0;
}
if (monitor_index >= display_info_list_.size()) {
LOG_ERROR("Invalid monitor index: {}", monitor_index);
return -1;
}
if (!sessions_[monitor_index].inited_) {
LOG_ERROR("Monitor {} not inited", monitor_index);
return -1;
}
Pause(monitor_index_);
monitor_index_ = monitor_index;
LOG_INFO("Switching to monitor {}:{}", monitor_index_,
display_info_list_[monitor_index_].name);
Resume(monitor_index);
return 0;
}
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame,
int id) {
if (on_data_) {
if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
}
libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4,
(uint8_t *)nv12_frame_, frame.width,
(uint8_t *)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height);
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height, display_info_list_[id].name.c_str());
}
}
void ScreenCapturerWgc::CleanUp() {
if (inited_) {
for (auto &session : sessions_) {
if (session.session_) {
session.session_->Stop();
}
}
sessions_.clear();
}
}

View File

@@ -0,0 +1,68 @@
#ifndef _SCREEN_CAPTURER_WGC_H_
#define _SCREEN_CAPTURER_WGC_H_
#include <atomic>
#include <functional>
#include <string>
#include <thread>
#include <vector>
#include "screen_capturer.h"
#include "wgc_session.h"
#include "wgc_session_impl.h"
class ScreenCapturerWgc : public ScreenCapturer,
public WgcSession::wgc_session_observer {
public:
ScreenCapturerWgc();
~ScreenCapturerWgc();
public:
bool IsWgcSupported();
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;
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
int SwitchTo(int monitor_index);
void OnFrame(const WgcSession::wgc_session_frame &frame, int id);
protected:
void CleanUp();
private:
HMONITOR monitor_;
MONITORINFOEX monitor_info_;
std::vector<DisplayInfo> display_info_list_;
int monitor_index_ = 0;
private:
class WgcSessionInfo {
public:
std::unique_ptr<WgcSession> session_;
bool inited_ = false;
bool running_ = false;
bool paused_ = false;
};
std::vector<WgcSessionInfo> sessions_;
std::atomic_bool running_;
std::atomic_bool inited_;
int fps_;
cb_desktop_data on_data_ = nullptr;
unsigned char *nv12_frame_ = nullptr;
unsigned char *nv12_frame_scaled_ = nullptr;
};
#endif

View File

@@ -16,7 +16,7 @@ class WgcSession {
class wgc_session_observer {
public:
virtual ~wgc_session_observer() {}
virtual void OnFrame(const wgc_session_frame &frame) = 0;
virtual void OnFrame(const wgc_session_frame &frame, int id) = 0;
};
public:
@@ -33,7 +33,6 @@ class WgcSession {
virtual int Pause() = 0;
virtual int Resume() = 0;
protected:
virtual ~WgcSession(){};
};

View File

@@ -23,7 +23,7 @@ HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
}
WgcSessionImpl::WgcSessionImpl() {}
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
WgcSessionImpl::~WgcSessionImpl() {
Stop();
@@ -89,6 +89,8 @@ int WgcSessionImpl::Start() {
capture_session_.StartCapture();
capture_session_.IsCursorCaptureEnabled(false);
error = 0;
} catch (winrt::hresult_error) {
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
@@ -122,6 +124,8 @@ int WgcSessionImpl::Stop() {
int WgcSessionImpl::Pause() {
std::lock_guard locker(lock_);
is_paused_ = true;
CHECK_INIT;
return 0;
}
@@ -129,6 +133,8 @@ int WgcSessionImpl::Pause() {
int WgcSessionImpl::Resume() {
std::lock_guard locker(lock_);
is_paused_ = false;
CHECK_INIT;
return 0;
}
@@ -146,7 +152,7 @@ auto WgcSessionImpl::CreateD3D11Device() {
if (DXGI_ERROR_UNSUPPORTED == hr) {
// change D3D_DRIVER_TYPE
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_WARP;
type = D3D_DRIVER_TYPE_WARP;
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
nullptr);
@@ -211,7 +217,7 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
void WgcSessionImpl::OnFrame(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender,
winrt::Windows::Foundation::IInspectable const &args) {
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const &args) {
std::lock_guard locker(lock_);
auto is_new_size = false;
@@ -231,6 +237,10 @@ void WgcSessionImpl::OnFrame(
// copy to mapped texture
{
if (is_paused_) {
return;
}
auto frame_captured =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
@@ -254,11 +264,13 @@ void WgcSessionImpl::OnFrame(
// copy data from map_result.pData
if (map_result.pData && observer_) {
observer_->OnFrame(wgc_session_frame{
static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height), map_result.RowPitch,
const_cast<const unsigned char *>(
(unsigned char *)map_result.pData)});
observer_->OnFrame(
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height),
map_result.RowPitch,
const_cast<const unsigned char *>(
(unsigned char *)map_result.pData)},
id_);
}
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);

View File

@@ -35,7 +35,7 @@ class WgcSessionImpl : public WgcSession {
} target_{0};
public:
WgcSessionImpl();
WgcSessionImpl(int id);
~WgcSessionImpl() override;
public:
@@ -72,6 +72,7 @@ class WgcSessionImpl : public WgcSession {
// void message_func();
private:
int id_ = -1;
std::mutex lock_;
bool is_initialized_ = false;
bool is_running_ = false;

File diff suppressed because it is too large Load Diff

View 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;
}

View File

@@ -0,0 +1,171 @@
#include "layout_style.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
bool Render::ConnectionStatusWindow(
std::shared_ptr<SubStreamWindowProperties> &props) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
bool ret_flag = false;
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
connection_status_window_width_) /
2,
(viewport->WorkSize.y - viewport->WorkPos.y -
connection_status_window_height_) /
2));
ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_,
connection_status_window_height_));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::Begin("ConnectionStatusWindow", nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoSavedSettings);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::SetWindowFontScale(0.5f);
std::string text;
if (ConnectionStatus::Connecting == props->connection_status_) {
text = localization::p2p_connecting[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
} else if (ConnectionStatus::Connected == props->connection_status_) {
text = localization::p2p_connected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Disconnected == props->connection_status_) {
text = localization::p2p_disconnected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Failed == props->connection_status_) {
text = localization::p2p_failed[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Closed == props->connection_status_) {
text = localization::p2p_closed[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::IncorrectPassword == props->connection_status_) {
if (!password_validating_) {
if (password_validating_time_ == 1) {
text = localization::input_password[localization_language_index_];
} else {
text = localization::reinput_password[localization_language_index_];
}
auto window_width = ImGui::GetWindowSize().x;
auto window_height = ImGui::GetWindowSize().y;
ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f);
ImGui::SetCursorPosY(window_height * 0.4f);
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
if (focus_on_input_widget_) {
ImGui::SetKeyboardFocusHere();
focus_on_input_widget_ = false;
}
ImGui::InputText("##password", props->remote_password_,
IM_ARRAYSIZE(props->remote_password_),
ImGuiInputTextFlags_CharsNoBlank);
ImGui::SetWindowFontScale(0.4f);
ImVec2 text_size = ImGui::CalcTextSize(
localization::remember_password[localization_language_index_]
.c_str());
ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f);
ImGui::Checkbox(
localization::remember_password[localization_language_index_].c_str(),
&(props->remember_password_));
ImGui::SetWindowFontScale(0.5f);
ImGui::PopStyleVar();
ImGui::SetCursorPosX(window_width * 0.315f);
ImGui::SetCursorPosY(window_height * 0.75f);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = true;
password_validating_ = true;
props->rejoin_ = true;
need_to_rejoin_ = true;
focus_on_input_widget_ = true;
}
ImGui::SameLine();
// cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
memset(props->remote_password_, 0, sizeof(props->remote_password_));
show_connection_status_window_ = false;
focus_on_input_widget_ = true;
}
} else if (password_validating_) {
text = localization::validate_password[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
}
} else if (ConnectionStatus::NoSuchTransmissionId ==
props->connection_status_) {
text = localization::no_such_id[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = false;
re_enter_remote_id_ = true;
DestroyPeer(&props->peer_);
ret_flag = true;
}
}
auto window_width = ImGui::GetWindowSize().x;
auto window_height = ImGui::GetWindowSize().y;
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
ImGui::SetCursorPosY(window_height * 0.2f);
ImGui::Text("%s", text.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar();
return ret_flag;
}

View 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;
}

View 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;
}

View 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

View 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;
}

View File

@@ -0,0 +1,49 @@
#include "localization.h"
#include "rd_log.h"
#include "render.h"
int Render::MainWindow() {
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::BeginChild("DeskWindow",
ImVec2(main_window_width_default_, local_window_height_),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
LocalWindow();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddLine(
ImVec2(main_window_width_default_ / 2, title_bar_height_ + 15.0f),
ImVec2(main_window_width_default_ / 2, title_bar_height_ + 225.0f),
IM_COL32(0, 0, 0, 122), 1.0f);
RemoteWindow();
ImGui::EndChild();
RecentConnectionsWindow();
StatusBar();
if (show_connection_status_window_) {
for (auto it = client_properties_.begin();
it != client_properties_.end();) {
auto& props = it->second;
if (focused_remote_id_ == props->remote_id_) {
if (ConnectionStatusWindow(props)) {
it = client_properties_.erase(it);
} else {
++it;
}
} else {
++it;
}
}
}
return 0;
}

View 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;
}

View File

@@ -0,0 +1,181 @@
#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);
focused_remote_id_ = remote_id;
if (client_properties_.find(remote_id) == client_properties_.end()) {
client_properties_[remote_id] =
std::make_shared<SubStreamWindowProperties>();
auto props = client_properties_[remote_id];
props->local_id_ = "C-" + std::string(client_id_);
props->remote_id_ = remote_id;
memcpy(&props->params_, &params_, 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;
}

1384
src/single_window/render.cpp Normal file

File diff suppressed because it is too large Load Diff

440
src/single_window/render.h Normal file
View File

@@ -0,0 +1,440 @@
/*
* @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();
bool ConnectionStatusWindow(
std::shared_ptr<SubStreamWindowProperties> &props);
int ShowRecentConnections();
private:
int ConnectTo(const std::string &remote_id, const char *password,
bool remember_password);
int CreateMainWindow();
int DestroyMainWindow();
int CreateStreamWindow();
int DestroyStreamWindow();
int SetupFontAndStyle();
int SetupMainWindow();
int DestroyMainWindowContext();
int SetupStreamWindow();
int DestroyStreamWindowContext();
int DrawMainWindow();
int DrawStreamWindow();
int ConfirmDeleteConnection();
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties> &props);
void DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties> &props);
public:
static void OnReceiveVideoBufferCb(const XVideoFrame *video_frame,
const char *user_id, size_t user_id_size,
void *user_data);
static void OnReceiveAudioBufferCb(const char *data, size_t size,
const char *user_id, size_t user_id_size,
void *user_data);
static void OnReceiveDataBufferCb(const char *data, size_t size,
const char *user_id, size_t user_id_size,
void *user_data);
static void OnSignalStatusCb(SignalStatus status, const char *user_id,
size_t user_id_size, void *user_data);
static void OnConnectionStatusCb(ConnectionStatus status, const char *user_id,
size_t user_id_size, void *user_data);
static void NetStatusReport(const char *client_id, size_t client_id_size,
TraversalMode mode,
const XNetTrafficStats *net_traffic_stats,
const char *user_id, const size_t user_id_size,
void *user_data);
static SDL_HitTestResult HitTestCallback(SDL_Window *window,
const SDL_Point *area, void *data);
static std::vector<char> SerializeRemoteAction(const RemoteAction &action);
static bool DeserializeRemoteAction(const char *data, size_t size,
RemoteAction &out);
static void FreeRemoteAction(RemoteAction &action);
private:
int SendKeyCommand(int key_code, bool is_down);
int ProcessMouseEvent(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_ = "";
std::string focused_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

View 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;
}
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,199 @@
#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);
focused_remote_id_ = props->remote_id_;
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;
}

View 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);
}

View 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

View 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;
}

View 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;
}

View 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

View 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

View 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(); }

View 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

View 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

View 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; }

View 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

View File

@@ -1,309 +0,0 @@
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
// Windows
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include "SDL2/SDL.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C" {
#endif
#include <SDL2/SDL.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
#endif
#include <chrono>
// Output YUV420P
#define OUTPUT_YUV420P 0
//'1' Use Dshow
//'0' Use GDIgrab
#define USE_DSHOW 0
// Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
int thread_exit = 0;
SDL_Texture *sdlTexture = nullptr;
SDL_Renderer *sdlRenderer = nullptr;
SDL_Rect sdlRect;
unsigned char nv12_buffer[NV12_BUFFER_SIZE];
std::chrono::_V2::system_clock::time_point last_frame_time;
const int pixel_w = 1280, pixel_h = 720;
int screen_w = 1280, screen_h = 720;
bool done = false;
int YUV420ToNV12FFmpeg(unsigned char *src_buffer, int width, int height,
unsigned char *des_buffer) {
AVFrame *Input_pFrame = av_frame_alloc();
AVFrame *Output_pFrame = av_frame_alloc();
struct SwsContext *img_convert_ctx = sws_getContext(
width, height, AV_PIX_FMT_NV12, width, height, AV_PIX_FMT_YUV420P,
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
av_image_fill_arrays(Input_pFrame->data, Input_pFrame->linesize, src_buffer,
AV_PIX_FMT_NV12, width, height, 1);
av_image_fill_arrays(Output_pFrame->data, Output_pFrame->linesize, des_buffer,
AV_PIX_FMT_YUV420P, width, height, 1);
sws_scale(img_convert_ctx, (uint8_t const **)Input_pFrame->data,
Input_pFrame->linesize, 0, height, Output_pFrame->data,
Output_pFrame->linesize);
if (Input_pFrame) av_free(Input_pFrame);
if (Output_pFrame) av_free(Output_pFrame);
if (img_convert_ctx) sws_freeContext(img_convert_ctx);
return 0;
}
int sfp_refresh_thread(void *opaque) {
thread_exit = 0;
while (!thread_exit) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(30);
printf("sfp_refresh_thread\n");
}
thread_exit = 0;
// Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);
printf("exit sfp_refresh_thread\n");
return 0;
}
int main(int argc, char *argv[]) {
AVFormatContext *pFormatCtx;
int i, videoindex;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVCodecParameters *pCodecParam;
// avformat_network_init();
pFormatCtx = avformat_alloc_context();
// Open File
char filepath[] = "out.h264";
// avformat_open_input(&pFormatCtx, filepath, NULL, NULL);
// Register Device
avdevice_register_all();
// Windows
// Linux
AVDictionary *options = NULL;
// Set some options
// 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);
AVInputFormat *ifmt = (AVInputFormat *)av_find_input_format("x11grab");
if (!ifmt) {
printf("Couldn't find_input_format\n");
}
// Grab at position 10,20
if (avformat_open_input(&pFormatCtx, ":0.0", ifmt, &options) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
if (videoindex == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
pCodecParam = pFormatCtx->streams[videoindex]->codecpar;
pCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx, pCodecParam);
pCodec = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx->codec_id));
if (pCodec == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
AVFrame *pFrame, *pFrameYUV, *pFrameNV12;
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
pFrameNV12 = av_frame_alloc();
// unsigned char *out_buffer=(unsigned char
// *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width,
// pCodecCtx->height)); avpicture_fill((AVPicture *)pFrameYUV, out_buffer,
// AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
// SDL----------------------------
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
// const SDL_VideoInfo *vi = SDL_GetVideoInfo();
// Half of the Desktop's width and height.
screen_w = 1280;
screen_h = 720;
// SDL_Surface *screen;
// screen = SDL_SetVideoMode(screen_w, screen_h, 0, 0);
SDL_Window *screen;
screen = SDL_CreateWindow("Linux Capture", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h,
SDL_WINDOW_RESIZABLE);
if (!screen) {
printf("SDL: could not set video mode - exiting:%s\n", SDL_GetError());
return -1;
}
// SDL_Overlay *bmp;
// bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,
// SDL_YV12_OVERLAY, screen);
sdlRenderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
Uint32 pixformat = 0;
pixformat = SDL_PIXELFORMAT_NV12;
SDL_Texture *sdlTexture = nullptr;
sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat,
SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = screen_w;
rect.h = screen_h;
// SDL End------------------------
int ret, got_picture;
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width,
pCodecCtx->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
//------------------------------
SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
//
// SDL_WM_SetCaption("Simplest FFmpeg Grab Desktop", NULL);
// Event Loop
SDL_Event event;
last_frame_time = std::chrono::high_resolution_clock::now();
for (;;) {
// Wait
SDL_WaitEvent(&event);
if (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);
// ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,
// packet);
if (ret < 0) {
printf("Decode Error.\n");
return -1;
}
printf("xxxxxxxxxxxxxxxxxxx\n");
if (!got_picture) {
auto now_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = now_time - last_frame_time;
auto tc = duration.count() * 1000;
printf("duration: %f\n", tc);
last_frame_time = now_time;
av_image_fill_arrays(pFrameNV12->data, pFrameNV12->linesize,
nv12_buffer, AV_PIX_FMT_NV12, pFrame->width,
pFrame->height, 1);
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0,
pFrame->height, pFrameNV12->data, pFrameNV12->linesize);
SDL_UpdateTexture(sdlTexture, NULL, nv12_buffer, pixel_w);
// FIX: If window is resize
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent(sdlRenderer);
}
}
// av_free_packet(packet);
} else {
// Exit Thread
// thread_exit = 1;
// printf("No frame read\n");
}
} else if (event.type == SDL_QUIT) {
printf("SDL_QUIT\n");
thread_exit = 1;
} else if (event.type == SFM_BREAK_EVENT) {
break;
}
}
sws_freeContext(img_convert_ctx);
#if OUTPUT_YUV420P
fclose(fp_yuv);
#endif
SDL_Quit();
// av_free(out_buffer);
av_frame_free(&pFrameNV12);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
getchar();
return 0;
}

View File

@@ -1,206 +0,0 @@
package("ffmpeg")
set_homepage("https://www.ffmpeg.org")
set_description("A collection of libraries to process multimedia content such as audio, video, subtitles and related metadata.")
set_license("GPL-3.0")
if is_plat("windows", "mingw") then
add_urls("https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-$(version)-full_build-shared.7z")
add_versions("5.1.2", "d9eb97b72d7cfdae4d0f7eaea59ccffb8c364d67d88018ea715d5e2e193f00e9")
add_versions("5.0.1", "ded28435b6f04b74f5ef5a6a13761233bce9e8e9f8ecb0eabe936fd36a778b0c")
add_configs("shared", {description = "Download shared binaries.", default = true, type = "boolean", readonly = true})
add_configs("vs_runtime", {description = "Set vs compiler runtime.", default = "MD", readonly = true})
else
add_urls("https://ffmpeg.org/releases/ffmpeg-$(version).tar.bz2", {alias = "home"})
add_urls("https://github.com/FFmpeg/FFmpeg/archive/n$(version).zip", {alias = "github"})
add_urls("https://git.ffmpeg.org/ffmpeg.git", "https://github.com/FFmpeg/FFmpeg.git", {alias = "git"})
add_versions("home:5.1.2", "39a0bcc8d98549f16c570624678246a6ac736c066cebdb409f9502e915b22f2b")
add_versions("home:5.1.1", "cd0e16f903421266d5ccddedf7b83b9e5754aef4b9f7a7f06ce9e4c802f0545b")
add_versions("home:5.0.1", "28df33d400a1c1c1b20d07a99197809a3b88ef765f5f07dc1ff067fac64c59d6")
add_versions("home:4.0.2", "346c51735f42c37e0712e0b3d2f6476c86ac15863e4445d9e823fe396420d056")
add_versions("github:5.1.2", "0c99f3609160f40946e2531804175eea16416320c4b6365ad075e390600539db")
add_versions("github:5.1.1", "a886fcc94792764c27c88ebe71dffbe5f0d37df8f06f01efac4833ac080c11bf")
add_versions("github:5.0.1", "f9c2e06cafa4381df8d5c9c9e14d85d9afcbc10c516c6a206f821997cc7f6440")
add_versions("github:4.0.2", "4df1ef0bf73b7148caea1270539ef7bd06607e0ea8aa2fbf1bb34062a097f026")
add_versions("git:5.1.2", "n5.1.2")
add_versions("git:5.1.1", "n5.1.1")
add_versions("git:5.0.1", "n5.0.1")
add_versions("git:4.0.2", "n4.0.2")
add_configs("gpl", {description = "Enable GPL code", default = false, type = "boolean"})
add_configs("ffprobe", {description = "Enable ffprobe program.", default = false, type = "boolean"})
add_configs("ffmpeg", {description = "Enable ffmpeg program.", default = true, type = "boolean"})
add_configs("ffplay", {description = "Enable ffplay program.", default = false, type = "boolean"})
add_configs("zlib", {description = "Enable zlib compression library.", default = false, type = "boolean"})
add_configs("lzma", {description = "Enable liblzma compression library.", default = false, type = "boolean"})
add_configs("bzlib", {description = "Enable bzlib compression library.", default = false, type = "boolean"})
add_configs("libx264", {description = "Enable libx264 decoder.", default = false, type = "boolean"})
add_configs("libx265", {description = "Enable libx265 decoder.", default = false, type = "boolean"})
add_configs("iconv", {description = "Enable libiconv library.", default = false, type = "boolean"})
add_configs("vaapi", {description = "Enable vaapi library.", default = false, type = "boolean"})
add_configs("vdpau", {description = "Enable vdpau library.", default = false, type = "boolean"})
add_configs("hardcoded-tables", {description = "Enable hardcoded tables.", default = true, type = "boolean"})
add_configs("asm", {description = "Enable asm", default = false, type = "boolean"})
add_configs("libopenh264", {description = "Enable libopenh264", default = true, type = "boolean"})
end
add_links("avfilter", "avdevice", "avformat", "avcodec", "swscale", "swresample", "avutil")
if is_plat("macosx") then
add_frameworks("CoreFoundation", "Foundation", "CoreVideo", "CoreMedia", "AudioToolbox", "VideoToolbox", "Security")
elseif is_plat("linux") then
add_syslinks("pthread", "openh264")
end
if is_plat("linux", "macosx") then
add_deps("yasm")
end
if on_fetch then
on_fetch("mingw", "linux", "macosx", function (package, opt)
import("lib.detect.find_tool")
if opt.system then
local result
for _, name in ipairs({"libavcodec", "libavdevice", "libavfilter", "libavformat", "libavutil", "libpostproc", "libswresample", "libswscale"}) do
local pkginfo = package:find_package("pkgconfig::" .. name, opt)
if pkginfo then
pkginfo.version = nil
if not result then
result = pkginfo
else
result = result .. pkginfo
end
else
return
end
end
local ffmpeg = find_tool("ffmpeg", {check = "-help", version = true, command = "-version", parse = "%d+%.?%d+%.?%d+", force = true})
if ffmpeg then
result.version = ffmpeg.version
end
return result
end
end)
end
on_load("linux", "macosx", "android", function (package)
local configdeps = {zlib = "zlib",
bzlib = "bzip2",
lzma = "xz",
libx264 = "x264",
libx265 = "x265",
iconv = "libiconv"}
for name, dep in pairs(configdeps) do
if package:config(name) then
package:add("deps", dep)
end
end
-- https://www.ffmpeg.org/platform.html#toc-Advanced-linking-configuration
if package:config("pic") ~= false and not package:is_plat("macosx") then
package:add("shflags", "-Wl,-Bsymbolic")
package:add("ldflags", "-Wl,-Bsymbolic")
end
if not package:config("gpl") then
package:set("license", "LGPL-3.0")
end
end)
on_install("windows|x64", "mingw|x86_64", function (package)
os.cp("bin", package:installdir())
os.cp("include", package:installdir())
os.cp("lib", package:installdir())
package:addenv("PATH", "bin")
end)
on_install("linux", "macosx", "android@linux,macosx", function (package)
local configs = {"--enable-version3",
"--disable-doc"}
if package:config("gpl") then
table.insert(configs, "--enable-gpl")
end
if package:is_plat("macosx") and macos.version():ge("10.8") then
table.insert(configs, "--enable-videotoolbox")
end
for name, enabled in pairs(package:configs()) do
if not package:extraconf("configs", name, "builtin") then
if enabled then
table.insert(configs, "--enable-" .. name)
else
table.insert(configs, "--disable-" .. name)
end
end
end
if package:config("shared") then
table.insert(configs, "--enable-shared")
table.insert(configs, "--disable-static")
else
table.insert(configs, "--enable-static")
table.insert(configs, "--disable-shared")
end
if package:debug() then
table.insert(configs, "--enable-debug")
else
table.insert(configs, "--disable-debug")
end
if package:is_plat("android") then
import("core.base.option")
import("core.tool.toolchain")
local ndk = toolchain.load("ndk", {plat = package:plat(), arch = package:arch()})
local bin = ndk:bindir()
local ndk_sdkver = ndk:config("ndk_sdkver")
local arch, cpu, triple, cross_prefix
if package:is_arch("arm64-v8a") then
arch = "arm64"
cpu = "armv8-a"
triple = "aarch64-linux-android"
cross_prefix = path.join(bin, "aarch64-linux-android-")
elseif package:arch():startswith("arm") then
arch = "arm"
cpu = "armv7-a"
triple = "armv7a-linux-androideabi"
cross_prefix = path.join(bin, "arm-linux-androideabi-")
else
raise("unknown arch(%s) for android!", package:arch())
end
local sysroot = path.join(path.directory(bin), "sysroot")
local cflags = table.join(table.wrap(package:config("cxflags")), table.wrap(package:config("cflags")), table.wrap(get_config("cxflags")), get_config("cflags"))
local cxxflags = table.join(table.wrap(package:config("cxflags")), table.wrap(package:config("cxxflags")), table.wrap(get_config("cxflags")), get_config("cxxflags"))
assert(os.isdir(sysroot), "we do not support old version ndk!")
if package:is_arch("arm64-v8a") then
table.insert(cflags, "-mfpu=neon")
table.insert(cflags, "-mfloat-abi=soft")
else
table.insert(cflags, "-mfpu=neon")
table.insert(cflags, "-mfloat-abi=soft")
end
table.insert(configs, "--enable-neon")
table.insert(configs, "--enable-asm")
table.insert(configs, "--enable-jni")
table.insert(configs, "--target-os=android")
table.insert(configs, "--enable-cross-compile")
table.insert(configs, "--disable-avdevice")
table.insert(configs, "--arch=" .. arch)
table.insert(configs, "--cpu=" .. cpu)
table.insert(configs, "--cc=" .. path.join(bin, triple .. ndk_sdkver .. "-clang"))
table.insert(configs, "--cxx=" .. path.join(bin, triple .. ndk_sdkver .. "-clang++"))
table.insert(configs, "--extra-cflags=" .. table.concat(cflags, ' '))
table.insert(configs, "--extra-cxxflags=" .. table.concat(cxxflags, ' '))
table.insert(configs, "--sysroot=" .. sysroot)
table.insert(configs, "--cross-prefix=" .. cross_prefix)
table.insert(configs, "--prefix=" .. package:installdir())
os.vrunv("./configure", configs)
local argv = {"-j4"}
if option.get("verbose") then
table.insert(argv, "V=1")
end
os.vrunv("make", argv)
os.vrun("make install")
else
import("package.tools.autoconf").install(package, configs)
end
package:addenv("PATH", "bin")
end)
on_test(function (package)
assert(package:has_cfuncs("avformat_open_input", {includes = "libavformat/avformat.h"}))
end)

Some files were not shown because too many files have changed in this diff Show More