339 Commits

Author SHA1 Message Date
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
111 changed files with 64743 additions and 3950 deletions

186
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,186 @@
name: Build and Release CrossDesk
on:
push:
branches: [release]
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
# Linux
build-linux:
name: Build on Ubuntu
runs-on: ubuntu-22.04
container:
image: crossdesk/ubuntu22.04:latest
options: --user root
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
ls -la $XMAKE_GLOBALDIR
xmake b -vy --root crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package
run: |
chmod +x ./scripts/linux/pkg_x86_64.sh
./scripts/linux/pkg_x86_64.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-x86_64
path: ${{ github.workspace }}/CrossDesk-0.0.1.deb
# macOS
build-macos:
name: Build on macOS
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: x86_64
runner: macos-13
cache-key: intel
out-dir: ./build/macosx/x86_64/release/crossdesk
artifact-name: crossdesk-macos-x86_64
package_script: ./scripts/macosx/pkg_x86_64.sh
- arch: arm64
runner: macos-14
cache-key: arm
out-dir: ./build/macosx/arm64/release/crossdesk
artifact-name: crossdesk-macos-arm64
package_script: ./scripts/macosx/pkg_arm64.sh
steps:
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: ~/.xmake/packages
key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
- name: Install xmake
run: brew install xmake
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Build CrossDesk
run: xmake b -vy crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package CrossDesk app
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: crossdesk-macos-${{ matrix.arch }}-v0.0.1.pkg
- name: Move files to release dir
run: |
mkdir -p release
cp crossdesk-macos-${{ matrix.arch }}-v0.0.1.pkg release/
# Windows
build-windows:
name: Build on Windows
runs-on: windows-2022
env:
XMAKE_GLOBALDIR: D:\xmake_global
steps:
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: D:\xmake_global\.xmake\packages
key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-intel-
- name: Install xmake
run: |
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
xmake create cuda
Set-Location cuda
xmake g --theme=plain
$cudaPath = ""
$packagesPath = "D:\xmake_global\.xmake\packages"
if (Test-Path $packagesPath) {
Write-Host "Packages directory exists: $packagesPath"
try {
$info = xmake require --info "cuda 12.6.3" 2>$null
if ($null -ne $info -and $info -ne "") {
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
} catch {}
} else {
Write-Host "Packages directory not found: $packagesPath"
Write-Host "Installing CUDA package..."
xmake require -vy "cuda 12.6.3"
$info = xmake require --info "cuda 12.6.3"
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
Write-Host "Resolved CUDA_PATH = $cudaPath"
Pop-Location
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Build CrossDesk
run: xmake b -vy crossdesk
- name: Decode and save certificate
shell: powershell
run: |
New-Item -ItemType Directory -Force -Path certs
[System.IO.File]::WriteAllBytes('certs\crossdesk.cn_root.crt', [Convert]::FromBase64String('${{ secrets.CROSSDESK_CERT_BASE64 }}'))
- name: Package
run: |
cd ./scripts/windows
makensis nsis_script.nsi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-win-x86_64
path: ${{ github.workspace }}/scripts/windows/CrossDesk-0.0.1.exe

6
.gitmodules vendored
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

39
Info.plist Normal file
View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- 应用的Bundle identifier通常使用反向域名标记 -->
<key>CFBundleIdentifier</key>
<string>com.yourcompany.yourappname</string>
<!-- 应用的显示名称 -->
<key>CFBundleName</key>
<string>Your App Name</string>
<!-- 应用的版本号 -->
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<!-- 应用的构建版本号 -->
<key>CFBundleVersion</key>
<string>1</string>
<!-- 请求麦克风访问权限 -->
<key>NSMicrophoneUsageDescription</key>
<string>App requires access to the microphone for audio recording.</string>
<!-- 请求相机访问权限 -->
<key>NSCameraUsageDescription</key>
<string>App requires access to the camera for video recording.</string>
<!-- 请求使用连续相机设备 -->
<key>NSCameraUseContinuityCameraDeviceType</key>
<string>Your usage description here</string>
<!-- High DPI -->>
<key>NSHighResolutionCapable</key>
<true/>
<!-- 其他权限和配置可以在这里添加 -->
</dict>
</plist>

View File

@@ -1,4 +1,4 @@
# Continuous Desk
# CrossDesk
#### More than remote desktop
@@ -9,9 +9,9 @@
# Intro
Continuous Desk is a lightweight cross-platform remote desktop. It allows multiple users to remotely control the same computer at the same time. In addition to desktop image transmission, it also supports end-to-end voice transmission, providing collaboration capabilities on the basis of remote desktop.
CrossDesk is a lightweight cross-platform remote desktop. It allows multiple users to remotely control the same computer at the same time. In addition to desktop image transmission, it also supports end-to-end voice transmission, providing collaboration capabilities on the basis of remote desktop.
Continuous Desk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video softwar/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)).
CrossDesk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)).
## Usage

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

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

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

@@ -0,0 +1,126 @@
#!/bin/bash
set -e
# 配置变量
APP_NAME="CrossDesk"
APP_VERSION="0.0.1"
ARCHITECTURE="amd64"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client."
# 目录结构
DEB_DIR="$APP_NAME-$APP_VERSION"
DEBIAN_DIR="$DEB_DIR/DEBIAN"
BIN_DIR="$DEB_DIR/usr/local/bin"
CERT_SRC_DIR="$DEB_DIR/opt/$APP_NAME/certs" # 用于中转安装时分发
ICON_DIR="$DEB_DIR/usr/share/icons/hicolor/256x256/apps"
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
# 清理已有的打包文件夹
rm -rf "$DEB_DIR"
# 创建目录结构
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$ICON_DIR" "$DESKTOP_DIR"
# 复制二进制文件
cp build/linux/x86_64/release/crossdesk "$BIN_DIR"
# 复制证书文件(将来通过 postinst 拷贝到每个用户 XDG_CONFIG_HOME
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
# 复制图标文件
cp icons/crossdesk.png "$ICON_DIR/crossdesk.png"
# 设置可执行权限
chmod +x "$BIN_DIR/crossdesk"
# 创建 control 文件
cat > "$DEBIAN_DIR/control" << EOF
Package: $APP_NAME
Version: $APP_VERSION
Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER
Description: $DESCRIPTION
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
libsndio7.0, libxcb-shm0, libpulse0, nvidia-cuda-toolkit
Priority: optional
Section: utils
EOF
# 创建 desktop 文件
cat > "$DESKTOP_DIR/$APP_NAME.desktop" << EOF
[Desktop Entry]
Version=$APP_VERSION
Name=$APP_NAME
Comment=$DESCRIPTION
Exec=/usr/local/bin/crossdesk
Icon=crossdesk
Terminal=false
Type=Application
Categories=Utility;
EOF
# 创建卸载脚本 postrm
cat > "$DEBIAN_DIR/postrm" << EOF
#!/bin/bash
# post-removal script for $APP_NAME
set -e
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
rm -f /usr/local/bin/crossdesk
rm -f /usr/share/icons/hicolor/256x256/apps/crossdesk.png
rm -f /usr/share/applications/$APP_NAME.desktop
rm -rf /opt/$APP_NAME
fi
exit 0
EOF
chmod +x "$DEBIAN_DIR/postrm"
# 创建安装后脚本 postinst拷贝证书到每个用户 XDG_CONFIG_HOME
cat > "$DEBIAN_DIR/postinst" << 'EOF'
#!/bin/bash
set -e
CERT_SRC="/opt/CrossDesk/certs"
CERT_FILE="crossdesk.cn_root.crt"
# 处理每个普通用户的配置目录
for user_home in /home/*; do
[ -d "$user_home" ] || continue
username=$(basename "$user_home")
config_dir="$user_home/.config/CrossDesk/certs"
target="$config_dir/$CERT_FILE"
if [ ! -f "$target" ]; then
mkdir -p "$config_dir"
cp "$CERT_SRC/$CERT_FILE" "$target"
chown -R "$username:$username" "$user_home/.config/CrossDesk"
echo "✔ Installed cert for $username at $target"
fi
done
# 处理 root 用户(可选)
if [ -d "/root" ]; then
config_dir="/root/.config/CrossDesk/certs"
mkdir -p "$config_dir"
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE"
chown -R root:root /root/.config/CrossDesk
fi
exit 0
EOF
chmod +x "$DEBIAN_DIR/postinst"
# 构建 .deb 包
dpkg-deb --build "$DEB_DIR"
# 清理构建目录
rm -rf "$DEB_DIR"
echo "✅ Deb package for $APP_NAME created successfully."

152
scripts/macosx/pkg_arm64.sh Normal file
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="0.0.1"
PLATFORM="macos"
ARCH="arm64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="./icons/crossdesk.icns" # .icns 图标路径
MACOS_MIN_VERSION="10.12"
CERTS_SOURCE="./certs" # 你的证书文件目录,里面放所有需要安装的文件
CERT_NAME="crossdesk.cn_root.crt"
APP_BUNDLE="${APP_NAME_UPPER}.app" # 使用大写的应用名称
CONTENTS_DIR="${APP_BUNDLE}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.pkg" # 保持安装包名称小写
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.dmg"
VOL_NAME="Install ${APP_NAME_UPPER}"
# === 清理旧文件 ===
echo "🧹 清理旧文件..."
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
mkdir -p build_pkg_temp
# === 创建 .app 结构 ===
echo "📦 创建 ${APP_BUNDLE}..."
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
echo "🚚 拷贝可执行文件..."
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}" # 拷贝时使用大写的应用名称
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
# === 图标 ===
if [ -f "${ICON_PATH}" ]; then
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
echo "🎨 图标添加完成"
else
ICON_KEY=""
echo "⚠️ 未找到图标文件,跳过图标设置"
fi
# === 生成 Info.plist ===
echo "📝 生成 Info.plist..."
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME_UPPER}</string> <!-- 使用大写名称 -->
<key>CFBundleDisplayName</key>
<string>${APP_NAME_UPPER}</string> <!-- 使用大写名称 -->
<key>CFBundleIdentifier</key>
<string>${IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${APP_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${APP_VERSION}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME_UPPER}</string> <!-- 使用大写名称 -->
<key>CFBundlePackageType</key>
<string>APPL</string>
${ICON_KEY}
<key>LSMinimumSystemVersion</key>
<string>${MACOS_MIN_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>应用需要访问摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>应用需要访问麦克风</string>
<key>NSAppleEventsUsageDescription</key>
<string>应用需要发送 Apple 事件</string>
<key>NSScreenCaptureUsageDescription</key>
<string>应用需要录屏权限以捕获屏幕内容</string>
</dict>
</plist>
EOF
echo "✅ .app 创建完成"
# === 构建应用组件包 ===
echo "📦 构建应用组件包..."
pkgbuild \
--identifier "${IDENTIFIER}" \
--version "${APP_VERSION}" \
--install-location "/Applications" \
--component "${APP_BUNDLE}" \
build_pkg_temp/${APP_NAME}-component.pkg
# === 构建 certs 组件包 ===
# 先创建脚本目录和脚本文件
mkdir -p scripts
cat > scripts/postinstall <<'EOF'
#!/bin/bash
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
mkdir -p "$DEST"
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
exit 0
EOF
chmod +x scripts/postinstall
# 构建 certs 组件包,增加 --scripts 参数指定 postinstall
pkgbuild \
--root "${CERTS_SOURCE}" \
--identifier "${IDENTIFIER}.certs" \
--version "${APP_VERSION}" \
--install-location "/Library/Application Support/CrossDesk/certs" \
--scripts scripts \
build_pkg_temp/${APP_NAME}-certs.pkg
# === 组合产品包 ===
echo "🏗️ 组合最终安装包..."
productbuild \
--package build_pkg_temp/${APP_NAME}-component.pkg \
--package build_pkg_temp/${APP_NAME}-certs.pkg \
"${PKG_NAME}"
echo "✅ 生成安装包完成:${PKG_NAME}"
# === 可选:打包成 DMG ===
echo "📦 可选打包成 DMG..."
mkdir -p CrossDesk_dmg_temp
cp "${PKG_NAME}" CrossDesk_dmg_temp/
ln -s /Applications CrossDesk_dmg_temp/Applications
hdiutil create -volname "${VOL_NAME}" \
-srcfolder CrossDesk_dmg_temp \
-ov -format UDZO "${DMG_NAME}"
rm -rf CrossDesk_dmg_temp build_pkg_temp scripts ${APP_BUNDLE} ${DMG_NAME}
echo "🎉 所有打包完成:"
echo " ✔️ 应用:${APP_BUNDLE}"
echo " ✔️ 安装包:${PKG_NAME}"
echo " ✔️ 镜像包(可选):${DMG_NAME}"

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="0.0.1"
PLATFORM="macos"
ARCH="x86_64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/crossdesk.icns" # .icns 图标路径
MACOS_MIN_VERSION="10.12"
CERTS_SOURCE="certs" # 你的证书文件目录,里面放所有需要安装的文件
CERT_NAME="crossdesk.cn_root.crt"
APP_BUNDLE="${APP_NAME_UPPER}.app" # 使用大写的应用名称
CONTENTS_DIR="${APP_BUNDLE}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.pkg" # 保持安装包名称小写
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-v${APP_VERSION}.dmg"
VOL_NAME="Install ${APP_NAME_UPPER}"
# === 清理旧文件 ===
echo "🧹 清理旧文件..."
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
mkdir -p build_pkg_temp
# === 创建 .app 结构 ===
echo "📦 创建 ${APP_BUNDLE}..."
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
echo "🚚 拷贝可执行文件..."
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}" # 拷贝时使用大写的应用名称
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
# === 图标 ===
if [ -f "${ICON_PATH}" ]; then
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
echo "🎨 图标添加完成"
else
ICON_KEY=""
echo "⚠️ 未找到图标文件,跳过图标设置"
fi
# === 生成 Info.plist ===
echo "📝 生成 Info.plist..."
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME_UPPER}</string> <!-- 使用大写名称 -->
<key>CFBundleDisplayName</key>
<string>${APP_NAME_UPPER}</string> <!-- 使用大写名称 -->
<key>CFBundleIdentifier</key>
<string>${IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${APP_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${APP_VERSION}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME_UPPER}</string> <!-- 使用大写名称 -->
<key>CFBundlePackageType</key>
<string>APPL</string>
${ICON_KEY}
<key>LSMinimumSystemVersion</key>
<string>${MACOS_MIN_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>应用需要访问摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>应用需要访问麦克风</string>
<key>NSAppleEventsUsageDescription</key>
<string>应用需要发送 Apple 事件</string>
<key>NSScreenCaptureUsageDescription</key>
<string>应用需要录屏权限以捕获屏幕内容</string>
</dict>
</plist>
EOF
echo "✅ .app 创建完成"
# === 构建应用组件包 ===
echo "📦 构建应用组件包..."
pkgbuild \
--identifier "${IDENTIFIER}" \
--version "${APP_VERSION}" \
--install-location "/Applications" \
--component "${APP_BUNDLE}" \
build_pkg_temp/${APP_NAME}-component.pkg
# === 构建 certs 组件包 ===
# 先创建脚本目录和脚本文件
mkdir -p scripts
cat > scripts/postinstall <<'EOF'
#!/bin/bash
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
mkdir -p "$DEST"
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
exit 0
EOF
chmod +x scripts/postinstall
# 构建 certs 组件包,增加 --scripts 参数指定 postinstall
pkgbuild \
--root "${CERTS_SOURCE}" \
--identifier "${IDENTIFIER}.certs" \
--version "${APP_VERSION}" \
--install-location "/Library/Application Support/CrossDesk/certs" \
--scripts scripts \
build_pkg_temp/${APP_NAME}-certs.pkg
# === 组合产品包 ===
echo "🏗️ 组合最终安装包..."
productbuild \
--package build_pkg_temp/${APP_NAME}-component.pkg \
--package build_pkg_temp/${APP_NAME}-certs.pkg \
"${PKG_NAME}"
echo "✅ 生成安装包完成:${PKG_NAME}"
# === 可选:打包成 DMG ===
echo "📦 可选打包成 DMG..."
mkdir -p CrossDesk_dmg_temp
cp "${PKG_NAME}" CrossDesk_dmg_temp/
ln -s /Applications CrossDesk_dmg_temp/Applications
hdiutil create -volname "${VOL_NAME}" \
-srcfolder CrossDesk_dmg_temp \
-ov -format UDZO "${DMG_NAME}"
rm -rf CrossDesk_dmg_temp build_pkg_temp scripts ${APP_BUNDLE} ${DMG_NAME}
echo "🎉 所有打包完成:"
echo " ✔️ 应用:${APP_BUNDLE}"
echo " ✔️ 安装包:${PKG_NAME}"
echo " ✔️ 镜像包(可选):${DMG_NAME}"

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 "0.0.1"
!define PRODUCT_PUBLISHER "CrossDesk"
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
!define APP_NAME "CrossDesk"
!define UNINSTALL_REG_KEY "CrossDesk"
; <20><><EFBFBD>ð<EFBFBD>װ<EFBFBD><D7B0>ͼ<EFBFBD><CDBC>·<EFBFBD><C2B7>
!define MUI_ICON "${__FILEDIR__}\..\..\icons\crossdesk.ico"
; <20><><EFBFBD><EFBFBD>֤<EFBFBD><D6A4>·<EFBFBD><C2B7>
!define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt"
; ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
SetCompressor /FINAL lzma
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԱȨ<D4B1>ޣ<EFBFBD>д<EFBFBD><D0B4>HKLM<4C><4D>Ҫ<EFBFBD><D2AA>
RequestExecutionLevel admin
; ------ MUI <20>ִ<EFBFBD><D6B4><EFBFBD><EFBFBD><EFBFBD><E6B6A8> ------
!include "MUI.nsh"
!define MUI_ABORTWARNING
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
; ------ MUI <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ------
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "CrossDesk-${PRODUCT_VERSION}.exe"
InstallDir "$PROGRAMFILES\CrossDesk"
ShowInstDetails show
Section "MainSection"
SetOutPath "$INSTDIR"
SetOverwrite ifnewer
; <20><><EFBFBD>ó<EFBFBD><C3B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>·<EFBFBD><C2B7>
File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
; ? <20><><EFBFBD><EFBFBD>ͼ<EFBFBD><CDBC><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD>װĿ¼
File "${MUI_ICON}"
; д<><D0B4>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>Ϣ
WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\crossdesk.ico"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1
SectionEnd
Section "Cert"
SetOutPath "$APPDATA\CrossDesk\certs"
File /r "${CERT_FILE}"
SectionEnd
Section -AdditionalIcons
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
; <20><>ʼ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
; <20><>ҳ<EFBFBD><D2B3><EFBFBD>ݷ<EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD>
WriteIniStr "$DESKTOP\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
SectionEnd
Section "Uninstall"
; ɾ<><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD>س<EFBFBD><D8B3><EFBFBD>
Delete "$INSTDIR\crossdesk.exe"
Delete "$INSTDIR\uninstall.exe"
; <20>ݹ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD><EFBFBD>װĿ¼
RMDir /r "$INSTDIR"
; ɾ<><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϳ<EFBFBD>ʼ<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD>ݷ<EFBFBD>ʽ
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
Delete "$DESKTOP\${PRODUCT_NAME}.url"
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
; ɾ<><C9BE>ע<EFBFBD><D7A2><EFBFBD><EFBFBD>ж<EFBFBD><D0B6><EFBFBD><EFBFBD>
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
; <20>ݹ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD>û<EFBFBD> AppData <20>е<EFBFBD> CrossDesk <20>ļ<EFBFBD><C4BC><EFBFBD>
RMDir /r "$APPDATA\CrossDesk"
RMDir /r "$LOCALAPPDATA\CrossDesk"
SectionEnd
Section -Post
SectionEnd

44
src/common/display_info.h Normal file
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

View File

@@ -1,6 +1,6 @@
#include "platform.h"
#include "log.h"
#include "rd_log.h"
#ifdef _WIN32
#include <Winsock2.h>
@@ -25,13 +25,13 @@ std::string GetMac() {
#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]);
len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
adapter->Address[i]);
}
break;
}
@@ -55,7 +55,8 @@ std::string GetMac() {
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]);
len +=
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
}
}
cursor = cursor->ifa_next;
@@ -98,4 +99,27 @@ std::string GetMac() {
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;
}

View File

@@ -10,5 +10,6 @@
#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

@@ -9,12 +9,31 @@
#include <stdio.h>
typedef enum { mouse = 0, keyboard } ControlType;
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
#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 {
size_t x;
size_t y;
float x;
float y;
int s;
MouseFlag flag;
} Mouse;
@@ -23,22 +42,42 @@ typedef struct {
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) = 0;
virtual int Destroy() = 0;
virtual int SendCommand(RemoteAction remote_action) = 0;
// 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

@@ -8,6 +8,7 @@
#define _DEVICE_CONTROLLER_FACTORY_H_
#include "device_controller.h"
#include "keyboard_capturer.h"
#include "mouse_controller.h"
class DeviceControllerFactory {
@@ -23,7 +24,7 @@ class DeviceControllerFactory {
case Mouse:
return new MouseController();
case Keyboard:
return nullptr;
return new KeyboardCapturer();
default:
return nullptr;
}

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

@@ -1,122 +1,124 @@
#include "mouse_controller.h"
#include "log.h"
#include <X11/extensions/XTest.h>
#include "rd_log.h"
MouseController::MouseController() {}
MouseController::~MouseController() {}
MouseController::~MouseController() { Destroy(); }
int MouseController::Init(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
uinput_fd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (uinput_fd_ < 0) {
LOG_ERROR("Cannot open device: /dev/uinput");
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;
}
ioctl(uinput_fd_, UI_SET_EVBIT, EV_KEY);
ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_RIGHT);
ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_LEFT);
ioctl(uinput_fd_, UI_SET_EVBIT, EV_ABS);
ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_X);
ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_Y);
ioctl(uinput_fd_, UI_SET_EVBIT, EV_REL);
root_ = DefaultRootWindow(display_);
struct uinput_user_dev uidev;
memset(&uidev, 0, sizeof(uidev));
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "VirtualMouse");
uidev.id.bustype = BUS_USB;
uidev.id.version = 1;
uidev.id.vendor = 0x1;
uidev.id.product = 0x1;
uidev.absmin[ABS_X] = 0;
uidev.absmax[ABS_X] = screen_width_;
uidev.absmin[ABS_Y] = 0;
uidev.absmax[ABS_Y] = screen_height_;
int 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;
}
write(uinput_fd_, &uidev, sizeof(uidev));
ioctl(uinput_fd_, UI_DEV_CREATE);
return 0;
}
int MouseController::Destroy() {
ioctl(uinput_fd_, UI_DEV_DESTROY);
close(uinput_fd_);
if (display_) {
XCloseDisplay(display_);
display_ = nullptr;
}
return 0;
}
int MouseController::SendCommand(RemoteAction remote_action) {
int mouse_pos_x = remote_action.m.x * screen_width_ / 1280;
int mouse_pos_y = remote_action.m.y * screen_height_ / 720;
if (remote_action.type == ControlType::mouse) {
struct input_event event;
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
if (remote_action.m.flag == MouseFlag::left_down) {
SimulateKeyDown(uinput_fd_, BTN_LEFT);
} else if (remote_action.m.flag == MouseFlag::left_up) {
SimulateKeyUp(uinput_fd_, BTN_LEFT);
} else if (remote_action.m.flag == MouseFlag::right_down) {
SimulateKeyDown(uinput_fd_, BTN_RIGHT);
} else if (remote_action.m.flag == MouseFlag::right_up) {
SimulateKeyUp(uinput_fd_, BTN_RIGHT);
} else {
SetMousePosition(uinput_fd_, mouse_pos_x, mouse_pos_y);
}
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::SimulateKeyDown(int fd, int kval) {
struct input_event event;
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, 0);
event.type = EV_KEY;
event.value = 1;
event.code = kval;
write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
write(fd, &event, sizeof(event));
void MouseController::SetMousePosition(int x, int y) {
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
XFlush(display_);
}
void MouseController::SimulateKeyUp(int fd, int kval) {
struct input_event event;
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, 0);
event.type = EV_KEY;
event.value = 0;
event.code = kval;
write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
write(fd, &event, sizeof(event));
void MouseController::SimulateKeyDown(int kval) {
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
XFlush(display_);
}
void MouseController::SetMousePosition(int fd, int x, int y) {
struct input_event ev[2], ev_sync;
memset(ev, 0, sizeof(ev));
memset(&ev_sync, 0, sizeof(ev_sync));
void MouseController::SimulateKeyUp(int kval) {
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
XFlush(display_);
}
ev[0].type = EV_ABS;
ev[0].code = ABS_X;
ev[0].value = x;
ev[1].type = EV_ABS;
ev[1].code = ABS_Y;
ev[1].value = y;
int res_w = write(fd, ev, sizeof(ev));
ev_sync.type = EV_SYN;
ev_sync.value = 0;
ev_sync.code = 0;
int res_ev_sync = write(fd, &ev_sync, sizeof(ev_sync));
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

@@ -1,19 +1,18 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-14
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
* @Date: 2025-05-07
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <fcntl.h>
#include <linux/uinput.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <unistd.h>
#include <vector>
#include "device_controller.h"
class MouseController : public DeviceController {
@@ -22,18 +21,19 @@ class MouseController : public DeviceController {
virtual ~MouseController();
public:
virtual int Init(int screen_width, int screen_height);
virtual int Init(std::vector<DisplayInfo> display_info_list);
virtual int Destroy();
virtual int SendCommand(RemoteAction remote_action);
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
private:
void SimulateKeyDown(int fd, int kval);
void SimulateKeyUp(int fd, int kval);
void SetMousePosition(int fd, int x, int y);
void SimulateKeyDown(int kval);
void SimulateKeyUp(int kval);
void SetMousePosition(int x, int y);
void SimulateMouseWheel(int direction_button, int count);
private:
int uinput_fd_;
struct uinput_user_dev uinput_dev_;
Display* display_ = nullptr;
Window root_ = 0;
std::vector<DisplayInfo> display_info_list_;
int screen_width_ = 0;
int screen_height_ = 0;
};

View File

@@ -2,47 +2,99 @@
#include <ApplicationServices/ApplicationServices.h>
#include "log.h"
#include "rd_log.h"
MouseController::MouseController() {}
MouseController::~MouseController() {}
int MouseController::Init(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
display_info_list_ = display_info_list;
return 0;
}
int MouseController::Destroy() { return 0; }
int MouseController::SendCommand(RemoteAction remote_action) {
int mouse_pos_x = remote_action.m.x * screen_width_ / 1280;
int mouse_pos_y = remote_action.m.y * screen_height_ / 720;
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;
CGEventRef mouse_event = nullptr;
CGEventType mouse_type;
CGMouseButton mouse_button;
CGPoint mouse_point = CGPointMake(mouse_pos_x, mouse_pos_y);
if (remote_action.m.flag == MouseFlag::left_down) {
mouse_type = kCGEventLeftMouseDown;
} else if (remote_action.m.flag == MouseFlag::left_up) {
mouse_type = kCGEventLeftMouseUp;
} else if (remote_action.m.flag == MouseFlag::right_down) {
mouse_type = kCGEventRightMouseDown;
} else if (remote_action.m.flag == MouseFlag::right_up) {
mouse_type = kCGEventRightMouseUp;
} else {
mouse_type = kCGEventMouseMoved;
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;
}
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type,
CGPointMake(mouse_pos_x, mouse_pos_y),
kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, mouse_event);
CFRelease(mouse_event);
if (mouse_event) {
CGEventPost(kCGHIDEventTap, mouse_event);
CFRelease(mouse_event);
}
}
return 0;

View File

@@ -7,6 +7,8 @@
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <vector>
#include "device_controller.h"
class MouseController : public DeviceController {
@@ -15,13 +17,14 @@ class MouseController : public DeviceController {
virtual ~MouseController();
public:
virtual int Init(int screen_width, int screen_height);
virtual int Init(std::vector<DisplayInfo> display_info_list);
virtual int Destroy();
virtual int SendCommand(RemoteAction remote_action);
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
private:
int screen_width_ = 0;
int screen_height_ = 0;
std::vector<DisplayInfo> display_info_list_;
bool left_dragging_ = false;
bool right_dragging_ = false;
};
#endif

View File

@@ -1,39 +1,64 @@
#include "mouse_controller.h"
#include "log.h"
#include "rd_log.h"
MouseController::MouseController() {}
MouseController::~MouseController() {}
int MouseController::Init(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
display_info_list_ = display_info_list;
return 0;
}
int MouseController::Destroy() { return 0; }
int MouseController::SendCommand(RemoteAction remote_action) {
int MouseController::SendMouseCommand(RemoteAction remote_action,
int display_index) {
INPUT ip;
if (remote_action.type == ControlType::mouse) {
ip.type = INPUT_MOUSE;
ip.mi.dx = remote_action.m.x * screen_width_ / 1280;
ip.mi.dy = remote_action.m.y * screen_height_ / 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.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.mouseData = 0;
ip.mi.time = 0;
SetCursorPos(ip.mi.dx, ip.mi.dy);

View File

@@ -7,6 +7,8 @@
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <vector>
#include "device_controller.h"
class MouseController : public DeviceController {
@@ -15,13 +17,12 @@ class MouseController : public DeviceController {
virtual ~MouseController();
public:
virtual int Init(int screen_width, int screen_height);
virtual int Init(std::vector<DisplayInfo> display_info_list);
virtual int Destroy();
virtual int SendCommand(RemoteAction remote_action);
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
private:
int screen_width_ = 0;
int screen_height_ = 0;
std::vector<DisplayInfo> display_info_list_;
};
#endif

View File

@@ -1,872 +1,17 @@
#include <SDL.h>
#include <stdio.h>
#ifdef _WIN32
#ifdef REMOTE_DESK_DEBUG
#ifdef DESK_PORT_DEBUG
#pragma comment(linker, "/subsystem:\"console\"")
#else
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#endif
#endif
#include <atomic>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include "rd_log.h"
#include "render.h"
#include "platform.h"
extern "C" {
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
};
#include <stdio.h>
#include "../../thirdparty/projectx/src/interface/x.h"
#include "device_controller_factory.h"
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_sdlrenderer2.h"
#include "log.h"
#include "screen_capturer_factory.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#ifdef REMOTE_DESK_DEBUG
#define MOUSE_CONTROL 0
#else
#define MOUSE_CONTROL 1
#endif
int screen_w = 1280, screen_h = 720;
int window_w = 1280, window_h = 720;
const int pixel_w = 1280, pixel_h = 720;
unsigned char dst_buffer[pixel_w * pixel_h * 3 / 2];
unsigned char audio_buffer[960];
SDL_Texture *sdlTexture = nullptr;
SDL_Renderer *sdlRenderer = nullptr;
SDL_Rect sdlRect;
SDL_Window *window;
static SDL_AudioDeviceID input_dev;
static SDL_AudioDeviceID output_dev;
uint32_t start_time, end_time, elapsed_time;
uint32_t frame_count = 0;
int fps = 0;
static std::atomic<bool> audio_buffer_fresh{false};
static uint32_t last_ts = 0;
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
int src_rate = 48000;
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_S16;
int src_nb_channels = 0;
uint8_t **src_data = NULL;
int src_linesize;
int src_nb_samples = 480;
int64_t dst_ch_layout = AV_CH_LAYOUT_MONO;
int dst_rate = 48000;
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
int dst_nb_channels = 0;
uint8_t **dst_data = NULL;
int dst_linesize;
int dst_nb_samples;
int max_dst_nb_samples;
int dst_bufsize;
struct SwrContext *swr_ctx;
int ret;
int audio_len = 0;
std::string window_title = "Remote Desk Client";
std::string server_connection_status_str = "-";
std::string client_connection_status_str = "-";
std::string server_signal_status_str = "-";
std::string client_signal_status_str = "-";
std::atomic<ConnectionStatus> server_connection_status{
ConnectionStatus::Closed};
std::atomic<ConnectionStatus> client_connection_status{
ConnectionStatus::Closed};
std::atomic<SignalStatus> server_signal_status{SignalStatus::SignalClosed};
std::atomic<SignalStatus> client_signal_status{SignalStatus::SignalClosed};
// Refresh Event
#define REFRESH_EVENT (SDL_USEREVENT + 1)
#define QUIT_EVENT (SDL_USEREVENT + 2)
typedef struct {
char password[7];
} CDCache;
int thread_exit = 0;
PeerPtr *peer_server = nullptr;
PeerPtr *peer_client = nullptr;
bool joined = false;
bool received_frame = false;
bool menu_hovered = false;
static bool connect_button_pressed = false;
static const char *connect_label = "Connect";
static char input_password[7] = "";
static FILE *cd_cache_file = nullptr;
static CDCache cd_cache;
static bool is_create_connection = false;
static bool done = false;
ScreenCapturerFactory *screen_capturer_factory = nullptr;
ScreenCapturer *screen_capturer = nullptr;
DeviceControllerFactory *device_controller_factory = nullptr;
MouseController *mouse_controller = nullptr;
char *nv12_buffer = nullptr;
#ifdef __linux__
std::chrono::_V2::system_clock::time_point last_frame_time_;
#else
std::chrono::steady_clock::time_point last_frame_time_;
#endif
inline int ProcessMouseKeyEven(SDL_Event &ev) {
float ratio = (float)(1280.0 / window_w);
RemoteAction remote_action;
remote_action.m.x = (size_t)(ev.button.x * ratio);
remote_action.m.y = (size_t)(ev.button.y * ratio);
if (SDL_KEYDOWN == ev.type) // SDL_KEYUP
{
// printf("SDLK_DOWN: %d\n", SDL_KeyCode(ev.key.keysym.sym));
if (SDLK_DOWN == ev.key.keysym.sym) {
// printf("SDLK_DOWN \n");
} else if (SDLK_UP == ev.key.keysym.sym) {
// printf("SDLK_UP \n");
} else if (SDLK_LEFT == ev.key.keysym.sym) {
// printf("SDLK_LEFT \n");
} else if (SDLK_RIGHT == ev.key.keysym.sym) {
// printf("SDLK_RIGHT \n");
}
} else if (SDL_MOUSEBUTTONDOWN == ev.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == ev.button.button) {
remote_action.m.flag = MouseFlag::left_down;
} else if (SDL_BUTTON_RIGHT == ev.button.button) {
remote_action.m.flag = MouseFlag::right_down;
}
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
} else if (SDL_MOUSEBUTTONUP == ev.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == ev.button.button) {
remote_action.m.flag = MouseFlag::left_up;
} else if (SDL_BUTTON_RIGHT == ev.button.button) {
remote_action.m.flag = MouseFlag::right_up;
}
SendData(peer_client, DATA_TYPE::DATA, (const char *)&remote_action,
sizeof(remote_action));
} else if (SDL_MOUSEMOTION == ev.type) {
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::move;
SendData(peer_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 SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
int64_t delay = swr_get_delay(swr_ctx, src_rate);
dst_nb_samples = (int)av_rescale_rnd(delay + src_nb_samples, dst_rate,
src_rate, AV_ROUND_UP);
if (dst_nb_samples > max_dst_nb_samples) {
av_freep(&dst_data[0]);
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, dst_sample_fmt, 1);
if (ret < 0) return;
max_dst_nb_samples = dst_nb_samples;
}
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
(const uint8_t **)&stream, src_nb_samples);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
return;
}
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret,
dst_sample_fmt, 1);
if (dst_bufsize < 0) {
fprintf(stderr, "Could not get sample buffer size\n");
return;
}
if (1) {
if ("ClientConnected" == client_connection_status_str) {
SendData(peer_client, DATA_TYPE::AUDIO, (const char *)dst_data[0],
dst_bufsize);
}
if ("ServerConnected" == server_connection_status_str) {
SendData(peer_server, DATA_TYPE::AUDIO, (const char *)dst_data[0],
dst_bufsize);
}
} else {
memcpy(audio_buffer, dst_data[0], dst_bufsize);
audio_len = dst_bufsize;
SDL_Delay(10);
audio_buffer_fresh = true;
}
}
void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len) {
// if ("ClientConnected" != client_connection_status_str) {
// return;
// }
if (!audio_buffer_fresh) {
return;
}
SDL_memset(stream, 0, len);
if (audio_len == 0) {
return;
} else {
}
len = (len > audio_len ? audio_len : len);
SDL_MixAudioFormat(stream, audio_buffer, AUDIO_S16LSB, len,
SDL_MIX_MAXVOLUME);
audio_buffer_fresh = false;
}
void ServerReceiveVideoBuffer(const char *data, size_t size,
const char *user_id, size_t user_id_size) {}
void ClientReceiveVideoBuffer(const char *data, size_t size,
const char *user_id, size_t user_id_size) {
// std::cout << "Receive: [" << user_id << "] " << std::endl;
if (joined) {
memcpy(dst_buffer, data, size);
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
received_frame = true;
}
}
void ServerReceiveAudioBuffer(const char *data, size_t size,
const char *user_id, size_t user_id_size) {
// memset(audio_buffer, 0, size);
// memcpy(audio_buffer, data, size);
// audio_len = size;
audio_buffer_fresh = true;
SDL_QueueAudio(output_dev, data, (Uint32)size);
// printf("queue audio\n");
}
void ClientReceiveAudioBuffer(const char *data, size_t size,
const char *user_id, size_t user_id_size) {
// std::cout << "Client receive audio, size " << size << ", user [" << user_id
// << "] " << std::endl;
SDL_QueueAudio(output_dev, data, (Uint32)size);
}
void ServerReceiveDataBuffer(const char *data, size_t size, const char *user_id,
size_t user_id_size) {
std::string user(user_id, user_id_size);
RemoteAction remote_action;
memcpy(&remote_action, data, sizeof(remote_action));
// std::cout << "remote_action: " << remote_action.type << " "
// << remote_action.m.flag << " " << remote_action.m.x << " "
// << remote_action.m.y << std::endl;
#if MOUSE_CONTROL
mouse_controller->SendCommand(remote_action);
#endif
}
void ClientReceiveDataBuffer(const char *data, size_t size, const char *user_id,
size_t user_id_size) {}
void ServerSignalStatus(SignalStatus status) {
server_signal_status = status;
if (SignalStatus::SignalConnecting == status) {
server_signal_status_str = "ServerSignalConnecting";
} else if (SignalStatus::SignalConnected == status) {
server_signal_status_str = "ServerSignalConnected";
} else if (SignalStatus::SignalFailed == status) {
server_signal_status_str = "ServerSignalFailed";
} else if (SignalStatus::SignalClosed == status) {
server_signal_status_str = "ServerSignalClosed";
} else if (SignalStatus::SignalReconnecting == status) {
server_signal_status_str = "ServerSignalReconnecting";
}
}
void ClientSignalStatus(SignalStatus status) {
client_signal_status = status;
if (SignalStatus::SignalConnecting == status) {
client_signal_status_str = "ClientSignalConnecting";
} else if (SignalStatus::SignalConnected == status) {
client_signal_status_str = "ClientSignalConnected";
} else if (SignalStatus::SignalFailed == status) {
client_signal_status_str = "ClientSignalFailed";
} else if (SignalStatus::SignalClosed == status) {
client_signal_status_str = "ClientSignalClosed";
} else if (SignalStatus::SignalReconnecting == status) {
client_signal_status_str = "ClientSignalReconnecting";
}
}
void ServerConnectionStatus(ConnectionStatus status) {
server_connection_status = status;
if (ConnectionStatus::Connecting == status) {
server_connection_status_str = "ServerConnecting";
} else if (ConnectionStatus::Connected == status) {
server_connection_status_str = "ServerConnected";
} else if (ConnectionStatus::Disconnected == status) {
server_connection_status_str = "ServerDisconnected";
} else if (ConnectionStatus::Failed == status) {
server_connection_status_str = "ServerFailed";
} else if (ConnectionStatus::Closed == status) {
server_connection_status_str = "ServerClosed";
} else if (ConnectionStatus::IncorrectPassword == status) {
server_connection_status_str = "Incorrect password";
if (connect_button_pressed) {
connect_button_pressed = false;
joined = false;
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
}
}
}
void ClientConnectionStatus(ConnectionStatus status) {
client_connection_status = status;
if (ConnectionStatus::Connecting == status) {
client_connection_status_str = "ClientConnecting";
} else if (ConnectionStatus::Connected == status) {
client_connection_status_str = "ClientConnected";
joined = true;
} else if (ConnectionStatus::Disconnected == status) {
client_connection_status_str = "ClientDisconnected";
} else if (ConnectionStatus::Failed == status) {
client_connection_status_str = "ClientFailed";
} else if (ConnectionStatus::Closed == status) {
client_connection_status_str = "ClientClosed";
} else if (ConnectionStatus::IncorrectPassword == status) {
client_connection_status_str = "Incorrect password";
if (connect_button_pressed) {
connect_button_pressed = false;
joined = false;
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
}
} else if (ConnectionStatus::NoSuchTransmissionId == status) {
client_connection_status_str = "No such transmission id";
if (connect_button_pressed) {
connect_button_pressed = false;
joined = false;
connect_label = connect_button_pressed ? "Disconnect" : "Connect";
}
}
}
int initResampler() {
/* create resampler context */
swr_ctx = swr_alloc();
if (!swr_ctx) {
fprintf(stderr, "Could not allocate resampler context\n");
ret = AVERROR(ENOMEM);
return -1;
}
/* set options */
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
/* initialize the resampling context */
if ((ret = swr_init(swr_ctx)) < 0) {
fprintf(stderr, "Failed to initialize the resampling context\n");
return -1;
}
/* allocate source and destination samples buffers */
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
src_nb_channels, src_nb_samples,
src_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate source samples\n");
return -1;
}
max_dst_nb_samples = dst_nb_samples =
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
/* buffer is going to be directly written to a rawaudio file, no alignment */
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
dst_nb_channels, dst_nb_samples,
dst_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate destination samples\n");
return -1;
}
}
int main() {
LOG_INFO("Remote desk");
last_ts = static_cast<uint32_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count());
initResampler();
cd_cache_file = fopen("cache.cd", "r+");
if (cd_cache_file) {
fseek(cd_cache_file, 0, SEEK_SET);
fread(&cd_cache.password, sizeof(cd_cache.password), 1, cd_cache_file);
fclose(cd_cache_file);
strncpy(input_password, cd_cache.password, sizeof(cd_cache.password));
}
// Setup SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER |
SDL_INIT_GAMECONTROLLER) != 0) {
printf("Error: %s\n", SDL_GetError());
return -1;
}
// From 2.0.18: Enable native IME.
#ifdef SDL_HINT_IME_SHOW_UI
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif
// Create window with SDL_Renderer graphics context
SDL_WindowFlags window_flags =
(SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
window = SDL_CreateWindow("Remote Desk", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, window_w, window_h,
window_flags);
SDL_DisplayMode DM;
SDL_GetCurrentDisplayMode(0, &DM);
screen_w = DM.w;
screen_h = DM.h;
sdlRenderer = SDL_CreateRenderer(
window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
if (sdlRenderer == nullptr) {
SDL_Log("Error creating SDL_Renderer!");
return 0;
}
Uint32 pixformat = 0;
pixformat = SDL_PIXELFORMAT_NV12;
sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat,
SDL_TEXTUREACCESS_STREAMING, pixel_w, pixel_h);
// Audio
SDL_AudioSpec want_in, have_in, want_out, have_out;
SDL_zero(want_in);
want_in.freq = 48000;
want_in.format = AUDIO_S16LSB;
want_in.channels = 1;
want_in.samples = 480;
want_in.callback = SdlCaptureAudioIn;
input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in, 0);
if (input_dev == 0) {
SDL_Log("Failed to open input: %s", SDL_GetError());
return 1;
}
SDL_zero(want_out);
want_out.freq = 48000;
want_out.format = AUDIO_S16LSB;
want_out.channels = 1;
// want_out.silence = 0;
want_out.samples = 480;
want_out.callback = NULL;
output_dev = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out, 0);
if (output_dev == 0) {
SDL_Log("Failed to open input: %s", SDL_GetError());
return 1;
}
SDL_PauseAudioDevice(input_dev, 0);
SDL_PauseAudioDevice(output_dev, 0);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForSDLRenderer(window, sdlRenderer);
ImGui_ImplSDLRenderer2_Init(sdlRenderer);
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
std::string mac_addr_str = GetMac();
std::thread rtc_thread(
[](int screen_width, int screen_height) {
std::string default_cfg_path = "../../../../config/config.ini";
std::ifstream f(default_cfg_path.c_str());
std::string mac_addr_str = GetMac();
Params server_params;
server_params.cfg_path =
f.good() ? "../../../../config/config.ini" : "config.ini";
server_params.on_receive_video_buffer = ServerReceiveVideoBuffer;
server_params.on_receive_audio_buffer = ServerReceiveAudioBuffer;
server_params.on_receive_data_buffer = ServerReceiveDataBuffer;
server_params.on_signal_status = ServerSignalStatus;
server_params.on_connection_status = ServerConnectionStatus;
Params client_params;
client_params.cfg_path =
f.good() ? "../../../../config/config.ini" : "config.ini";
client_params.on_receive_video_buffer = ClientReceiveVideoBuffer;
client_params.on_receive_audio_buffer = ClientReceiveAudioBuffer;
client_params.on_receive_data_buffer = ClientReceiveDataBuffer;
client_params.on_signal_status = ClientSignalStatus;
client_params.on_connection_status = ClientConnectionStatus;
std::string transmission_id = "000001";
peer_server = CreatePeer(&server_params);
LOG_INFO("Create peer_server");
std::string server_user_id = "S-" + mac_addr_str;
Init(peer_server, server_user_id.c_str());
LOG_INFO("peer_server init finish");
peer_client = CreatePeer(&client_params);
LOG_INFO("Create peer_client");
std::string client_user_id = "C-" + mac_addr_str;
Init(peer_client, client_user_id.c_str());
LOG_INFO("peer_client init finish");
{
while (SignalStatus::SignalConnected != server_signal_status &&
!done) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (done) {
return;
}
std::string user_id = "S-" + mac_addr_str;
is_create_connection =
CreateConnection(peer_server, mac_addr_str.c_str(),
input_password)
? false
: true;
nv12_buffer = new char[NV12_BUFFER_SIZE];
// Screen capture
screen_capturer_factory = new ScreenCapturerFactory();
screen_capturer = (ScreenCapturer *)screen_capturer_factory->Create();
last_frame_time_ = std::chrono::high_resolution_clock::now();
ScreenCapturer::RECORD_DESKTOP_RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = screen_w;
rect.bottom = screen_h;
screen_capturer->Init(
rect, 60,
[](unsigned char *data, int size, int width, int height) -> void {
auto now_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration =
now_time - last_frame_time_;
auto tc = duration.count() * 1000;
if (tc >= 0) {
SendData(peer_server, DATA_TYPE::VIDEO, (const char *)data,
NV12_BUFFER_SIZE);
last_frame_time_ = now_time;
}
});
screen_capturer->Start();
// Mouse control
device_controller_factory = new DeviceControllerFactory();
mouse_controller =
(MouseController *)device_controller_factory->Create(
DeviceControllerFactory::Device::Mouse);
mouse_controller->Init(screen_w, screen_h);
}
},
screen_w, screen_h);
// Main loop
while (!done) {
// Start the Dear ImGui frame
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
if (joined && !menu_hovered) {
ImGui::SetMouseCursor(ImGuiMouseCursor_None);
}
{
static float f = 0.0f;
static int counter = 0;
const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Once);
ImGui::SetNextWindowSize(ImVec2(190, 200));
ImGui::Begin("Menu", nullptr, ImGuiWindowFlags_NoResize);
{
menu_hovered = ImGui::IsWindowHovered();
ImGui::Text(" LOCAL ID:");
ImGui::SameLine();
ImGui::SetNextItemWidth(95);
ImGui::InputText(
"##local_id", (char *)mac_addr_str.c_str(),
mac_addr_str.length() + 1,
ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_ReadOnly);
ImGui::Text(" PASSWORD:");
ImGui::SameLine();
ImGui::SetNextItemWidth(95);
char input_password_tmp[7] = "";
strncpy(input_password_tmp, input_password, sizeof(input_password));
ImGui::InputTextWithHint("##server_pwd", "max 6 chars", input_password,
IM_ARRAYSIZE(input_password),
ImGuiInputTextFlags_CharsNoBlank);
if (strcmp(input_password_tmp, input_password)) {
cd_cache_file = fopen("cache.cd", "w+");
if (cd_cache_file) {
fseek(cd_cache_file, 0, SEEK_SET);
strncpy(cd_cache.password, input_password, sizeof(input_password));
fwrite(&cd_cache.password, sizeof(cd_cache.password), 1,
cd_cache_file);
fclose(cd_cache_file);
}
LeaveConnection(peer_server);
CreateConnection(peer_server, mac_addr_str.c_str(), input_password);
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
{
{
static char remote_id[20] = "";
ImGui::Text("REMOTE ID:");
ImGui::SameLine();
ImGui::SetNextItemWidth(95);
ImGui::InputTextWithHint("##remote_id", mac_addr_str.c_str(),
remote_id, IM_ARRAYSIZE(remote_id),
ImGuiInputTextFlags_CharsUppercase |
ImGuiInputTextFlags_CharsNoBlank);
ImGui::Spacing();
ImGui::Text(" PASSWORD:");
ImGui::SameLine();
ImGui::SetNextItemWidth(95);
static char client_password[20] = "";
ImGui::InputTextWithHint("##client_pwd", "max 6 chars",
client_password,
IM_ARRAYSIZE(client_password),
ImGuiInputTextFlags_CharsNoBlank);
if (ImGui::Button(connect_label)) {
int ret = -1;
if ("ClientSignalConnected" == client_signal_status_str) {
if (strcmp(connect_label, "Connect") == 0 && !joined) {
std::string user_id = "C-" + mac_addr_str;
ret = JoinConnection(peer_client, remote_id, client_password);
if (0 == ret) {
// joined = true;
}
} else if (strcmp(connect_label, "Disconnect") == 0 && joined) {
ret = LeaveConnection(peer_client);
memset(audio_buffer, 0, 960);
if (0 == ret) {
joined = false;
received_frame = false;
}
}
if (0 == ret) {
connect_button_pressed = !connect_button_pressed;
connect_label =
connect_button_pressed ? "Disconnect" : "Connect";
}
}
}
}
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
{
if (ImGui::Button("Resize Window")) {
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);
} 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 [" + server_signal_status_str + "|" +
client_signal_status_str + "|" +
server_connection_status_str + "|" +
client_connection_status_str + "]";
// For MacOS, UI frameworks can only be called from the main thread
SDL_SetWindowTitle(window, window_title.c_str());
start_time = end_time;
}
}
// Cleanup
if (is_create_connection) {
LeaveConnection(peer_server);
}
if (joined) {
LeaveConnection(peer_client);
}
rtc_thread.join();
SDL_CloseAudioDevice(output_dev);
SDL_CloseAudioDevice(input_dev);
mouse_controller->Destroy();
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(window);
SDL_CloseAudio();
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,143 +1,174 @@
#include "screen_capturer_x11.h"
#include <iostream>
#include <chrono>
#include <thread>
#include "log.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
#include "libyuv.h"
#include "rd_log.h"
ScreenCapturerX11::ScreenCapturerX11() {}
ScreenCapturerX11::~ScreenCapturerX11() {}
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
int ScreenCapturerX11::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
if (cb) {
_on_data = cb;
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;
av_log_set_level(AV_LOG_QUIET);
pFormatCtx_ = avformat_alloc_context();
avdevice_register_all();
// grabbing frame rate
av_dict_set(&options_, "framerate", "30", 0);
// Make the grabbed area follow the mouse
// av_dict_set(&options_, "follow_mouse", "centered", 0);
// Video frame size. The default is to capture the full screen
// av_dict_set(&options_, "video_size", "1280x720", 0);
std::string capture_method = "x11grab";
ifmt_ = (AVInputFormat *)av_find_input_format(capture_method.c_str());
if (!ifmt_) {
LOG_ERROR("Couldn't find_input_format [{}]", capture_method.c_str());
}
// Grab at position 10,20
if (avformat_open_input(&pFormatCtx_, ":0.0", ifmt_, &options_) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex_ = -1;
for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++)
if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex_ = i_;
break;
}
if (videoindex_ == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar;
pCodecCtx_ = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx_, pCodecParam_);
pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id));
if (pCodec_ == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width;
const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height;
pFrame_ = av_frame_alloc();
pFrameNv12_ = av_frame_alloc();
pFrame_->width = screen_w;
pFrame_->height = screen_h;
pFrameNv12_->width = 1280;
pFrameNv12_->height = 720;
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
img_convert_ctx_ = sws_getContext(
pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width,
pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
y_plane_.resize(width_ * height_);
uv_plane_.resize((width_ / 2) * (height_ / 2) * 2);
return 0;
}
int ScreenCapturerX11::Destroy() {
if (capture_thread_->joinable()) {
capture_thread_->join();
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() {
capture_thread_.reset(new std::thread([this]() {
while (1) {
if (av_read_frame(pFormatCtx_, packet_) >= 0) {
if (packet_->stream_index == videoindex_) {
avcodec_send_packet(pCodecCtx_, packet_);
av_packet_unref(packet_);
got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_);
if (!got_picture_) {
av_image_fill_arrays(pFrameNv12_->data, pFrameNv12_->linesize,
nv12_buffer_, AV_PIX_FMT_NV12,
pFrameNv12_->width, pFrameNv12_->height, 1);
sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0,
pFrame_->height, pFrameNv12_->data,
pFrameNv12_->linesize);
_on_data((unsigned char *)nv12_buffer_,
pFrameNv12_->width * pFrameNv12_->height * 3 / 2,
pFrameNv12_->width, pFrameNv12_->height);
}
}
}
if (running_) return 0;
running_ = true;
paused_ = false;
thread_ = std::thread([this]() {
while (running_) {
if (!paused_) OnFrame();
}
}));
});
return 0;
}
int ScreenCapturerX11::Pause() { return 0; }
int ScreenCapturerX11::Stop() {
if (!running_) return 0;
running_ = false;
if (thread_.joinable()) thread_.join();
return 0;
}
int ScreenCapturerX11::Resume() { return 0; }
int ScreenCapturerX11::Pause(int monitor_index) {
paused_ = true;
return 0;
}
int ScreenCapturerX11::Stop() { return 0; }
int ScreenCapturerX11::Resume(int monitor_index) {
paused_ = false;
return 0;
}
void ScreenCapturerX11::OnFrame() {}
int ScreenCapturerX11::SwitchTo(int monitor_index) {
monitor_index_ = monitor_index;
return 0;
}
void ScreenCapturerX11::CleanUp() {}
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
return display_info_list_;
}
void ScreenCapturerX11::OnFrame() {
if (!display_) {
LOG_ERROR("Display is not initialized");
return;
}
if (monitor_index_ < 0 || monitor_index_ >= display_info_list_.size()) {
LOG_ERROR("Invalid monitor index: {}", monitor_index_.load());
return;
}
left_ = display_info_list_[monitor_index_].left;
top_ = display_info_list_[monitor_index_].top;
width_ = display_info_list_[monitor_index_].width;
height_ = display_info_list_[monitor_index_].height;
XImage* image = XGetImage(display_, root_, left_, top_, width_, height_,
AllPlanes, ZPixmap);
if (!image) return;
bool needs_copy = image->bytes_per_line != width_ * 4;
std::vector<uint8_t> argb_buf;
uint8_t* src_argb = nullptr;
if (needs_copy) {
argb_buf.resize(width_ * height_ * 4);
for (int y = 0; y < height_; ++y) {
memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line,
width_ * 4);
}
src_argb = argb_buf.data();
} else {
src_argb = reinterpret_cast<uint8_t*>(image->data);
}
libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_,
uv_plane_.data(), width_, width_, height_);
std::vector<uint8_t> nv12;
nv12.reserve(y_plane_.size() + uv_plane_.size());
nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end());
nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end());
if (callback_) {
callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_,
display_info_list_[monitor_index_].name.c_str());
}
XDestroyImage(image);
}

View File

@@ -1,23 +1,24 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-05-07
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_X11_H_
#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 <string>
#include <iostream>
#include <thread>
#include <vector>
#include "screen_capturer.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
class ScreenCapturerX11 : public ScreenCapturer {
public:
@@ -25,56 +26,39 @@ class ScreenCapturerX11 : public ScreenCapturer {
~ScreenCapturerX11();
public:
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb);
int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
virtual int Destroy();
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
virtual int Start();
int SwitchTo(int monitor_index) override;
int Pause();
int Resume();
int Stop();
std::vector<DisplayInfo> GetDisplayInfoList() override;
void OnFrame();
protected:
void CleanUp();
private:
std::atomic_bool _running;
std::atomic_bool _paused;
std::atomic_bool _inited;
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::thread _thread;
std::string _device_name;
RECORD_DESKTOP_RECT _rect;
int _fps;
cb_desktop_data _on_data;
private:
int i_ = 0;
int videoindex_ = 0;
int got_picture_ = 0;
int fps_ = 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;
// 缓冲区
std::vector<uint8_t> y_plane_;
std::vector<uint8_t> uv_plane_;
};
#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,144 +0,0 @@
#include "screen_capturer_avf.h"
#include <iostream>
#include "log.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
ScreenCapturerAvf::ScreenCapturerAvf() {}
ScreenCapturerAvf::~ScreenCapturerAvf() {}
int ScreenCapturerAvf::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
if (cb) {
_on_data = cb;
}
av_log_set_level(AV_LOG_QUIET);
pFormatCtx_ = avformat_alloc_context();
avdevice_register_all();
// grabbing frame rate
av_dict_set(&options_, "framerate", "60", 0);
av_dict_set(&options_, "pixel_format", "nv12", 0);
// show remote cursor
av_dict_set(&options_, "capture_cursor", "1", 0);
// Make the grabbed area follow the mouse
// av_dict_set(&options_, "follow_mouse", "centered", 0);
// Video frame size. The default is to capture the full screen
// av_dict_set(&options_, "video_size", "1280x720", 0);
ifmt_ = (AVInputFormat *)av_find_input_format("avfoundation");
if (!ifmt_) {
printf("Couldn't find_input_format\n");
}
// Grab at position 10,20
if (avformat_open_input(&pFormatCtx_, "Capture screen 0", ifmt_, &options_) !=
0) {
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex_ = -1;
for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++)
if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex_ = i_;
break;
}
if (videoindex_ == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar;
pCodecCtx_ = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx_, pCodecParam_);
pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id));
if (pCodec_ == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width;
const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height;
pFrame_ = av_frame_alloc();
pFrameNv12_ = av_frame_alloc();
pFrame_->width = screen_w;
pFrame_->height = screen_h;
pFrameNv12_->width = 1280;
pFrameNv12_->height = 720;
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
img_convert_ctx_ = sws_getContext(
pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width,
pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
return 0;
}
int ScreenCapturerAvf::Destroy() {
if (capture_thread_->joinable()) {
capture_thread_->join();
}
return 0;
}
int ScreenCapturerAvf::Start() {
capture_thread_.reset(new std::thread([this]() {
while (1) {
if (av_read_frame(pFormatCtx_, packet_) >= 0) {
if (packet_->stream_index == videoindex_) {
avcodec_send_packet(pCodecCtx_, packet_);
av_packet_unref(packet_);
got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_);
if (!got_picture_) {
av_image_fill_arrays(pFrameNv12_->data, pFrameNv12_->linesize,
nv12_buffer_, AV_PIX_FMT_NV12,
pFrameNv12_->width, pFrameNv12_->height, 1);
sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0,
pFrame_->height, pFrameNv12_->data,
pFrameNv12_->linesize);
_on_data((unsigned char *)nv12_buffer_,
pFrameNv12_->width * pFrameNv12_->height * 3 / 2,
pFrameNv12_->width, pFrameNv12_->height);
}
}
}
}
}));
return 0;
}
int ScreenCapturerAvf::Pause() { return 0; }
int ScreenCapturerAvf::Resume() { return 0; }
int ScreenCapturerAvf::Stop() { return 0; }
void ScreenCapturerAvf::OnFrame() {}
void ScreenCapturerAvf::CleanUp() {}

View File

@@ -1,85 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-01
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_AVF_H_
#define _SCREEN_CAPTURER_AVF_H_
#include <atomic>
#include <functional>
#include <string>
#include <thread>
#include "screen_capturer.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
class ScreenCapturerAvf : public ScreenCapturer {
public:
ScreenCapturerAvf();
~ScreenCapturerAvf();
public:
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb);
virtual int Destroy();
virtual int Start();
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;
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

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

@@ -9,25 +9,26 @@
#include <functional>
#include "display_info.h"
class ScreenCapturer {
public:
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(unsigned char*, int, int, int, const char*)>
cb_desktop_data;
public:
virtual ~ScreenCapturer() {}
public:
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) = 0;
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

@@ -8,12 +8,12 @@
#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_avf.h"
#include "screen_capturer_sck.h"
#endif
class ScreenCapturerFactory {
@@ -27,7 +27,8 @@ class ScreenCapturerFactory {
#elif __linux__
return new ScreenCapturerX11();
#elif __APPLE__
return new ScreenCapturerAvf();
// return new ScreenCapturerAvf();
return new ScreenCapturerSck();
#else
return nullptr;
#endif

View File

@@ -5,64 +5,76 @@
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Graphics.Capture.h>
extern "C" {
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
};
#include <iostream>
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);
#include "libyuv.h"
#include "rd_log.h"
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);
static std::vector<DisplayInfo> gs_display_list;
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;
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, HDC hdc, LPRECT lprc,
LPARAM data) {
MONITORINFOEX info_ex;
info_ex.cbSize = sizeof(MONITORINFOEX);
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
[[maybe_unused]] LPRECT lprc, LPARAM data) {
MONITORINFOEX monitor_info_;
monitor_info_.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;
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() {}
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
ScreenCapturerWgc::~ScreenCapturerWgc() {}
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 {
@@ -76,105 +88,185 @@ bool ScreenCapturerWgc::IsWgcSupported() {
}
}
int ScreenCapturerWgc::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
int error = 0;
if (_inited == true) return error;
if (inited_ == true) return error;
nv12_frame_ = new unsigned char[rect.right * rect.bottom * 4];
// nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2];
// nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2];
_fps = fps;
fps_ = fps;
_on_data = cb;
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) {
if (!IsWgcSupported()) {
LOG_ERROR("WGC not supported");
error = 2;
return error;
}
return error;
}
monitor_ = GetPrimaryMonitor();
int ScreenCapturerWgc::Destroy() {
if (nv12_frame_) {
delete nv12_frame_;
nv12_frame_ = nullptr;
display_info_list_ = gs_display_list;
if (display_info_list_.empty()) {
LOG_ERROR("No display found");
return -1;
}
Stop();
CleanUp();
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) {
std::cout << "record desktop duplication is already running" << std::endl;
if (running_ == true) {
LOG_ERROR("Screen capturer already running");
return 0;
}
if (_inited == false) {
std::cout << "AE_NEED_INIT" << std::endl;
if (inited_ == false) {
LOG_ERROR("Screen capturer not inited");
return 4;
}
_running = true;
session_->Start();
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() {
_paused = true;
if (session_) session_->Pause();
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() {
_paused = false;
if (session_) session_->Resume();
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() {
_running = false;
if (session_) session_->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;
}
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
if (_on_data)
BGRAToNV12FFmpeg((unsigned char *)frame.data, frame.width, frame.height,
nv12_frame_);
_on_data(nv12_frame_, frame.width * frame.height * 4, frame.width,
frame.height);
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() {
_inited = false;
if (session_) session_->Release();
session_ = nullptr;
if (inited_) {
for (auto &session : sessions_) {
if (session.session_) {
session.session_->Stop();
}
}
sessions_.clear();
}
}

View File

@@ -5,6 +5,7 @@
#include <functional>
#include <string>
#include <thread>
#include <vector>
#include "screen_capturer.h"
#include "wgc_session.h"
@@ -19,39 +20,49 @@ class ScreenCapturerWgc : public ScreenCapturer,
public:
bool IsWgcSupported();
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb);
virtual int Destroy();
int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
virtual int Start();
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
int Pause();
int Resume();
int Stop();
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
void OnFrame(const WgcSession::wgc_session_frame &frame);
int SwitchTo(int monitor_index);
void OnFrame(const WgcSession::wgc_session_frame &frame, int id);
protected:
void CleanUp();
private:
WgcSession *session_ = nullptr;
HMONITOR monitor_;
MONITORINFOEX monitor_info_;
std::vector<DisplayInfo> display_info_list_;
int monitor_index_ = 0;
std::atomic_bool _running;
std::atomic_bool _paused;
std::atomic_bool _inited;
private:
class WgcSessionInfo {
public:
std::unique_ptr<WgcSession> session_;
bool inited_ = false;
bool running_ = false;
bool paused_ = false;
};
std::thread _thread;
std::vector<WgcSessionInfo> sessions_;
std::string _device_name;
std::atomic_bool running_;
std::atomic_bool inited_;
RECORD_DESKTOP_RECT _rect;
int fps_;
int _fps;
cb_desktop_data _on_data;
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,179 @@
#include "layout_style.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
int Render::ConnectionStatusWindow(
std::shared_ptr<SubStreamWindowProperties> &props) {
if (show_connection_status_window_) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
connection_status_window_width_) /
2,
(viewport->WorkSize.y - viewport->WorkPos.y -
connection_status_window_height_) /
2));
ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_,
connection_status_window_height_));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::Begin("ConnectionStatusWindow", nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoSavedSettings);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::SetWindowFontScale(0.5f);
std::string text;
if (ConnectionStatus::Connecting == props->connection_status_) {
text = localization::p2p_connecting[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
} else if (ConnectionStatus::Connected == props->connection_status_) {
text = localization::p2p_connected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Disconnected == props->connection_status_) {
text = localization::p2p_disconnected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Failed == props->connection_status_) {
text = localization::p2p_failed[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::Closed == props->connection_status_) {
text = localization::p2p_closed[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
show_connection_status_window_ = false;
}
} else if (ConnectionStatus::IncorrectPassword ==
props->connection_status_) {
if (!password_validating_) {
if (password_validating_time_ == 1) {
text = localization::input_password[localization_language_index_];
} else {
text = localization::reinput_password[localization_language_index_];
}
auto window_width = ImGui::GetWindowSize().x;
auto window_height = ImGui::GetWindowSize().y;
ImGui::SetCursorPosX((window_width - IPUT_WINDOW_WIDTH / 2) * 0.5f);
ImGui::SetCursorPosY(window_height * 0.4f);
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH / 2);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
if (focus_on_input_widget_) {
ImGui::SetKeyboardFocusHere();
focus_on_input_widget_ = false;
}
ImGui::InputText("##password", props->remote_password_,
IM_ARRAYSIZE(props->remote_password_),
ImGuiInputTextFlags_CharsNoBlank);
ImGui::SetWindowFontScale(0.4f);
ImVec2 text_size = ImGui::CalcTextSize(
localization::remember_password[localization_language_index_]
.c_str());
ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f);
ImGui::Checkbox(
localization::remember_password[localization_language_index_]
.c_str(),
&(props->remember_password_));
ImGui::SetWindowFontScale(0.5f);
ImGui::PopStyleVar();
ImGui::SetCursorPosX(window_width * 0.315f);
ImGui::SetCursorPosY(window_height * 0.75f);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = true;
password_validating_ = true;
props->rejoin_ = true;
need_to_rejoin_ = true;
focus_on_input_widget_ = true;
}
ImGui::SameLine();
// cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
memset(props->remote_password_, 0, sizeof(props->remote_password_));
show_connection_status_window_ = false;
focus_on_input_widget_ = true;
}
} else if (password_validating_) {
text = localization::validate_password[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
}
} else if (ConnectionStatus::NoSuchTransmissionId ==
props->connection_status_) {
text = localization::no_such_id[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
if (ImGui::Button(
localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = false;
re_enter_remote_id_ = true;
DestroyPeer(&props->peer_);
client_properties_.erase(props->remote_id_);
}
}
auto window_width = ImGui::GetWindowSize().x;
auto window_height = ImGui::GetWindowSize().y;
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
ImGui::SetCursorPosY(window_height * 0.2f);
ImGui::Text("%s", text.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar();
}
return 0;
}

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

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,180 @@
#include "layout_style.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
static int InputTextCallback(ImGuiInputTextCallbackData *data);
int Render::RemoteWindow() {
ImGui::SetNextWindowPos(ImVec2(local_window_width_ + 1.0f, title_bar_height_),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::BeginChild("RemoteDesktopWindow",
ImVec2(remote_window_width_, remote_window_height_),
ImGuiChildFlags_None,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleColor();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_);
ImGui::Indent(main_child_window_x_padding_ - 1.0f);
ImGui::TextColored(
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
localization::remote_desktop[localization_language_index_].c_str());
ImGui::Spacing();
{
ImGui::SetNextWindowPos(
ImVec2(local_window_width_ + main_child_window_x_padding_ - 1.0f,
title_bar_height_ + main_child_window_y_padding_),
ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255,
242.0f / 255, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
ImGui::BeginChild(
"RemoteDesktopWindow_1",
ImVec2(remote_child_window_width_, remote_child_window_height_),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
{
ImGui::SetWindowFontScale(0.8f);
ImGui::Text(
"%s", localization::remote_id[localization_language_index_].c_str());
ImGui::Spacing();
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH);
ImGui::SetWindowFontScale(1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
if (re_enter_remote_id_) {
ImGui::SetKeyboardFocusHere();
re_enter_remote_id_ = false;
memset(remote_id_display_, 0, sizeof(remote_id_display_));
}
bool enter_pressed = ImGui::InputText(
"##remote_id_", remote_id_display_, IM_ARRAYSIZE(remote_id_display_),
ImGuiInputTextFlags_CharsDecimal |
ImGuiInputTextFlags_EnterReturnsTrue |
ImGuiInputTextFlags_CallbackEdit,
InputTextCallback);
ImGui::PopStyleVar();
ImGui::SameLine();
std::string remote_id = remote_id_display_;
remote_id.erase(remove_if(remote_id.begin(), remote_id.end(),
static_cast<int (*)(int)>(&isspace)),
remote_id.end());
if (ImGui::Button(ICON_FA_ARROW_RIGHT_LONG, ImVec2(55, 38)) ||
enter_pressed) {
connect_button_pressed_ = true;
bool found = false;
for (auto &[id, props] : recent_connections_) {
if (id.find(remote_id) != std::string::npos) {
found = true;
if (client_properties_.find(remote_id) !=
client_properties_.end()) {
if (!client_properties_[remote_id]->connection_established_) {
ConnectTo(props.remote_id, props.password.c_str(), false);
} else {
// todo: show warning message
LOG_INFO("Already connected to [{}]", remote_id);
}
} else {
ConnectTo(props.remote_id, props.password.c_str(), false);
}
}
}
if (!found) {
ConnectTo(remote_id, "", false);
}
}
if (need_to_rejoin_) {
need_to_rejoin_ = false;
for (const auto &[_, props] : client_properties_) {
if (props->rejoin_) {
ConnectTo(props->remote_id_, props->remote_password_,
props->remember_password_);
}
}
}
}
ImGui::EndChild();
}
ImGui::EndChild();
ImGui::PopStyleVar();
return 0;
}
static int InputTextCallback(ImGuiInputTextCallbackData *data) {
if (data->BufTextLen > 3 && data->Buf[3] != ' ') {
data->InsertChars(3, " ");
}
if (data->BufTextLen > 7 && data->Buf[7] != ' ') {
data->InsertChars(7, " ");
}
return 0;
}
int Render::ConnectTo(const std::string &remote_id, const char *password,
bool remember_password) {
LOG_INFO("Connect to [{}]", remote_id);
if (client_properties_.find(remote_id) == client_properties_.end()) {
client_properties_[remote_id] =
std::make_shared<SubStreamWindowProperties>();
auto props = client_properties_[remote_id];
props->local_id_ = "C-" + std::string(client_id_);
props->remote_id_ = remote_id;
memcpy(&props->params_, &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;
}

1379
src/single_window/render.cpp Normal file

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,438 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-05-29
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _MAIN_WINDOW_H_
#define _MAIN_WINDOW_H_
#include <SDL.h>
#include <atomic>
#include <chrono>
#include <fstream>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include "IconsFontAwesome6.h"
#include "config_center.h"
#include "device_controller_factory.h"
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_sdlrenderer2.h"
#include "imgui_internal.h"
#include "minirtc.h"
#include "path_manager.h"
#include "screen_capturer_factory.h"
#include "speaker_capturer_factory.h"
#include "thumbnail.h"
class Render {
public:
struct SubStreamWindowProperties {
Params params_;
PeerPtr *peer_ = nullptr;
std::string audio_label_ = "control_audio";
std::string data_label_ = "control_data";
std::string local_id_ = "";
std::string remote_id_ = "";
bool exit_ = false;
bool signal_connected_ = false;
SignalStatus signal_status_ = SignalStatus::SignalClosed;
bool connection_established_ = false;
bool rejoin_ = false;
bool net_traffic_stats_button_pressed_ = false;
bool mouse_control_button_pressed_ = false;
bool mouse_controller_is_started_ = false;
bool audio_capture_button_pressed_ = false;
bool control_mouse_ = false;
bool streaming_ = false;
bool is_control_bar_in_left_ = true;
bool control_bar_hovered_ = false;
bool display_selectable_hovered_ = false;
bool control_bar_expand_ = true;
bool reset_control_bar_pos_ = false;
bool control_window_width_is_changing_ = false;
bool control_window_height_is_changing_ = false;
bool p2p_mode_ = true;
bool remember_password_ = false;
char remote_password_[7] = "";
float sub_stream_window_width_ = 1280;
float sub_stream_window_height_ = 720;
float control_window_min_width_ = 20;
float control_window_max_width_ = 230;
float control_window_min_height_ = 40;
float control_window_max_height_ = 150;
float control_window_width_ = 230;
float control_window_height_ = 40;
float control_bar_pos_x_ = 0;
float control_bar_pos_y_ = 30;
float mouse_diff_control_bar_pos_x_ = 0;
float mouse_diff_control_bar_pos_y_ = 0;
double control_bar_button_pressed_time_ = 0;
double net_traffic_stats_button_pressed_time_ = 0;
unsigned char *dst_buffer_ = nullptr;
size_t dst_buffer_capacity_ = 0;
int mouse_pos_x_ = 0;
int mouse_pos_y_ = 0;
int mouse_pos_x_last_ = 0;
int mouse_pos_y_last_ = 0;
int texture_width_ = 1280;
int texture_height_ = 720;
int video_width_ = 0;
int video_height_ = 0;
int video_width_last_ = 0;
int video_height_last_ = 0;
int selected_display_ = 0;
size_t video_size_ = 0;
bool tab_selected_ = false;
bool tab_opened_ = true;
std::optional<float> pos_x_before_docked_;
std::optional<float> pos_y_before_docked_;
float render_window_x_ = 0;
float render_window_y_ = 0;
float render_window_width_ = 0;
float render_window_height_ = 0;
std::string fullscreen_button_label_ = "Fullscreen";
std::string net_traffic_stats_button_label_ = "Show Net Traffic Stats";
std::string mouse_control_button_label_ = "Mouse Control";
std::string audio_capture_button_label_ = "Audio Capture";
std::string remote_host_name_ = "";
std::vector<DisplayInfo> display_info_list_;
SDL_Texture *stream_texture_ = nullptr;
SDL_Rect stream_render_rect_;
SDL_Rect stream_render_rect_last_;
ImVec2 control_window_pos_;
ConnectionStatus connection_status_ = ConnectionStatus::Closed;
TraversalMode traversal_mode_ = TraversalMode::UnknownMode;
XNetTrafficStats net_traffic_stats_;
};
public:
Render();
~Render();
public:
int Run();
private:
void InitializeLogger();
void InitializeSettings();
void InitializeSDL();
void InitializeModules();
void InitializeMainWindow();
void MainLoop();
void UpdateLabels();
void UpdateInteractions();
void HandleRecentConnections();
void HandleStreamWindow();
void Cleanup();
void CleanupFactories();
void CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props);
void CleanupPeers();
void CleanSubStreamWindowProperties(
std::shared_ptr<SubStreamWindowProperties> props);
void UpdateRenderRect();
void ProcessSdlEvent();
private:
int CreateStreamRenderWindow();
int TitleBar(bool main_window);
int MainWindow();
int StreamWindow();
int LocalWindow();
int RemoteWindow();
int RecentConnectionsWindow();
int SettingWindow();
int ControlWindow(std::shared_ptr<SubStreamWindowProperties> &props);
int ControlBar(std::shared_ptr<SubStreamWindowProperties> &props);
int AboutWindow();
int StatusBar();
int ConnectionStatusWindow(std::shared_ptr<SubStreamWindowProperties> &props);
int ShowRecentConnections();
private:
int ConnectTo(const std::string &remote_id, const char *password,
bool remember_password);
int CreateMainWindow();
int DestroyMainWindow();
int CreateStreamWindow();
int DestroyStreamWindow();
int SetupFontAndStyle();
int SetupMainWindow();
int DestroyMainWindowContext();
int SetupStreamWindow();
int DestroyStreamWindowContext();
int DrawMainWindow();
int DrawStreamWindow();
int ConfirmDeleteConnection();
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties> &props);
void DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties> &props);
public:
static void OnReceiveVideoBufferCb(const XVideoFrame *video_frame,
const char *user_id, size_t user_id_size,
void *user_data);
static void OnReceiveAudioBufferCb(const char *data, size_t size,
const char *user_id, size_t user_id_size,
void *user_data);
static void OnReceiveDataBufferCb(const char *data, size_t size,
const char *user_id, size_t user_id_size,
void *user_data);
static void OnSignalStatusCb(SignalStatus status, const char *user_id,
size_t user_id_size, void *user_data);
static void OnConnectionStatusCb(ConnectionStatus status, const char *user_id,
size_t user_id_size, void *user_data);
static void NetStatusReport(const char *client_id, size_t client_id_size,
TraversalMode mode,
const XNetTrafficStats *net_traffic_stats,
const char *user_id, const size_t user_id_size,
void *user_data);
static SDL_HitTestResult HitTestCallback(SDL_Window *window,
const SDL_Point *area, void *data);
static std::vector<char> SerializeRemoteAction(const RemoteAction &action);
static bool DeserializeRemoteAction(const char *data, size_t size,
RemoteAction &out);
static void FreeRemoteAction(RemoteAction &action);
private:
int SendKeyCommand(int key_code, bool is_down);
int ProcessMouseEvent(SDL_Event &event);
static void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len);
static void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len);
private:
int SaveSettingsIntoCacheFile();
int LoadSettingsFromCacheFile();
int ScreenCapturerInit();
int StartScreenCapturer();
int StopScreenCapturer();
int StartSpeakerCapturer();
int StopSpeakerCapturer();
int StartMouseController();
int StopMouseController();
int StartKeyboardCapturer();
int StopKeyboardCapturer();
int CreateConnectionPeer();
int AudioDeviceInit();
int AudioDeviceDestroy();
private:
struct CDCache {
char client_id_with_password[17];
int language;
int video_quality;
int video_encode_format;
bool enable_hardware_video_codec;
bool enable_turn;
unsigned char key[16];
unsigned char iv[16];
};
private:
CDCache cd_cache_;
std::mutex cd_cache_mutex_;
ConfigCenter config_center_;
ConfigCenter::LANGUAGE localization_language_ =
ConfigCenter::LANGUAGE::CHINESE;
std::unique_ptr<PathManager> path_manager_;
std::string cert_path_;
std::string exec_log_path_;
std::string dll_log_path_;
std::string cache_path_;
std::string imgui_cache_path_;
int localization_language_index_ = -1;
int localization_language_index_last_ = -1;
bool modules_inited_ = false;
/* ------ all windows property start ------ */
float title_bar_width_ = 640;
float title_bar_height_ = 30;
/* ------ all windows property end ------ */
/* ------ main window property start ------ */
// thumbnail
unsigned char aes128_key_[16];
unsigned char aes128_iv_[16];
std::unique_ptr<Thumbnail> thumbnail_;
// recent connections
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>
recent_connections_;
int recent_connection_image_width_ = 160;
int recent_connection_image_height_ = 90;
uint32_t recent_connection_image_save_time_ = 0;
// main window render
SDL_Window *main_window_ = nullptr;
SDL_Renderer *main_renderer_ = nullptr;
ImGuiContext *main_ctx_ = nullptr;
bool exit_ = false;
// main window properties
bool start_mouse_controller_ = false;
bool mouse_controller_is_started_ = false;
bool start_screen_capturer_ = false;
bool screen_capturer_is_started_ = false;
bool start_keyboard_capturer_ = false;
bool keyboard_capturer_is_started_ = false;
bool foucs_on_main_window_ = false;
bool foucs_on_stream_window_ = false;
bool audio_capture_ = false;
int main_window_width_real_ = 720;
int main_window_height_real_ = 540;
float main_window_dpi_scaling_w_ = 1.0f;
float main_window_dpi_scaling_h_ = 1.0f;
float main_window_width_default_ = 640;
float main_window_height_default_ = 480;
float main_window_width_ = 640;
float main_window_height_ = 480;
float main_window_width_last_ = 640;
float main_window_height_last_ = 480;
float local_window_width_ = 320;
float local_window_height_ = 235;
float remote_window_width_ = 320;
float remote_window_height_ = 235;
float local_child_window_width_ = 266;
float local_child_window_height_ = 180;
float remote_child_window_width_ = 266;
float remote_child_window_height_ = 180;
float main_window_text_y_padding_ = 10;
float main_child_window_x_padding_ = 27;
float main_child_window_y_padding_ = 45;
float status_bar_height_ = 22;
float connection_status_window_width_ = 200;
float connection_status_window_height_ = 150;
float notification_window_width_ = 200;
float notification_window_height_ = 80;
float about_window_width_ = 200;
float about_window_height_ = 150;
int screen_width_ = 1280;
int screen_height_ = 720;
int selected_display_ = 0;
std::string connect_button_label_ = "Connect";
char input_password_tmp_[7] = "";
char input_password_[7] = "";
std::string random_password_ = "";
char new_password_[7] = "";
char remote_id_display_[12] = "";
unsigned char audio_buffer_[720];
int audio_len_ = 0;
bool audio_buffer_fresh_ = false;
bool need_to_rejoin_ = false;
bool just_created_ = false;
std::string controlled_remote_id_ = "";
bool need_to_send_host_info_ = false;
SDL_Event last_mouse_event;
// stream window render
SDL_Window *stream_window_ = nullptr;
SDL_Renderer *stream_renderer_ = nullptr;
ImGuiContext *stream_ctx_ = nullptr;
// stream window properties
bool need_to_create_stream_window_ = false;
bool stream_window_created_ = false;
bool stream_window_inited_ = false;
bool window_maximized_ = false;
bool stream_window_grabbed_ = false;
bool control_mouse_ = false;
int stream_window_width_default_ = 1280;
int stream_window_height_default_ = 720;
float stream_window_width_ = 1280;
float stream_window_height_ = 720;
uint32_t stream_pixformat_ = 0;
int stream_window_width_real_ = 1280;
int stream_window_height_real_ = 720;
float stream_window_dpi_scaling_w_ = 1.0f;
float stream_window_dpi_scaling_h_ = 1.0f;
bool label_inited_ = false;
bool connect_button_pressed_ = false;
bool password_validating_ = false;
uint32_t password_validating_time_ = 0;
bool show_settings_window_ = false;
bool rejoin_ = false;
bool local_id_copied_ = false;
bool show_password_ = true;
bool regenerate_password_ = false;
bool show_about_window_ = false;
bool show_connection_status_window_ = false;
bool show_reset_password_window_ = false;
bool fullscreen_button_pressed_ = false;
bool focus_on_input_widget_ = true;
bool is_client_mode_ = false;
bool reload_recent_connections_ = true;
bool show_confirm_delete_connection_ = false;
bool delete_connection_ = false;
bool is_tab_bar_hovered_ = false;
std::string delete_connection_name_ = "";
bool re_enter_remote_id_ = false;
double copy_start_time_ = 0;
double regenerate_password_start_time_ = 0;
SignalStatus signal_status_ = SignalStatus::SignalClosed;
std::string signal_status_str_ = "";
bool signal_connected_ = false;
PeerPtr *peer_ = nullptr;
PeerPtr *peer_reserved_ = nullptr;
std::string video_primary_label_ = "primary_display";
std::string video_secondary_label_ = "secondary_display";
std::string audio_label_ = "audio";
std::string data_label_ = "data";
Params params_;
SDL_AudioDeviceID input_dev_;
SDL_AudioDeviceID output_dev_;
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
ScreenCapturer *screen_capturer_ = nullptr;
SpeakerCapturerFactory *speaker_capturer_factory_ = nullptr;
SpeakerCapturer *speaker_capturer_ = nullptr;
DeviceControllerFactory *device_controller_factory_ = nullptr;
MouseController *mouse_controller_ = nullptr;
KeyboardCapturer *keyboard_capturer_ = nullptr;
std::vector<DisplayInfo> display_info_list_;
uint64_t last_frame_time_;
char client_id_[10] = "";
char client_id_display_[12] = "";
char client_id_with_password_[17] = "";
char password_saved_[7] = "";
int language_button_value_ = 0;
int video_quality_button_value_ = 0;
int video_encode_format_button_value_ = 0;
bool enable_hardware_video_codec_ = false;
bool enable_turn_ = false;
int language_button_value_last_ = 0;
int video_quality_button_value_last_ = 0;
int video_encode_format_button_value_last_ = 0;
bool enable_hardware_video_codec_last_ = false;
bool enable_turn_last_ = false;
bool settings_window_pos_reset_ = true;
/* ------ main window property end ------ */
/* ------ sub stream window property start ------ */
std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>>
client_properties_;
void CloseTab(decltype(client_properties_)::iterator &it);
/* ------ stream window property end ------ */
};
#endif

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,197 @@
#include "localization.h"
#include "rd_log.h"
#include "render.h"
void Render::DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties>& props) {
std::string text;
switch (props->connection_status_) {
case ConnectionStatus::Disconnected:
text = localization::p2p_disconnected[localization_language_index_];
break;
case ConnectionStatus::Failed:
text = localization::p2p_failed[localization_language_index_];
break;
case ConnectionStatus::Closed:
text = localization::p2p_closed[localization_language_index_];
break;
default:
break;
}
if (!text.empty()) {
ImVec2 size = ImGui::GetWindowSize();
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
ImGui::SetCursorPos(
ImVec2((size.x - text_size.x) * 0.5f,
(size.y - text_size.y - title_bar_height_) * 0.5f));
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str());
}
}
void Render::CloseTab(decltype(client_properties_)::iterator& it) {
CleanupPeer(it->second);
it = client_properties_.erase(it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
}
}
int Render::StreamWindow() {
ImGui::SetNextWindowPos(
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::Begin("VideoBg", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor(2);
ImGui::PopStyleVar();
ImGuiWindowFlags stream_window_flag =
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove;
if (!fullscreen_button_pressed_) {
ImGui::SetNextWindowPos(ImVec2(20, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(0, 20), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 8.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
ImGui::Begin("TabBar", nullptr,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
if (ImGui::BeginTabBar("StreamTabBar",
ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_AutoSelectNewTabs)) {
is_tab_bar_hovered_ = ImGui::IsWindowHovered();
for (auto it = client_properties_.begin();
it != client_properties_.end();) {
auto& props = it->second;
if (!props->tab_opened_) {
CloseTab(it);
continue;
}
ImGui::SetWindowFontScale(0.6f);
if (ImGui::BeginTabItem(props->remote_id_.c_str(),
&props->tab_opened_)) {
props->tab_selected_ = true;
ImGui::SetWindowFontScale(1.0f);
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_, stream_window_height_),
ImGuiCond_Always);
ImGui::SetNextWindowPos(
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImVec2 pos = ImGui::GetWindowPos();
ImVec2 size = ImGui::GetWindowSize();
props->render_window_x_ = pos.x;
props->render_window_y_ = pos.y;
props->render_window_width_ = size.x;
props->render_window_height_ = size.y;
UpdateRenderRect();
ControlWindow(props);
if (!props->peer_) {
it = client_properties_.erase(it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
}
} else {
DrawConnectionStatusText(props);
++it;
}
ImGui::End();
ImGui::EndTabItem();
} else {
props->tab_selected_ = false;
ImGui::SetWindowFontScale(1.0f);
++it;
}
}
ImGui::EndTabBar();
}
ImGui::End(); // End TabBar
} else {
for (auto it = client_properties_.begin();
it != client_properties_.end();) {
auto& props = it->second;
if (!props->tab_opened_) {
CloseTab(it);
continue;
}
if (props->tab_selected_) {
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_, stream_window_height_),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.0f));
ImGui::Begin(props->remote_id_.c_str(), nullptr, stream_window_flag);
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImVec2 pos = ImGui::GetWindowPos();
ImVec2 size = ImGui::GetWindowSize();
props->render_window_x_ = pos.x;
props->render_window_y_ = pos.y;
props->render_window_width_ = size.x;
props->render_window_height_ = size.y;
UpdateRenderRect();
ControlWindow(props);
ImGui::End();
if (!props->peer_) {
fullscreen_button_pressed_ = false;
SDL_SetWindowFullscreen(stream_window_, SDL_FALSE);
it = client_properties_.erase(it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
}
} else {
DrawConnectionStatusText(props);
++it;
}
} else {
++it;
}
}
}
// UpdateRenderRect();
ImGui::End(); // End VideoBg
return 0;
}

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,232 +0,0 @@
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
};
static int get_format_from_sample_fmt(const char **fmt,
enum AVSampleFormat sample_fmt) {
int i;
struct sample_fmt_entry {
enum AVSampleFormat sample_fmt;
const char *fmt_be, *fmt_le;
} sample_fmt_entries[] = {
{AV_SAMPLE_FMT_U8, "u8", "u8"},
{AV_SAMPLE_FMT_S16, "s16be", "s16le"},
{AV_SAMPLE_FMT_S32, "s32be", "s32le"},
{AV_SAMPLE_FMT_FLT, "f32be", "f32le"},
{AV_SAMPLE_FMT_DBL, "f64be", "f64le"},
};
*fmt = NULL;
for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
struct sample_fmt_entry *entry = &sample_fmt_entries[i];
if (sample_fmt == entry->sample_fmt) {
*fmt = AV_NE(entry->fmt_be, entry->fmt_le);
return 0;
}
}
fprintf(stderr, "Sample format %s not supported as output format\n",
av_get_sample_fmt_name(sample_fmt));
return AVERROR(EINVAL);
}
/**
* Fill dst buffer with nb_samples, generated starting from t. <20><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>
*/
static void fill_samples(double *dst, int nb_samples, int nb_channels,
int sample_rate, double *t) {
int i, j;
double tincr = 1.0 / sample_rate, *dstp = dst;
const double c = 2 * M_PI * 440.0;
/* generate sin tone with 440Hz frequency and duplicated channels */
for (i = 0; i < nb_samples; i++) {
*dstp = sin(c * *t);
for (j = 1; j < nb_channels; j++) dstp[j] = dstp[0];
dstp += nb_channels;
*t += tincr;
}
}
int main(int argc, char **argv) {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
int src_rate = 44100;
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_DBL;
int src_nb_channels = 0;
uint8_t **src_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
int src_linesize;
int src_nb_samples = 1024;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO;
int dst_rate = 48000;
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
int dst_nb_channels = 0;
uint8_t **dst_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
int dst_linesize;
int dst_nb_samples;
int max_dst_nb_samples;
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
const char *dst_filename = NULL; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD>󲥷<EFBFBD><F3B2A5B7><EFBFBD>֤
FILE *dst_file;
int dst_bufsize;
const char *fmt;
// <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5>
struct SwrContext *swr_ctx;
double t;
int ret;
dst_filename = "res.pcm";
dst_file = fopen(dst_filename, "wb");
if (!dst_file) {
fprintf(stderr, "Could not open destination file %s\n", dst_filename);
exit(1);
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD>
/* create resampler context */
swr_ctx = swr_alloc();
if (!swr_ctx) {
fprintf(stderr, "Could not allocate resampler context\n");
ret = AVERROR(ENOMEM);
goto end;
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/* set options */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
// <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
/* initialize the resampling context */
if ((ret = swr_init(swr_ctx)) < 0) {
fprintf(stderr, "Failed to initialize the resampling context\n");
goto end;
}
/* allocate source and destination samples buffers */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD>
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
src_nb_channels, src_nb_samples,
src_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate source samples\n");
goto end;
}
/* compute the number of converted samples: buffering is avoided
* ensuring that the output buffer will contain at least all the
* converted input samples */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
max_dst_nb_samples = dst_nb_samples =
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
/* buffer is going to be directly written to a rawaudio file, no alignment
*/
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
dst_nb_channels, dst_nb_samples,
dst_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate destination samples\n");
goto end;
}
t = 0;
do {
/* generate synthetic audio */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
fill_samples((double *)src_data[0], src_nb_samples, src_nb_channels,
src_rate, &t);
/* compute destination number of samples */
int64_t delay = swr_get_delay(swr_ctx, src_rate);
dst_nb_samples =
av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
if (dst_nb_samples > max_dst_nb_samples) {
av_freep(&dst_data[0]);
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, dst_sample_fmt, 1);
if (ret < 0) break;
max_dst_nb_samples = dst_nb_samples;
}
// int fifo_size = swr_get_out_samples(swr_ctx,src_nb_samples);
// printf("fifo_size:%d\n", fifo_size);
// if(fifo_size < 1024)
// continue;
/* convert to destination format */
// ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const
// uint8_t **)src_data, src_nb_samples);
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
(const uint8_t **)src_data, src_nb_samples);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
goto end;
}
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
ret, dst_sample_fmt, 1);
if (dst_bufsize < 0) {
fprintf(stderr, "Could not get sample buffer size\n");
goto end;
}
printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret);
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
} while (t < 10);
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, NULL, 0);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
goto end;
}
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret,
dst_sample_fmt, 1);
if (dst_bufsize < 0) {
fprintf(stderr, "Could not get sample buffer size\n");
goto end;
}
printf("flush in:%d out:%d\n", 0, ret);
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
if ((ret = get_format_from_sample_fmt(&fmt, dst_sample_fmt)) < 0) goto end;
fprintf(stderr,
"Resampling succeeded. Play the output file with the command:\n"
"ffplay -f %s -channel_layout %" PRId64 " -channels %d -ar %d %s\n",
fmt, dst_ch_layout, dst_nb_channels, dst_rate, dst_filename);
end:
fclose(dst_file);
if (src_data) av_freep(&src_data[0]);
av_freep(&src_data);
if (dst_data) av_freep(&dst_data[0]);
av_freep(&dst_data);
swr_free(&swr_ctx);
return ret < 0;
}

View File

@@ -1,53 +0,0 @@
#include <SDL2/SDL.h>
int main(int argc, char *argv[]) {
int ret;
SDL_AudioSpec wanted_spec, obtained_spec;
// Initialize SDL
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL: %s",
SDL_GetError());
return -1;
}
// Set audio format
wanted_spec.freq = 44100; // Sample rate
wanted_spec.format =
AUDIO_F32SYS; // Sample format (32-bit float, system byte order)
wanted_spec.channels = 2; // Number of channels (stereo)
wanted_spec.samples = 1024; // Buffer size (in samples)
wanted_spec.callback = NULL; // Audio callback function (not used here)
// Open audio device
ret = SDL_OpenAudio(&wanted_spec, &obtained_spec);
if (ret < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to open audio device: %s", SDL_GetError());
return -1;
}
// Start playing audio
SDL_PauseAudio(0);
// Write PCM data to audio buffer
float *pcm_data = ...; // PCM data buffer (float, interleaved)
int pcm_data_size = ...; // Size of PCM data buffer (in bytes)
int bytes_written = SDL_QueueAudio(0, pcm_data, pcm_data_size);
// Wait until audio buffer is empty
while (SDL_GetQueuedAudioSize(0) > 0) {
SDL_Delay(100);
}
// Stop playing audio
SDL_PauseAudio(1);
// Close audio device
SDL_CloseAudio();
// Quit SDL
SDL_Quit();
return 0;
}

View File

@@ -1,89 +0,0 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_AUDIO)) {
printf("SDL init error\n");
return -1;
}
// SDL_AudioSpec
SDL_AudioSpec wanted_spec;
SDL_zero(wanted_spec);
wanted_spec.freq = 48000;
wanted_spec.format = AUDIO_S16LSB;
wanted_spec.channels = 2;
wanted_spec.silence = 0;
wanted_spec.samples = 960;
wanted_spec.callback = NULL;
SDL_AudioDeviceID deviceID = 0;
// <20><><EFBFBD><EFBFBD><EFBFBD>
if ((deviceID = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, NULL,
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE)) < 2) {
printf("could not open audio device: %s\n", SDL_GetError());
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD>ϵͳ
SDL_Quit();
return 0;
}
SDL_PauseAudioDevice(deviceID, 0);
FILE* fp = nullptr;
fopen_s(&fp, "ls.pcm", "rb+");
if (fp == NULL) {
printf("cannot open this file\n");
return -1;
}
if (fp == NULL) {
printf("error \n");
}
Uint32 buffer_size = 4096;
char* buffer = (char*)malloc(buffer_size);
while (true) {
if (fread(buffer, 1, buffer_size, fp) != buffer_size) {
printf("end of file\n");
break;
}
SDL_QueueAudio(deviceID, buffer, buffer_size);
}
printf("Play...\n");
SDL_Delay(10000);
// Uint32 residueAudioLen = 0;
// while (true) {
// residueAudioLen = SDL_GetQueuedAudioSize(deviceID);
// printf("%10d\n", residueAudioLen);
// if (residueAudioLen <= 0) break;
// SDL_Delay(1);
// }
// while (true) {
// printf("1 <20><>ͣ 2 <20><><EFBFBD><EFBFBD> 3 <20>˳<EFBFBD> \n");
// int flag = 0;
// scanf_s("%d", &flag);
// if (flag == 1)
// SDL_PauseAudioDevice(deviceID, 1);
// else if (flag == 2)
// SDL_PauseAudioDevice(deviceID, 0);
// else if (flag == 3)
// break;
// }
SDL_CloseAudio();
SDL_Quit();
fclose(fp);
return 0;
}

View File

@@ -1,225 +0,0 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
};
static SDL_AudioDeviceID input_dev;
static SDL_AudioDeviceID output_dev;
static Uint8 *buffer = 0;
static int in_pos = 0;
static int out_pos = 0;
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
int src_rate = 48000;
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_S16;
int src_nb_channels = 0;
uint8_t **src_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
int src_linesize;
int src_nb_samples = 480;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int64_t dst_ch_layout = AV_CH_LAYOUT_MONO;
int dst_rate = 48000;
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
int dst_nb_channels = 0;
uint8_t **dst_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
int dst_linesize;
int dst_nb_samples;
int max_dst_nb_samples;
static unsigned char audio_buffer[960 * 3];
static int audio_len = 0;
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
const char *dst_filename = NULL; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD>󲥷<EFBFBD><F3B2A5B7><EFBFBD>֤
FILE *dst_file;
int dst_bufsize;
const char *fmt;
// <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5>
struct SwrContext *swr_ctx;
double t;
int ret;
char *out = "audio_old.pcm";
FILE *outfile = fopen(out, "wb+");
void cb_in(void *userdata, Uint8 *stream, int len) {
// If len < 4, the printf below will probably segfault
// SDL_QueueAudio(output_dev, stream, len);
int64_t delay = swr_get_delay(swr_ctx, src_rate);
dst_nb_samples =
av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
if (dst_nb_samples > max_dst_nb_samples) {
av_freep(&dst_data[0]);
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, dst_sample_fmt, 1);
if (ret < 0) return;
max_dst_nb_samples = dst_nb_samples;
}
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
(const uint8_t **)&stream, src_nb_samples);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
return;
}
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, ret,
dst_sample_fmt, 1);
if (dst_bufsize < 0) {
fprintf(stderr, "Could not get sample buffer size\n");
return;
}
printf("t:%f in:%d out:%d %d\n", t, src_nb_samples, ret, len);
memcpy(audio_buffer, dst_data[0], len);
// SDL_QueueAudio(output_dev, dst_data[0], len);
audio_len = len;
}
void cb_out(void *userdata, Uint8 *stream, int len) {
// If len < 4, the printf below will probably segfault
printf("cb_out len = %d\n", len);
SDL_memset(stream, 0, len);
if (audio_len == 0) return;
len = (len > audio_len ? audio_len : len);
SDL_MixAudioFormat(stream, audio_buffer, AUDIO_S16LSB, len,
SDL_MIX_MAXVOLUME);
}
int init() {
dst_filename = "res.pcm";
dst_file = fopen(dst_filename, "wb");
if (!dst_file) {
fprintf(stderr, "Could not open destination file %s\n", dst_filename);
exit(1);
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD>
/* create resampler context */
swr_ctx = swr_alloc();
if (!swr_ctx) {
fprintf(stderr, "Could not allocate resampler context\n");
ret = AVERROR(ENOMEM);
return -1;
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/* set options */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
// <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
/* initialize the resampling context */
if ((ret = swr_init(swr_ctx)) < 0) {
fprintf(stderr, "Failed to initialize the resampling context\n");
return -1;
}
/* allocate source and destination samples buffers */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD>
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
src_nb_channels, src_nb_samples,
src_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate source samples\n");
return -1;
}
/* compute the number of converted samples: buffering is avoided
* ensuring that the output buffer will contain at least all the
* converted input samples */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
max_dst_nb_samples = dst_nb_samples =
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
/* buffer is going to be directly written to a rawaudio file, no alignment */
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
dst_nb_channels, dst_nb_samples,
dst_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate destination samples\n");
return -1;
}
}
int main() {
init();
SDL_Init(SDL_INIT_AUDIO);
// 16Mb should be enough; the test lasts 5 seconds
buffer = (Uint8 *)malloc(16777215);
SDL_AudioSpec want_in, want_out, have_in, have_out;
SDL_zero(want_in);
want_in.freq = 48000;
want_in.format = AUDIO_S16LSB;
want_in.channels = 1;
want_in.samples = 480;
want_in.callback = cb_in;
input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in, 0);
printf("%d %d %d %d\n", have_in.freq, have_in.format, have_in.channels,
have_in.samples);
if (input_dev == 0) {
SDL_Log("Failed to open input: %s", SDL_GetError());
return 1;
}
SDL_zero(want_out);
want_out.freq = 48000;
want_out.format = AUDIO_S16LSB;
want_out.channels = 1;
want_out.samples = 480;
want_out.callback = cb_out;
output_dev = SDL_OpenAudioDevice(NULL, 0, &want_out, &have_out, 0);
printf("%d %d %d %d\n", have_out.freq, have_out.format, have_out.channels,
have_out.samples);
if (output_dev == 0) {
SDL_Log("Failed to open input: %s", SDL_GetError());
return 1;
}
SDL_PauseAudioDevice(input_dev, 0);
SDL_PauseAudioDevice(output_dev, 0);
while (1) {
}
SDL_CloseAudioDevice(output_dev);
SDL_CloseAudioDevice(input_dev);
free(buffer);
fclose(outfile);
}

View File

@@ -1,95 +0,0 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int ret;
AVFrame *frame = NULL;
AVFrame *resampled_frame = NULL;
AVCodecContext *codec_ctx = NULL;
SwrContext *swr_ctx = NULL;
// Initialize FFmpeg
av_log_set_level(AV_LOG_INFO);
av_register_all();
// Allocate input frame
frame = av_frame_alloc();
if (!frame) {
av_log(NULL, AV_LOG_ERROR, "Failed to allocate input frame\n");
return -1;
}
// Allocate output frame for resampled data
resampled_frame = av_frame_alloc();
if (!resampled_frame) {
av_log(NULL, AV_LOG_ERROR, "Failed to allocate output frame\n");
return -1;
}
// Set input frame properties
frame->format = AV_SAMPLE_FMT_FLTP; // Input sample format (float planar)
frame->channel_layout = AV_CH_LAYOUT_STEREO; // Input channel layout (stereo)
frame->sample_rate = 44100; // Input sample rate (44100 Hz)
frame->nb_samples = 1024; // Number of input samples
// Set output frame properties
resampled_frame->format =
AV_SAMPLE_FMT_S16; // Output sample format (signed 16-bit)
resampled_frame->channel_layout =
AV_CH_LAYOUT_STEREO; // Output channel layout (stereo)
resampled_frame->sample_rate = 48000; // Output sample rate (48000 Hz)
resampled_frame->nb_samples = av_rescale_rnd(
frame->nb_samples, resampled_frame->sample_rate, frame->sample_rate,
AV_ROUND_UP); // Number of output samples
// Initialize resampler context
swr_ctx = swr_alloc_set_opts(
NULL, av_get_default_channel_layout(resampled_frame->channel_layout),
av_get_default_sample_fmt(resampled_frame->format),
resampled_frame->sample_rate,
av_get_default_channel_layout(frame->channel_layout),
av_get_default_sample_fmt(frame->format), frame->sample_rate, 0, NULL);
if (!swr_ctx) {
av_log(NULL, AV_LOG_ERROR, "Failed to allocate resampler context\n");
return -1;
}
// Initialize and configure the resampler
if ((ret = swr_init(swr_ctx)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to initialize resampler context: %s\n",
av_err2str(ret));
return -1;
}
// Allocate buffer for output samples
ret = av_samples_alloc(resampled_frame->data, resampled_frame->linesize,
resampled_frame->channels, resampled_frame->nb_samples,
resampled_frame->format, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to allocate output samples buffer: %s\n",
av_err2str(ret));
return -1;
}
// Resample the input data
ret = swr_convert(swr_ctx, resampled_frame->data, resampled_frame->nb_samples,
(const uint8_t **)frame->data, frame->nb_samples);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to resample input data: %s\n",
av_err2str(ret));
return -1;
}
// Cleanup and free resources
swr_free(&swr_ctx);
av_frame_free(&frame);
av_frame_free(&resampled_frame);
return 0;
}

View File

@@ -1,205 +0,0 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
};
static SDL_AudioDeviceID input_dev;
static SDL_AudioDeviceID output_dev;
static Uint8 *buffer = 0;
static int in_pos = 0;
static int out_pos = 0;
int64_t src_ch_layout = AV_CH_LAYOUT_MONO;
int src_rate = 48000;
enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT;
int src_nb_channels = 0;
uint8_t **src_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
int src_linesize;
int src_nb_samples = 480;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO;
int dst_rate = 48000;
enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;
int dst_nb_channels = 0;
uint8_t **dst_data = NULL; // <20><><EFBFBD><EFBFBD>ָ<EFBFBD><D6B8>
int dst_linesize;
int dst_nb_samples;
int max_dst_nb_samples;
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
const char *dst_filename = NULL; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pcm<63><6D><EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>Ȼ<EFBFBD>󲥷<EFBFBD><F3B2A5B7><EFBFBD>֤
FILE *dst_file;
int dst_bufsize;
const char *fmt;
// <20>ز<EFBFBD><D8B2><EFBFBD>ʵ<EFBFBD><CAB5>
struct SwrContext *swr_ctx;
double t;
int ret;
char *out = "audio_old.pcm";
FILE *outfile = fopen(out, "wb+");
void cb_in(void *userdata, Uint8 *stream, int len) {
// If len < 4, the printf below will probably segfault
{
fwrite(stream, 1, len, outfile);
fflush(outfile);
}
{
int64_t delay = swr_get_delay(swr_ctx, src_rate);
dst_nb_samples =
av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
if (dst_nb_samples > max_dst_nb_samples) {
av_freep(&dst_data[0]);
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, dst_sample_fmt, 1);
if (ret < 0) return;
max_dst_nb_samples = dst_nb_samples;
}
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
(const uint8_t **)&stream, src_nb_samples);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
return;
}
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
ret, dst_sample_fmt, 1);
if (dst_bufsize < 0) {
fprintf(stderr, "Could not get sample buffer size\n");
return;
}
printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret);
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
}
}
void cb_out(void *userdata, Uint8 *stream, int len) {
// If len < 4, the printf below will probably segfault
SDL_memcpy(buffer + out_pos, stream, len);
out_pos += len;
}
int init() {
dst_filename = "res.pcm";
dst_file = fopen(dst_filename, "wb");
if (!dst_file) {
fprintf(stderr, "Could not open destination file %s\n", dst_filename);
exit(1);
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD>
/* create resampler context */
swr_ctx = swr_alloc();
if (!swr_ctx) {
fprintf(stderr, "Could not allocate resampler context\n");
ret = AVERROR(ENOMEM);
return -1;
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/* set options */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
// <20><>ʼ<EFBFBD><CABC><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
/* initialize the resampling context */
if ((ret = swr_init(swr_ctx)) < 0) {
fprintf(stderr, "Failed to initialize the resampling context\n");
return -1;
}
/* allocate source and destination samples buffers */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><D4B4><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4>ռ<EFBFBD>
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
src_nb_channels, src_nb_samples,
src_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate source samples\n");
return -1;
}
/* compute the number of converted samples: buffering is avoided
* ensuring that the output buffer will contain at least all the
* converted input samples */
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
max_dst_nb_samples = dst_nb_samples =
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
/* buffer is going to be directly written to a rawaudio file, no alignment */
dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize,
dst_nb_channels, dst_nb_samples,
dst_sample_fmt, 0);
if (ret < 0) {
fprintf(stderr, "Could not allocate destination samples\n");
return -1;
}
}
int main() {
init();
SDL_Init(SDL_INIT_AUDIO);
// 16Mb should be enough; the test lasts 5 seconds
buffer = (Uint8 *)malloc(16777215);
SDL_AudioSpec want_in, want_out, have_in, have_out;
SDL_zero(want_in);
want_in.freq = 48000;
want_in.format = AUDIO_F32LSB;
want_in.channels = 2;
want_in.samples = 960;
want_in.callback = cb_in;
input_dev = SDL_OpenAudioDevice(NULL, 1, &want_in, &have_in,
SDL_AUDIO_ALLOW_ANY_CHANGE);
printf("%d %d %d %d\n", have_in.freq, have_in.format, have_in.channels,
have_in.samples);
if (input_dev == 0) {
SDL_Log("Failed to open input: %s", SDL_GetError());
return 1;
}
SDL_PauseAudioDevice(input_dev, 0);
SDL_PauseAudioDevice(output_dev, 0);
SDL_Delay(5000);
SDL_CloseAudioDevice(output_dev);
SDL_CloseAudioDevice(input_dev);
free(buffer);
fclose(outfile);
}

View File

@@ -1,123 +0,0 @@
#define __STDC_CONSTANT_MACROS
extern "C" {
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libswresample/swresample.h>
}
#include <windows.h>
#include <memory>
#include <string>
#include <vector>
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "Winmm.lib")
using std::shared_ptr;
using std::string;
using std::vector;
void capture_audio() {
// windows api <20><>ȡ<EFBFBD><C8A1>Ƶ<EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD>ffmpeg <20><><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><E1B9A9>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><E8B1B8>api<70><69>
int nDeviceNum = waveInGetNumDevs();
vector<string> vecDeviceName;
for (int i = 0; i < nDeviceNum; ++i) {
WAVEINCAPS wic;
waveInGetDevCaps(i, &wic, sizeof(wic));
// ת<><D7AA>utf-8
int nSize = WideCharToMultiByte(CP_UTF8, 0, wic.szPname,
wcslen(wic.szPname), NULL, 0, NULL, NULL);
shared_ptr<char> spDeviceName(new char[nSize + 1]);
memset(spDeviceName.get(), 0, nSize + 1);
WideCharToMultiByte(CP_UTF8, 0, wic.szPname, wcslen(wic.szPname),
spDeviceName.get(), nSize, NULL, NULL);
vecDeviceName.push_back(spDeviceName.get());
av_log(NULL, AV_LOG_DEBUG, "audio input device : %s \n",
spDeviceName.get());
}
if (vecDeviceName.size() <= 0) {
av_log(NULL, AV_LOG_ERROR, "not find audio input device.\n");
return;
}
string sDeviceName = "audio=" + vecDeviceName[0]; // ʹ<>õ<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>Ƶ<EFBFBD>
// ffmpeg
avdevice_register_all(); // ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
AVInputFormat* ifmt =
(AVInputFormat*)av_find_input_format("dshow"); // <20><><EFBFBD>òɼ<C3B2><C9BC><EFBFBD>ʽ dshow
if (ifmt == NULL) {
av_log(NULL, AV_LOG_ERROR, "av_find_input_format for dshow fail.\n");
return;
}
AVFormatContext* fmt_ctx = NULL; // format <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int ret = avformat_open_input(&fmt_ctx, sDeviceName.c_str(), ifmt,
NULL); // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD>
if (ret != 0) {
av_log(NULL, AV_LOG_ERROR, "avformat_open_input fail. return %d.\n", ret);
return;
}
AVPacket pkt;
int64_t src_rate = 44100;
int64_t dst_rate = 48000;
SwrContext* swr_ctx = swr_alloc();
uint8_t** dst_data = NULL;
int dst_linesize = 0;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "in_channel_layout", AV_CH_LAYOUT_MONO, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
av_opt_set_int(swr_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
// <20><>ʼ<EFBFBD><CABC>SwrContext
swr_init(swr_ctx);
FILE* fp = fopen("dst.pcm", "wb");
int count = 0;
while (count++ < 10) {
ret = av_read_frame(fmt_ctx, &pkt);
if (ret != 0) {
av_log(NULL, AV_LOG_ERROR, "av_read_frame fail, return %d .\n", ret);
break;
}
int out_samples_per_channel =
(int)av_rescale_rnd(1024, dst_rate, src_rate, AV_ROUND_UP);
int out_buffer_size = av_samples_get_buffer_size(
NULL, 1, out_samples_per_channel, AV_SAMPLE_FMT_S16, 0);
// uint8_t* out_buffer = (uint8_t*)av_malloc(out_buffer_size);
ret = av_samples_alloc_array_and_samples(
&dst_data, &dst_linesize, 2, out_buffer_size, AV_SAMPLE_FMT_S16, 0);
// <20><><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD>
swr_convert(swr_ctx, dst_data, out_samples_per_channel,
(const uint8_t**)&pkt.data, 1024);
fwrite(dst_data[1], 1, out_buffer_size, fp);
av_packet_unref(&pkt); // <20><><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD>pkt<6B><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ棬<DAB4><E6A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>й¶
}
fflush(fp); // ˢ<><CBA2><EFBFBD>ļ<EFBFBD>io<69><6F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
fclose(fp);
avformat_close_input(&fmt_ctx);
}
int main(int argc, char** argv) {
av_log_set_level(AV_LOG_DEBUG); // <20><><EFBFBD><EFBFBD>ffmpeg<65><67>־<EFBFBD><D6BE><EFBFBD>ȼ<EFBFBD>
capture_audio();
Sleep(1);
}

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