287 Commits

Author SHA1 Message Date
dijunkun
2c622bc76e [ci] update GitHub Actions to use macos-13 runner instead of macos-15 for Intel builds 2025-10-27 22:31:23 +08:00
dijunkun
b790c7d08e [chore] update README 2025-10-27 22:11:30 +08:00
dijunkun
0ca90d2516 [chore] move minirtc into submodules 2025-10-27 21:36:11 +08:00
dijunkun
401bfe4483 [refactor] add namespace 'crossdesk' to codebase 2025-10-27 21:09:39 +08:00
dijunkun
3b34c26555 [feat] enable custom configuration of Coturn server port 2025-10-27 16:04:40 +08:00
dijunkun
b668b3c936 [chore] update README 2025-10-27 13:42:49 +08:00
dijunkun
cc19ec125a [ci] update close-issue.yml 2025-10-27 11:04:36 +08:00
dijunkun
ffa77fdf44 Merge branch 'run-in-bg' into self-hosted-server 2025-10-27 10:41:16 +08:00
dijunkun
47cf806532 [ci] add permissions to close-issue.yml 2025-10-25 17:06:14 +08:00
dijunkun
911dce2e71 [feat] optimize certificate selection table 2025-10-24 17:39:25 +08:00
dijunkun
9f80d4f69d Merge branch 'self-hosted-server' into run-in-bg 2025-10-24 14:16:18 +08:00
dijunkun
cba644f055 [fix] fix TURN server authentication credentials, fixes #8 2025-10-24 11:41:23 +08:00
dijunkun
f733fe9e49 Merge branch 'self-hosted-server' into run-in-bg 2025-10-24 11:05:07 +08:00
dijunkun
27263fe1db [ci] fix permission issue in close inactive issues script by updating token usage 2025-10-24 10:57:11 +08:00
dijunkun
698bf72a6c Merge branch 'self-hosted-server' into run-in-bg 2025-10-24 10:05:54 +08:00
dijunkun
0bd27d0b17 [ci] use GITHUB_SHORT_SHA instead of build number in version 2025-10-24 02:09:50 +08:00
dijunkun
ee5612da8b [ci] resolve compilation issue 2025-10-23 23:30:52 +08:00
dijunkun
c7a2023c88 [ci] add build ID to version number 2025-10-23 23:18:44 +08:00
dijunkun
c031a8c145 [fix] adjust settings window height for different platforms 2025-10-23 23:04:31 +08:00
dijunkun
0bf83f07ad [fix] correct version display in about window 2025-10-23 23:01:38 +08:00
dijunkun
3638b712bd [ci] update close-issue.yml 2025-10-23 21:20:10 +08:00
dijunkun
b2ab940f20 [feat] use no close select table 2025-10-23 17:55:30 +08:00
dijunkun
17f9536476 [chore] update README 2025-10-23 10:37:45 +08:00
dijunkun
0ef51e3faf [feat] add FAQ.md 2025-10-23 10:33:39 +08:00
dijunkun
cccf5dadb2 Merge branch 'run-in-bg' into self-hosted-server 2025-10-22 17:23:08 +08:00
dijunkun
2f0b0ffc22 [feat] add configuration to minimize to system tray when clicking the close button, refs #4 2025-10-22 17:21:47 +08:00
dijunkun
c7411b59f1 [chore] update README 2025-10-21 22:51:21 +08:00
dijunkun
8222782522 [chore] update README 2025-10-21 20:54:37 +08:00
dijunkun
5fed09c1aa [ci] only close inactive issues with no labels 2025-10-21 11:37:25 +08:00
dijunkun
8e499772f9 [ci] add GitHub Actions workflow to automatically close inactive issues 2025-10-21 11:29:31 +08:00
dijunkun
0da812e7e9 [chore] update README 2025-10-20 21:51:09 +08:00
dijunkun
3d67b6e9d6 [chore] update README 2025-10-20 11:18:35 +08:00
dijunkun
506b2893c6 [chore] update README 2025-10-20 11:04:41 +08:00
Junkun Di
56abe9e690 [chore] update README 2025-10-20 09:55:59 +08:00
dijunkun
ab13fa582d [chore] update README 2025-10-20 01:36:42 +08:00
dijunkun
b9e8192eee [feat] support self-hosted service, fixes #3 2025-10-20 01:02:44 +08:00
dijunkun
5590aaeb1e [feat] add support for self-hosted server configuration 2025-10-18 23:41:10 +08:00
dijunkun
adfe14809f [ci] switch GitHub-hosted runner from macos-13 to macos-15-intel 2025-10-18 02:40:32 +08:00
dijunkun
a21dbc8d69 Merge branch 'sdl3' into self-hosted-server 2025-10-18 02:26:01 +08:00
dijunkun
9d45e497f4 [config] disable SRTP by default 2025-10-18 02:09:40 +08:00
dijunkun
e5891eb397 [fix] use static pcre linking to ensure this lib runs on machines without pcre, fixes #5 2025-10-18 01:59:35 +08:00
dijunkun
b9c70f54d3 [feat] add self-hosted server configuration section in settings window 2025-10-17 17:22:31 +08:00
dijunkun
9cd617d078 [feat] add self-hosted server config item in settings windows 2025-10-16 17:42:49 +08:00
dijunkun
92fd7f2e89 [feat] remove vc runtime from nsis script since using static linking 2025-10-16 16:03:27 +08:00
dijunkun
b8535fff6f [feat] switch to /MT static runtime to improve Windows compatibility 2025-10-16 12:40:53 +08:00
dijunkun
09f34a81ad [feat] add screen capture frame rate option in settings window 2025-10-15 17:28:36 +08:00
dijunkun
22cc552e85 [feat] set screen capture frame rate to 60 fps 2025-10-15 15:42:42 +08:00
dijunkun
69a4dfcbb9 [feat] add fps display in net traffic stats window 2025-10-15 11:26:45 +08:00
dijunkun
e1390ca2d3 [chore] update README 2025-10-13 17:12:45 +08:00
dijunkun
dccdcd1b6f [chore] update README 2025-10-13 16:48:46 +08:00
dijunkun
276c3a336f [chore] update README 2025-10-13 10:52:01 +08:00
dijunkun
08518a9409 [fix] resolve mouse control issue due to SRTP packet parsing 2025-10-11 23:57:45 +08:00
dijunkun
f6b0767bb1 [chore] update README 2025-10-11 23:57:26 +08:00
Junkun Di
641fc84430 [chore] pdate README_EN 2025-10-11 17:50:41 +08:00
Junkun Di
4ce79b87a3 [chore] update README 2025-10-11 17:49:49 +08:00
dijunkun
477b204913 [chore] update README 2025-10-11 17:47:24 +08:00
dijunkun
98f349ea2f [chore] update README 2025-10-11 16:32:42 +08:00
dijunkun
47b1e15eef [chore] update README 2025-10-10 09:16:29 +08:00
dijunkun
590bf50924 [chore] update README 2025-10-09 16:36:41 +08:00
dijunkun
097633e47d [chore] add license 2025-10-09 16:03:05 +08:00
Junkun Di
d9ba88107d [chore] update README.md 2025-10-09 15:29:06 +08:00
Junkun Di
f6a6ef0b08 [chore] update README_CN.md 2025-10-09 15:28:06 +08:00
Junkun Di
a93ee0c19e [chore] update README.md 2025-10-09 15:25:21 +08:00
dijunkun
7e73553aca [fix] fix line drawing in recent connections window 2025-10-09 15:23:37 +08:00
Junkun Di
b858bd78c0 [chore] update README.md 2025-10-09 15:07:58 +08:00
dijunkun
f10947edf9 Merge branch 'sdl3' of https://github.com/kunkundi/crossdesk into sdl3 2025-10-09 14:47:47 +08:00
dijunkun
c723cca8f9 [chore] update README.md 2025-10-09 14:47:33 +08:00
dijunkun
b8f8b07ebe [chore] remove password regenerate button 2025-10-01 01:30:04 +08:00
dijunkun
f9c6e5a6ef [fix] correct packet loss in network statistics 2025-09-30 17:54:41 +08:00
dijunkun
15bf8ef8c0 [feat] show shield icon when SRTP is enabled 2025-09-30 17:26:42 +08:00
dijunkun
6565816c0e [fix] fix log path issue on first launch 2025-09-30 15:50:22 +08:00
dijunkun
2aa67ccd57 [feat] prompt user to close crossdesk.exe during install/uninstall on Windows 2025-09-30 15:27:34 +08:00
dijunkun
88c75f94e4 [feat] add SRTP switch in settings 2025-09-24 20:27:56 +08:00
dijunkun
aea9505c4c [chore] move nvidia-cuda-toolkit from Depends to Recommends in linux amd64 package 2025-09-12 17:42:47 +08:00
dijunkun
646740aab6 [chore] update minirtc to latest 2025-09-12 17:28:39 +08:00
dijunkun
a458836e6e [fix] resolve certs file loading failure on linux 2025-09-12 16:29:20 +08:00
dijunkun
5fe7df8ea8 [feat] ensure VC++ Redistributable is installed on Windows during setup 2025-09-12 12:49:35 +08:00
dijunkun
fda3743f86 [feat] add run-after-install option for windows installer 2025-09-11 16:23:48 +08:00
dijunkun
9e4170b3a8 [fix] update minirtc to fix startup crash on Windows when CUDA is not supported or not installed by using explicit dynamic loading 2025-09-11 01:01:46 +08:00
dijunkun
43338aaf02 [fix] release all peers when settings modified 2025-09-10 22:53:07 +08:00
dijunkun
274b7fcedc [test] test libstdc++ 2025-09-10 15:46:51 +08:00
dijunkun
01a5660984 [fix] set YUV colorspace to SDL_COLORSPACE_BT601_LIMITED to resolve NV12 texture display issue 2025-09-08 09:52:16 +08:00
dijunkun
a45118f785 [feat] remove internet shortcut installing for windows script 2025-09-05 18:51:16 +08:00
dijunkun
b6631c3db0 [fix] fix crash due to wrong linux keycode value 2025-09-04 16:31:03 +08:00
dijunkun
84d164c3af [fix] compelete linux x11 keycode map 2025-09-04 15:46:09 +08:00
dijunkun
32815ce25a [feat] use crossdesk/ubuntu20.04:latest as build image to support more users 2025-09-03 18:40:29 +08:00
dijunkun
7c2b3f8c8d [fix] use x64 and amd64 instead of x86_64 in artifact name 2025-09-03 17:10:17 +08:00
dijunkun
309d9d6ed6 [fix] fix linux x86_64 package script no Size information issue 2025-09-03 15:55:16 +08:00
dijunkun
91ab21c5f2 [fix] fix function keys release handling issue 2025-09-02 18:58:54 +08:00
dijunkun
b8369c4f0a [feat] remove redundant ImGui ConfigFlags to improve performance 2025-09-02 17:10:55 +08:00
dijunkun
a068a32303 [fix] remove SDL3 input audio stream recording 2025-09-02 16:53:55 +08:00
dijunkun
b6fe0da581 [feat] use SDL_WaitEventTimeout to refresh frames and reduce CPU usage 2025-09-02 16:45:04 +08:00
dijunkun
6e7e2697b5 [fix] fix dpi scaling 2025-09-02 15:48:05 +08:00
dijunkun
22084072b0 [feat] update icon files 2025-09-01 18:12:47 +08:00
dijunkun
d5457373f4 [feat] use squircle icons 2025-08-29 17:53:47 +08:00
dijunkun
01ae299cde [feat] delete old files 2025-08-29 10:16:26 +08:00
dijunkun
fbcd4c21cf [feat] use rsync instead of appleboy/scp-action to update artifacts 2025-08-29 09:56:20 +08:00
dijunkun
cf9dc0a9a5 [fix] fix artifacts generation path on linux in github actions scripts 2025-08-28 23:29:18 +08:00
dijunkun
1b4f41af46 [feat] upload artifacts to chinese server for download 2025-08-28 19:16:11 +08:00
dijunkun
7f3425519b [fix] fix linux github actions building 2025-08-28 19:14:25 +08:00
dijunkun
434c92deb6 [fix] add crossdesk_128x128.png to icons 2025-08-28 18:37:26 +08:00
dijunkun
e5eae1feb4 [feat] update icons 2025-08-28 18:23:02 +08:00
dijunkun
37e37d7d20 [fix] mv deb to /tmp in install process 2025-08-28 17:48:09 +08:00
dijunkun
062568dc96 [fix] fix SDL3 texture display error 2025-08-26 16:26:06 +08:00
dijunkun
d60fdf9050 [feat] update SDL2 to SDL3 2025-08-25 19:46:30 +08:00
dijunkun
9912a88a13 [feat] github actions support linux aarch64 platform 2025-08-22 18:43:07 +08:00
dijunkun
8a8f2cd5d7 [feat] update minirtc to support linux aarch64 platform 2025-08-21 20:16:55 +08:00
dijunkun
e77e16d9c2 [fix] Add validity check for props 2025-08-20 16:42:31 +08:00
dijunkun
1616d0ec33 [fix] Add validity check for props 2025-08-20 16:37:29 +08:00
dijunkun
374453775f [fix] fix connection status window gui display 2025-08-19 17:47:09 +08:00
dijunkun
6f8fd6a030 [fix] fix crash when transmission id does not exist 2025-08-19 16:33:29 +08:00
dijunkun
c7166975b3 [fix] fix version number replace for pages 2025-08-15 20:52:42 +08:00
dijunkun
2ae5e5a969 [feat] do not generate dmg for macOS 2025-08-15 20:34:27 +08:00
dijunkun
85cdc995c5 [fix] fix artifacts name with version number 2025-08-15 20:07:18 +08:00
dijunkun
b051c8a059 [fix] fix artifacts name with version number 2025-08-15 19:55:03 +08:00
dijunkun
508b7dc7a1 [feat] add script to update the version number in github pages 2025-08-15 19:41:04 +08:00
dijunkun
f203566b81 [feat] use dynamic version number 2025-08-15 19:35:16 +08:00
dijunkun
8360c1725f [feat] add release scripts 2025-08-15 15:41:10 +08:00
dijunkun
3b00fdef71 [fix] fix github actions scripts 2025-08-14 16:58:29 +08:00
dijunkun
f2664daaec [feat] add github actions scripts 2025-08-10 00:03:13 +08:00
dijunkun
1a1bbe3e4d [fix] fix cache file and cert file read 2025-08-09 23:59:53 +08:00
dijunkun
4c898c5f07 [fix] save log files into XDG_CACHE_HOME/CrossDesk/logs for Linux platform 2025-07-25 18:11:40 +08:00
dijunkun
5d704edbc8 [fix] use std::filesystem::create_directories instead of std::filesystem::create_directory to create folder 2025-07-24 15:52:52 +08:00
dijunkun
b23038bac6 [feat] update signal server domain 2025-07-23 17:30:30 +08:00
dijunkun
147b9b2ddc [fix] fix thumbnail order by using std::vector instead of std::unordered_map 2025-07-23 16:38:29 +08:00
dijunkun
1327b995f2 [fix] fix compile error on MacOSX 2025-07-23 11:31:32 +08:00
dijunkun
e8d7ec8daf [feat] save log and cache files into user folder 2025-07-22 18:55:12 +08:00
dijunkun
3bf68a396f [feat] add PathManager class in order to manage config/log/cache file path 2025-07-16 18:22:33 +08:00
kunkundi
29077fad5e Merge branch 'multi-stream' of https://github.com/kunkundi/crossdesk into multi-stream 2025-07-16 17:10:18 +08:00
kunkundi
69367bdadf [fix] fix same memory leak issue 2025-07-16 17:09:51 +08:00
dijunkun
f650b5a6ef [feat] use unordered map to store recent connection info so that the most recent connections are shown at the front 2025-07-15 17:34:25 +08:00
kunkundi
75021b74ef [fix] use temp frame to store the audio frame that will be sent 2025-07-15 17:18:00 +08:00
dijunkun
205421621b [fix] fix local id when using as client 2025-07-15 16:35:05 +08:00
dijunkun
4bb5607702 [fix] fix restart audio capturer error on MacOSX 2025-07-15 16:05:57 +08:00
kunkundi
78c54136e2 [fix] fix crash when Stop() called in SpeakerCapturerLinux 2025-07-15 15:38:58 +08:00
kunkundi
8fe8f4fd7e [feat] audio capture supported on Linux 2025-07-11 17:17:05 +08:00
dijunkun
c13e2613b6 [feat] audio capture supported on MacOSX 2025-07-02 19:25:21 +08:00
dijunkun
e52ec6cde2 [fix] use domain name instead of ip address to enable tls connection 2025-07-01 17:38:24 +08:00
dijunkun
ad6a24b230 [feat] add tls cert file path in configure parameters 2025-07-01 13:55:41 +08:00
dijunkun
ad60815d83 [fix] fix thumbnails deletion 2025-07-01 11:13:24 +08:00
dijunkun
c47a90bf9c [feat] update project name 2025-06-25 17:33:54 +08:00
dijunkun
3901e26c37 [fix] fix thumbnail filename which stores remote id, remote host name and remote password 2025-06-20 18:42:29 +08:00
dijunkun
41c27139a0 [feat] password modification supported 2025-06-20 17:50:04 +08:00
dijunkun
a31ca21ef4 [feat] receive id and password when login to server in the first time 2025-06-20 00:57:56 +08:00
dijunkun
57939ccd00 [feat] update submodule info 2025-06-18 00:06:47 +08:00
dijunkun
fc14f7b9c6 [fix] register minirtc as submodule properly 2025-06-17 23:29:02 +08:00
dijunkun
cfcf6dd9cb [feat] replace submodule projectx with minirtc at new path 2025-06-17 17:11:43 +08:00
dijunkun
854c3a72c7 [feat] modify project name to DeskPort 2025-06-17 14:24:41 +08:00
dijunkun
8004d25ca3 [feat] limit screen capturing to 30 fps 2025-06-07 02:46:51 +08:00
dijunkun
b04dfd188a [fix] fix SCStream stop error 2025-06-03 10:44:52 +08:00
dijunkun
383ace2493 [fix] only receive remote host info once 2025-06-03 10:33:32 +08:00
dijunkun
094204361c [feat] enable screen switch on Linux platform 2025-05-29 18:09:34 +08:00
dijunkun
d72c6d9df7 [feat] enable mouse dragging for MacOSx 2025-05-29 17:05:56 +08:00
dijunkun
05d73ebe9a [feat] add notification text in stream windows when connection failed/disconnected/closed 2025-05-29 16:51:18 +08:00
dijunkun
a8b5e934b8 [feat] implementation for ScreenCapturerSck::Destroy() and ScreenCapturerSck::Stop() 2025-05-29 15:49:42 +08:00
dijunkun
920677a433 [fix] use black to fill the blank areas around the thumbnail 2025-05-29 15:02:43 +08:00
dijunkun
b86d3d42ee [fix] fix color space for thumbnail 2025-05-29 14:17:33 +08:00
dijunkun
818dab764f [fix] do not recreate screen capturer when reload configuration file 2025-05-28 19:15:02 +08:00
dijunkun
7ea26854af [fix] fix strncpy error 2025-05-28 17:25:39 +08:00
dijunkun
f34b55b88a [fix] do not need to call SDL_CloseAudioDevice() before SDL_Quit() 2025-05-28 16:02:37 +08:00
dijunkun
67168f7735 [fix] destroy screen capturer only when clean up 2025-05-27 10:17:25 +08:00
dijunkun
6e69c37056 [feat] use display name as video stream id 2025-05-26 15:29:46 +08:00
dijunkun
8f5dd21e75 [fix] remove data length check in OnReceiveDataBufferCb 2025-05-22 18:09:23 +08:00
dijunkun
e2dfeb1186 [fix] fix RemoteAction read from received data 2025-05-22 17:18:06 +08:00
dijunkun
96c7d3174b [feat] improve the performance of ScreenCapturerSck 2025-05-22 17:17:15 +08:00
dijunkun
97f6c18296 [feat] update ScreenCapturerSck 2025-05-20 19:11:17 +08:00
dijunkun
9138ca771f [fix] do not send mouse click event when cursor is on control bar and display selectable 2025-05-16 19:06:25 +08:00
dijunkun
abc6b17a3b [fix] display index shown in control bar should start from 1 2025-05-16 18:51:57 +08:00
dijunkun
8e0524bf60 [feat] display switch supported on MacOSx platform 2025-05-16 18:49:20 +08:00
dijunkun
6ebf583a13 [fix] fix compile error on MacOSx 2025-05-15 19:54:17 +08:00
dijunkun
eca4f614c8 [feat] cursor mapping in different displays supported 2025-05-15 19:47:08 +08:00
dijunkun
11358e0b60 [feat] enable mouse control for multi-display 2025-05-15 18:37:59 +08:00
dijunkun
0a1014dded [fix] use serialize and deserialize function to process RemoteAction::HostInfo 2025-05-15 17:52:40 +08:00
dijunkun
a6beb48b1f [feat] display switch supported on Windows platform 2025-05-15 16:37:03 +08:00
dijunkun
5eff530ab9 [fix] fix ScreenCapturerWgc crash during clean up 2025-05-15 15:27:44 +08:00
dijunkun
e6e237279c [feat] update rtc module 2025-05-13 21:47:05 +08:00
dijunkun
6fe46f6181 [feat] add a=m lines in sdp for multi-stream 2025-05-12 22:09:08 +08:00
dijunkun
9b5023645c [feat] display selection supported on Windows platform 2025-05-09 22:30:56 +08:00
dijunkun
c2da4ebcb7 [feat] add remote display selection button in control bar 2025-05-09 17:23:27 +08:00
dijunkun
dc05f2405f [feat] add display selection method for Windows platform 2025-05-08 17:40:14 +08:00
dijunkun
e2a469ed65 [feat] remove dependence on ffmpeg for MacOSx 2025-05-08 11:33:04 +08:00
dijunkun
e89d385f99 [fix] remove redundant link flags on Linux platform 2025-05-08 11:07:04 +08:00
dijunkun
0a61dcc2be [fix] update keyboard converter 2025-05-07 19:47:09 +08:00
dijunkun
250fd49406 [feat] mouse/keyboard control and screen capture supported by using X11 on Linux platform 2025-05-07 19:37:41 +08:00
dijunkun
93bd5b2660 [fix] fix control bar display error in fullscreen mode when multiple connections established 2025-05-06 19:49:08 +08:00
dijunkun
d05ff9f905 [fix] update rtc module 2025-05-06 18:58:52 +08:00
dijunkun
f3451a5fa1 [fix] fix compile error on Linux 2025-05-06 18:39:30 +08:00
dijunkun
a9084ba98d [fix] fix control bar layout and position in stream window 2025-04-30 18:18:55 +08:00
dijunkun
893051f9b3 [fix] fix crash due to invalid iterator 2025-04-30 15:16:32 +08:00
dijunkun
dfbd4317b7 [feat] use tab bar to manage stream windows 2025-04-29 22:16:10 +08:00
dijunkun
532ad0eb51 [feat] support multi-stream and enable stream windows docking 2025-04-28 20:49:11 +08:00
dijunkun
184983d857 [fix] fix FindThumbnailPath() 2025-04-27 18:46:45 +08:00
dijunkun
c33e4bbe0e [fix] use relative percentage-based mouse mapping instead of absolute positions 2025-04-27 14:02:20 +08:00
dijunkun
4b9e86c424 [fix] fix recent connection deletion 2025-04-27 10:53:44 +08:00
dijunkun
f7f8ddd925 [fix] fix log error 2025-04-18 15:39:31 +08:00
dijunkun
188b1758f2 [feat] remove screen resolution info in HostInfo 2025-04-17 16:58:45 +08:00
dijunkun
9705374b9a [feat] save thumbnail as black picture if have not received a full frame 2025-04-16 16:47:30 +08:00
dijunkun
22aed6ea53 [fix] send host info when connected 2025-04-16 16:13:54 +08:00
dijunkun
44c5fde086 [fix] send host info when connected 2025-04-16 15:36:25 +08:00
dijunkun
2cd5a9dd76 [fix] do not send click event if cursor clicks the button which is on control bar 2025-04-16 14:47:44 +08:00
dijunkun
fa73447f88 [fix] fix mouse wheel mapping on Windows 2025-04-16 14:33:11 +08:00
dijunkun
536fe17055 [fix] fix wheel event grabbing 2025-04-15 17:32:23 +08:00
dijunkun
662cbbc3cc [feat] mouse middle button and wheel supported 2025-04-15 14:26:34 +08:00
dijunkun
69a8503ee1 [fix] need to DestroyPeer if NoSuchTransmissionId 2025-04-15 10:28:19 +08:00
dijunkun
b906bfcafd [fix] fix audio command sending 2025-04-14 17:33:35 +08:00
dijunkun
f891a047d6 [fix] flush STREAM_FRASH event when stream closed 2025-04-14 17:15:00 +08:00
dijunkun
dce32672a6 [fix] fix cursor mapping 2025-04-14 16:59:48 +08:00
dijunkun
2774991107 [fix] only send hostname and resolution before sending first frame 2025-04-14 16:49:31 +08:00
dijunkun
b7ce8b6299 [fix] fix render resolution 2025-04-14 16:31:46 +08:00
dijunkun
700fb2ec14 [feat] send server resolution before sending first frame 2025-04-14 16:12:55 +08:00
dijunkun
62d14587cd [fix] fix compile warning 2025-04-11 16:09:11 +08:00
dijunkun
824d9243cc [fix] fix join problem that when join to a server which does not exist in recent connections 2025-04-10 17:35:21 +08:00
dijunkun
5351b4da0e [fix] do not show stream window if the client is only in server mode 2025-04-10 17:19:19 +08:00
dijunkun
fcd0488624 [fix] fix control bar position when stream window created 2025-04-10 16:46:53 +08:00
dijunkun
193e905b28 [fix] save password only when join the connection successfully 2025-04-09 17:31:59 +08:00
dijunkun
f48085c69a [fix] fix connection error when reinput password 2025-04-09 17:17:30 +08:00
dijunkun
cf078be53f [feat] separate function Run() into several functions 2025-04-09 16:51:33 +08:00
dijunkun
badcd6a05c [fix] fix mouse contorl error 2025-04-09 15:47:24 +08:00
dijunkun
d828bd736d [feat] use peer map to manage multiple client instances 2025-04-08 17:34:33 +08:00
dijunkun
1e014bdae3 [fix] fix crash due to encoder releasing 2025-03-25 17:37:12 +08:00
dijunkun
334ab182db [fix] fix crash when try to close connection 2025-03-24 17:40:44 +08:00
dijunkun
f52de76bfc [feat] update rtc api 2025-03-19 18:38:13 +08:00
dijunkun
6c7d0e8cab [feat] support FIR and NACK 2025-03-07 18:37:26 +08:00
dijunkun
7229ae856e [fix] fix video capture timestamp 2025-02-28 15:53:03 +08:00
dijunkun
597d760d24 [feat] fix crash and update rtc module 2025-02-06 17:37:18 +08:00
dijunkun
bcf61e1ada [fix] fix crash due to sub stream window properties 2025-02-05 23:31:33 +08:00
dijunkun
08e596714b [fix] fix h264 rtp packetization error 2025-02-05 17:29:26 +08:00
dijunkun
bff577ba34 [feat] update rtc module 2025-01-23 17:29:26 +08:00
dijunkun
4df935b9d2 [fix] fix log name conflict 2025-01-22 17:34:35 +08:00
dijunkun
9bb560314b [fix] use reinterpret_cast to convert u8"x" to const char* 2024-12-18 17:29:13 +08:00
dijunkun
1a0c5e8b42 [feat] use client_properties_ and server_properties_ to store streams properties 2024-12-03 17:22:15 +08:00
dijunkun
011919d0e7 [feat] use SubStreamWindowProperties to store sub stream properties 2024-12-02 17:30:51 +08:00
dijunkun
418ab7a1d2 [fix] fix net traffic stats display 2024-12-02 11:03:55 +08:00
dijunkun
6a2c9af316 [feat] loss rate display supported 2024-12-01 17:00:59 +08:00
dijunkun
eaabf478cc [feat] update net statistics module 2024-11-29 17:54:15 +08:00
dijunkun
ffe3ca76af [feat] switch rtc module into new branch 'qos' 2024-11-28 18:27:34 +08:00
dijunkun
c5a6302220 [fix] update rtc module 2024-11-28 15:16:48 +08:00
dijunkun
9b2f81690f [fix] fix stream window scale error 2024-11-27 20:28:42 +08:00
dijunkun
9621e6b570 [fix] fix right shift key mapping on MacOSX 2024-11-27 17:33:54 +08:00
dijunkun
ce3ae03bef [fix] prevent cursor relocation when stream window resized 2024-11-27 17:24:45 +08:00
dijunkun
e0457213ea [fix] fix display errors when stream window resized 2024-11-27 16:14:14 +08:00
dijunkun
10cdc440a0 [fix] fix key press/release event 2024-11-27 14:34:54 +08:00
dijunkun
ddc62c90bb [fix] fix all unused variables and type conversions on Windows 2024-11-27 11:17:43 +08:00
dijunkun
9e70d0e8fc [fix] fix all unused variables and type conversions on MacOSX 2024-11-27 10:57:09 +08:00
dijunkun
4533d53ba8 [fix] fix all unused variables and type conversions 2024-11-26 23:31:06 +08:00
dijunkun
0caa243006 [fix] fix unused variables and type conversions 2024-11-26 17:31:37 +08:00
dijunkun
1e58abdfdd [feat] rewrite log module 2024-11-26 16:05:28 +08:00
dijunkun
370ac08d09 [fix] use new rtc api 2024-11-26 15:06:55 +08:00
dijunkun
31b6b2736c [fix] fix control and shift keys convertion from Win to MacOSX 2024-11-25 17:29:49 +08:00
dijunkun
abd22ab7f1 [fix] fix Win/MacOSX keycodes convertion 2024-11-25 17:15:22 +08:00
dijunkun
5ab68988aa [fix] fix stream window grabbing cannot be released after connection closed 2024-11-22 18:12:36 +08:00
dijunkun
ba3edcc02a [feat] keyboard control supported on MacOSX 2024-11-22 18:05:13 +08:00
dijunkun
8414a57a5b [feat] keyboard control supported on Windows 2024-11-22 17:17:33 +08:00
dijunkun
3f777e4662 [fix] fix compile error 2024-11-22 16:45:14 +08:00
dijunkun
52828183a1 [feat] keyboard capture supported on Windows 2024-11-22 16:39:01 +08:00
dijunkun
df7489f8e2 [fix] update imgui to v1.91.5-docking to fix: inputText cannot input text after add a space in remote id 2024-11-21 18:40:42 +08:00
dijunkun
4fea7d86e1 [feat] cursor will move into remote id input box automaticlly if the last input remote id does not exist 2024-11-21 16:20:25 +08:00
dijunkun
cb17b7c8db [fix] fix setting item [enable turn] 2024-11-21 16:05:10 +08:00
dijunkun
cf7ef89bf2 [fix] fix cursor position when stream window initialization 2024-11-21 10:34:04 +08:00
dijunkun
2d2a578800 [fix] fix control window position when stream window size changed 2024-11-20 19:17:33 +08:00
dijunkun
0ba12f3ccf [fix] only display client side net status if connected to itself 2024-11-19 22:33:59 +08:00
dijunkun
5ac603977d [fix] fix net traffic stats display 2024-11-19 17:31:31 +08:00
dijunkun
25d5a80bee [fix] reset net traffic stats after connection closed 2024-11-19 17:26:01 +08:00
dijunkun
c9d452a025 [feat] disable settings modification during streaming 2024-11-19 17:21:27 +08:00
dijunkun
8132d62c02 [feat] show net traffic stats in control bar 2024-11-19 16:56:30 +08:00
dijunkun
ca32ebeefe [feat] net traffic stats supported 2024-11-18 17:33:09 +08:00
dijunkun
5bf5e9ee25 [feat] change UI layouts 2024-11-18 16:08:54 +08:00
dijunkun
bf097008e7 [feat] use SDL_WINDOW_HIDDEN to delay showing the main window to avoid black window during initialization 2024-11-18 15:10:02 +08:00
dijunkun
59b1208321 [feat] load different size fonts in initialization 2024-11-15 18:19:54 +08:00
dijunkun
84194188f8 [fix] fix redundant recent connection cache file due to remember_password_ flag not being set correctly 2024-11-14 17:26:08 +08:00
dijunkun
a067441fb9 [feat] change recent connections layout 2024-11-14 17:13:38 +08:00
dijunkun
6eac8380b6 [feat] generate random AES128 key and iv during initialization, save them in cache file and load when program starts 2024-11-14 00:49:30 +08:00
dijunkun
5aed8317ca [feat] quick connecting supported 2024-11-13 23:27:06 +08:00
dijunkun
9aed7a19cf [fix] fix AES encrypt and decrypt 2024-11-13 17:58:59 +08:00
dijunkun
c0154be1aa [fix] use APIs in evp.h to encrypt and decrypt 2024-11-13 00:47:54 +08:00
dijunkun
f9d024e971 [feat] use AES to encrypt image names 2024-11-12 17:30:28 +08:00
dijunkun
526eb4bb31 [feat] add trash and connect buttons on recent connection images 2024-11-11 17:05:47 +08:00
dijunkun
f9347cbd49 [feat] use confirm window to confirm recent connections deletion 2024-11-11 10:49:07 +08:00
dijunkun
b6671bdbe7 [feat] delete recent connection supported 2024-11-08 17:52:19 +08:00
dijunkun
edcf5d408c [feat] use horizontal scroll bar to show all recent connections 2024-11-08 14:51:58 +08:00
dijunkun
8c8731909e [feat] remote host names will be shown below thumbnails 2024-11-07 17:30:55 +08:00
dijunkun
de721ac6e3 [feat] use a list to show thumbnails of recent connections 2024-11-07 16:58:25 +08:00
dijunkun
963f1da1d8 [fix] fix redefinition error on MacOSX 2024-11-07 16:32:35 +08:00
dijunkun
4c6159e4d4 [feat] write and load thumbnails supported 2024-11-07 16:29:02 +08:00
dijunkun
e3c2e9ec6d [fix] fix png images write and read 2024-11-07 02:38:35 +08:00
dijunkun
02022bdcdf [feat] add recent connections window 2024-11-06 17:28:11 +08:00
dijunkun
19ea426efc [feat] change UI layouts 2024-11-05 17:29:39 +08:00
151 changed files with 72742 additions and 49502 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
*.h linguist-language=C++
*.cpp linguist-language=C++
*.lua linguist-language=Xmake

387
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,387 @@
name: Build and Release
on:
push:
branches:
- "**"
tags:
- "*"
workflow_dispatch:
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
# Linux amd64
build-linux-amd64:
name: Build on Ubuntu 22.04 amd64
runs-on: ubuntu-22.04
container:
image: crossdesk/ubuntu20.04:latest
options: --user root
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
- name: Set legal Debian version
shell: bash
id: set_deb_version
run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
else
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
ls -la $XMAKE_GLOBALDIR
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
xmake b -vy --root crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package
run: |
chmod +x ./scripts/linux/pkg_amd64.sh
./scripts/linux/pkg_amd64.sh ${LEGAL_VERSION}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-amd64-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-amd64-${{ env.LEGAL_VERSION }}.deb
# Linux arm64
build-linux-arm64:
name: Build on Ubuntu 22.04 arm64
runs-on: ubuntu-22.04-arm
strategy:
matrix:
include:
- arch: arm64
image: crossdesk/ubuntu20.04-arm64v8:latest
package_script: ./scripts/linux/pkg_arm64.sh
container:
image: ${{ matrix.image }}
options: --user root
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
- name: Set legal Debian version
shell: bash
id: set_deb_version
run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}-${SHORT_SHA}"
else
LEGAL_VERSION="${VERSION_NUM}-${SHORT_SHA}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --root -y
xmake b -vy --root crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${LEGAL_VERSION}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}.deb
# macOS
build-macos:
name: Build on macOS
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: x64
runner: macos-13
cache-key: intel
out-dir: ./build/macosx/x86_64/release/crossdesk
package_script: ./scripts/macosx/pkg_x64.sh
- arch: arm64
runner: macos-14
cache-key: arm
out-dir: ./build/macosx/arm64/release/crossdesk
package_script: ./scripts/macosx/pkg_arm64.sh
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}"
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: ~/.xmake/packages
key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
- name: Install xmake
run: brew install xmake
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Build CrossDesk
run: |
xmake f --CROSSDESK_VERSION=${VERSION_NUM} -y
xmake b -vy crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package CrossDesk app
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${VERSION_NUM}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
- name: Move files to release dir
run: |
mkdir -p release
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
# Windows
build-windows-x64:
name: Build on Windows x64
runs-on: windows-2022
env:
XMAKE_GLOBALDIR: D:\xmake_global
steps:
- name: Extract version number
shell: pwsh
run: |
$ref = $env:GITHUB_REF
$version = $ref -replace '^refs/(tags|heads)/', ''
$version = $version -replace '^v', ''
$version = $version -replace '/', '-'
$SHORT_SHA = $env:GITHUB_SHA.Substring(0,7)
echo "VERSION_NUM=$version-$SHORT_SHA" >> $env:GITHUB_ENV
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: D:\xmake_global\.xmake\packages
key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-intel-
- name: Install xmake
run: |
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
xmake create cuda
Set-Location cuda
xmake g --theme=plain
$cudaPath = ""
$packagesPath = "D:\xmake_global\.xmake\packages"
if (Test-Path $packagesPath) {
Write-Host "Packages directory exists: $packagesPath"
try {
$info = xmake require --info "cuda 12.6.3" 2>$null
if ($null -ne $info -and $info -ne "") {
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
} catch {}
} else {
Write-Host "Packages directory not found: $packagesPath"
Write-Host "Installing CUDA package..."
xmake require -vy "cuda 12.6.3"
$info = xmake require --info "cuda 12.6.3"
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
Write-Host "Resolved CUDA_PATH = $cudaPath"
Pop-Location
- name: Download INetC plugin
shell: powershell
run: |
$url = "https://github.com/DigitalMediaServer/NSIS-INetC-plugin/releases/download/v1.0.5.7/InetC.zip"
$out = "$env:RUNNER_TEMP\InetC.zip"
Invoke-WebRequest -Uri $url -OutFile $out
Expand-Archive $out -DestinationPath "$env:RUNNER_TEMP\InetC" -Force
$source = "$env:RUNNER_TEMP\InetC\x86-unicode\INetC.dll"
$target = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\INetC.dll"
Write-Host "Copying $source to $target"
Copy-Item $source $target -Force
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Copy nsProcess plugin to NSIS folder
run: |
$nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode"
copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir
- name: Build CrossDesk
run: |
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} -y
xmake b -vy crossdesk
- name: Decode and save certificate
shell: powershell
run: |
New-Item -ItemType Directory -Force -Path certs
[System.IO.File]::WriteAllBytes('certs\crossdesk.cn_root.crt', [Convert]::FromBase64String('${{ secrets.CROSSDESK_CERT_BASE64 }}'))
- name: Package
shell: pwsh
run: |
cd "${{ github.workspace }}\scripts\windows"
makensis /DVERSION=$env:VERSION_NUM nsis_script.nsi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-win-x64-${{ env.VERSION_NUM }}
path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe
release:
name: Publish Release
if: startsWith(github.ref, 'refs/tags/v')
needs:
[build-linux-amd64, build-linux-arm64, build-macos, build-windows-x64]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
VERSION_NUM="${VERSION#v}-${SHORT_SHA}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
- name: Rename artifacts
run: |
mkdir -p release
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_NUM }}.pkg
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_NUM }}.pkg
cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_NUM }}.deb
cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_NUM }}.deb
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_NUM }}.exe
- name: List release files
run: ls -lh release/
- name: Upload to Versioned GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.VERSION_NUM }}
name: Release v${{ steps.version.outputs.VERSION_NUM }}
draft: false
prerelease: false
files: release/*
generate_release_notes: false
body: |
Binary release only. Source code is not included.
- name: Create or update 'latest' tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -f latest
git push origin latest --force
- name: Upload to GitHub Release (latest)
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: Latest Release
draft: false
prerelease: false
files: release/*
generate_release_notes: false
- name: Upload artifacts to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --progress --delete
path: release/*
remote_path: /var/www/html/downloads/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}

80
.github/workflows/close-issue.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Close Inactive Issues
on:
schedule:
# run every day at midnight
- cron: "0 0 * * *"
permissions:
issues: write
pull-requests: write
contents: read
jobs:
close_inactive_issues:
runs-on: ubuntu-latest
steps:
- name: Check inactive issues and close them
uses: actions/github-script@v6
with:
script: |
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
const now = new Date().getTime();
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
for (const issue of issues) {
// skip pull requests (they are also returned by listForRepo)
if (issue.pull_request) continue;
// skip labeled issues
if (issue.labels.length > 0) {
console.log(`Skipping issue #${issue.number} (Has labels).`);
continue;
}
// fetch comments for this issue
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 100,
});
// determine the "last activity" time
let lastActivityTime;
if (comments.length > 0) {
const lastComment = comments[comments.length - 1];
lastActivityTime = new Date(lastComment.updated_at).getTime();
} else {
lastActivityTime = new Date(issue.created_at).getTime();
}
// check inactivity
if (now - lastActivityTime > inactivePeriod) {
console.log(`Closing inactive issue: #${issue.number} (No recent replies for 7 days)`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: "This issue has been automatically closed due to inactivity for 7 days."
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
});
} else {
console.log(`Skipping issue #${issue.number} (Active within 7 days).`);
}
}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

51
.github/workflows/update-pages.yml vendored Normal file
View File

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

6
.gitmodules vendored
View File

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

View File

@@ -1,39 +0,0 @@
<?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>

177
LICENSE
View File

@@ -1,20 +1,165 @@
The MIT License (MIT)
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2023 The Continuous Desk Authors
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

308
README.md
View File

@@ -1,67 +1,307 @@
# Continuous Desk
# CrossDesk
#### More than remote desktop
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen.svg)]()
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
[![GitHub stars](https://img.shields.io/github/stars/kunkundi/crossdesk.svg?style=social)]()
[![GitHub forks](https://img.shields.io/github/forks/kunkundi/crossdesk.svg?style=social)]()
----
[中文](README_CN.md) / [English](README.md)
[ [English](README_EN.md) / 中文 ]
![sup_example](https://github.com/dijunkun/continuous-desk/assets/29698109/46536bc8-3ddd-438d-bf52-dccf143f1c20)
![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905)
# 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 是一个轻量级的跨平台远程桌面软件。
Continuous Desk is an experimental application of [Projectx](https://github.com/dijunkun/projectx) real-time communications library. Projectx is a lightweight cross-platform real-time communications library. It has basic capabilities such as network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding/decoding (H264), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, and network congestion control ([TCP over UDP](https://libnice.freedesktop.org/)).
CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)视频软硬编解码H264/AV1音频编解码[Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。
## Usage
Enter the remote desktop ID in the 'REMOTE ID' field on the menu bar, and click 'Connect' button to initiate the remote connection.
## 使用
![usage1](https://github.com/dijunkun/continuous-desk/assets/29698109/2ad59e6d-bdba-46d0-90cf-cbc9c06c2278)
在菜单栏“对端ID”处输入远端桌面的ID点击“→”即可发起远程连接。
If the remote desktop is set with a connection password, the local end needs to enter the correct password to initiate the remote connection. If the password is incorrect, an "Incorrect password" alert will appear in the status bar.
![usage1](https://github.com/user-attachments/assets/3a4bb59f-c84c-44d2-9a20-11790aac510e)
![incorrect password](https://github.com/dijunkun/continuous-desk/assets/29698109/cb05501c-ec4e-4adf-952d-7a55ef770a97)
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。
After connection successfully established, the status bar will display the message "ClientConnected."
![password](https://github.com/user-attachments/assets/1beadcce-640d-4f5c-8e77-51917b5294d5)
![success](https://github.com/dijunkun/continuous-desk/assets/29698109/0cca21f7-48fe-44a5-b83d-eafeb8a81eb1)
发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
## How to build
## 如何编译
Requirements:
依赖:
- [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/)
- [vcpkg](https://vcpkg.io/en/getting-started)
Following packages need to be installed on Linux:
Linux环境下需安装以下包
```
sudo apt-get install -y nvidia-cuda-toolkit libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev libxv-dev libasound2-dev libsndio-dev libasound2-dev libpulse-dev
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
```
Commands:
编译
```
git clone https://github.com/dijunkun/continuous-desk
git clone https://github.com/kunkundi/crossdesk.git
cd continuous-desk
cd crossdesk
git submodule init
git submodule update
xmake b remote_desk
```
Run:
```
# Windows/MacOS
xmake r remote_desk
# root privileges are required on Linux
./remote_desk
xmake b -vy crossdesk
```
## LICENSE
运行
```
xmake r crossdesk
```
Continuous Desk is licenced under MIT, and some third-party libraries are distributed under their licenses.
### 无 CUDA 环境下的开发支持
对于**未安装 CUDA 环境的 Linux 开发者**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
进入容器,下载工程后执行:
```
export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data
xmake b --root -vy crossdesk
```
对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 CUDA 编译环境:
```
xmake require -vy "cuda 12.6.3"
```
安装完成后执行:
```
xmake require --info "cuda 12.6.3"
```
输出如下:
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入:
```
set CUDA_PATH=path_to_cuda_installdir
```
重新执行:
```
xmake b -vy crossdesk
```
#### 注意
运行时如果客户端状态栏显示 **未连接服务器**,请先在 [CrossDesk 官方网站](https://www.crossdesk.cn/) 安装客户端,以便在环境中安装所需的证书文件。
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
## 关于 Xmake
#### 安装 Xmake
使用 curl
```
curl -fsSL https://xmake.io/shget.text | bash
```
使用 wget
```
wget https://xmake.io/shget.text -O - | bash
```
使用 powershell
```
irm https://xmake.io/psget.text | iex
```
#### 编译选项
```
# 切换编译模式
xmake f -m debug/release
# 可选编译参数
-r :重新构建目标
-v :显示详细的构建日志
-y :自动确认提示
# 示例
xmake b -vy crossdesk
```
#### 运行选项
```
# 使用调试模式运行
xmake r -d crossdesk
```
更多使用方法可参考 [Xmake官方文档](https://xmake.io/guide/quick-start.html) 。
## 自托管服务器
推荐使用Docker部署CrossDesk Server。
```
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
-e CROSSDESK_SERVER_PORT=xxxx \
-e COTURN_PORT=xxxx \
-e MIN_PORT=xxxxx \
-e MAX_PORT=xxxxx \
-v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:v1.0.0
```
上述命令中,用户需注意的参数如下:
- EXTERNAL_IP服务器公网 IP , 对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器地址**
- INTERNAL_IP服务器内网 IP
- CROSSDESK_SERVER_PORT自托管服务使用的端口对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口**
- COTURN_PORT: COTURN 服务使用的端口, 对应 CrossDesk 客户端**自托管服务器配置**中填写的**中继服务端口**
- MIN_PORT/MAX_PORTCOTURN 服务使用的端口范围例如MIN_PORT=50000, MAX_PORT=60000范围可根据客户端数量调整。
- /path/to/your/certs证书文件目录
- /path/to/your/dbCrossDesk Server 设备管理数据库
- /path/to/your/logs日志目录
**注意**
- **/path/to/your/ 是示例路径,请替换为你自己的实际路径。挂载的目录必须事先创建好,否则容器会报错。**
- **服务器需开放端口3478/udp3478/tcpMIN_PORT-MAX_PORT/udpCROSSDESK_SERVER_PORT/tcp。**
## 证书文件
客户端需加载根证书文件,服务端需加载服务器私钥和服务器证书文件。
如果已有SSL证书的用户可以忽略下面的证书生成步骤。
对于无证书的用户,可使用下面的脚本自行生成证书文件:
```
# 创建证书生成脚本
vim generate_certs.sh
```
拷贝到脚本中
```
#!/bin/bash
set -e
# 检查参数
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <SERVER_IP>"
exit 1
fi
SERVER_IP="$1"
# 文件名
ROOT_KEY="crossdesk.cn_root.key"
ROOT_CERT="crossdesk.cn_root.crt"
SERVER_KEY="crossdesk.cn.key"
SERVER_CSR="crossdesk.cn.csr"
SERVER_CERT="crossdesk.cn_bundle.crt"
FULLCHAIN_CERT="crossdesk.cn_fullchain.crt"
# 证书主题
SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP"
# 1. 生成根证书
echo "Generating root private key..."
openssl genrsa -out "$ROOT_KEY" 4096
echo "Generating self-signed root certificate..."
openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ"
# 2. 生成服务器私钥
echo "Generating server private key..."
openssl genrsa -out "$SERVER_KEY" 2048
# 3. 生成服务器 CSR
echo "Generating server CSR..."
openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ"
# 4. 生成临时 OpenSSL 配置文件,加入 SAN
SAN_CONF="san.cnf"
cat > $SAN_CONF <<EOL
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no
[ req_distinguished_name ]
C = CN
ST = Zhejiang
L = Hangzhou
O = CrossDesk
OU = CrossDesk
CN = $SERVER_IP
[ req_ext ]
subjectAltName = IP:$SERVER_IP
EOL
# 5. 用根证书签发服务器证书(包含 SAN
echo "Signing server certificate with root certificate..."
openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \
-out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext
# 6. 生成完整链证书
cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT"
# 7. 清理中间文件
rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT"
echo "Generation complete. Deployment files:"
echo " Client root certificate: $ROOT_CERT"
echo " Server private key: $SERVER_KEY"
echo " Server certificate: $SERVER_CERT"
```
执行
```
chmod +x generate_certs.sh
./generate_certs.sh 服务器公网IP
# 例如 ./generate_certs.sh 111.111.111.111
```
输出如下:
```
Generating root private key...
Generating self-signed root certificate...
Generating server private key...
Generating server CSR...
Signing server certificate with root certificate...
Certificate request self-signature ok
subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx
cleaning up intermediate files...
Generation complete. Deployment files::
Client root certificate:: crossdesk.cn_root.crt
Server private key: crossdesk.cn.key
Server certificate: crossdesk.cn_bundle.crt
```
#### 服务端
**crossdesk.cn.key****crossdesk.cn_bundle.crt** 放置到 **/path/to/your/certs** 目录下。
#### 客户端
1. 点击右上角设置进入设置页面。<br>
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
3. 点击点击**自托管服务器配置**。<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
5. 在**证书文件路径**选择框中找到 **crossdesk.cn_root.crt** 的存放路径,选中 **crossdesk.cn_root.crt**,点击确认。<br><br>
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
# 常见问题
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。

View File

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

312
README_EN.md Normal file
View File

@@ -0,0 +1,312 @@
# CrossDesk
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen.svg)]()
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
[![GitHub stars](https://img.shields.io/github/stars/kunkundi/crossdesk.svg?style=social)]()
[![GitHub forks](https://img.shields.io/github/forks/kunkundi/crossdesk.svg?style=social)]()
[ [中文](README.md) / English ]
![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687)
# Intro
CrossDesk is a lightweight cross-platform remote desktop software.
CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)).
## Usage
Enter the remote desktop ID in the menu bars “Remote ID” field and click “→” to initiate a remote connection.
![usage1](https://github.com/user-attachments/assets/3a4bb59f-c84c-44d2-9a20-11790aac510e)
If the remote desktop requires a connection password, you must enter the correct password on your side to successfully establish the connection.
![password](https://github.com/user-attachments/assets/1beadcce-640d-4f5c-8e77-51917b5294d5)
Before connecting, you can customize configuration options in the settings, such as language and video encoding format.
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
## How to build
Requirements:
- [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/)
Following packages need to be installed on Linux:
```
sudo apt-get install -y software-properties-common git curl unzip build-essential libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev libxtst-dev libasound2-dev libsndio-dev libxcb-shm0-dev libasound2-dev libpulse-dev
```
Build:
```
git clone https://github.com/kunkundi/crossdesk.git
cd crossdesk
git submodule init
git submodule update
xmake b -vy crossdesk
```
Run:
```
xmake r crossdesk
```
#### Development Without CUDA Environment
For **Linux developers who do not have a CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
After entering the container, download the project and run:
```
export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data
xmake b --root -vy crossdesk
```
For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment:
```
xmake require -vy "cuda 12.6.3"
```
After the installation is complete, execute:
```
xmake require --info "cuda 12.6.3"
```
The output will look like this:
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
From the output above, locate the CUDA installation directory — this is the path pointed to by installdir.
Add this path to your system environment variable CUDA_PATH, or set it in the terminal using:
```
set CUDA_PATH=path_to_cuda_installdir:
```
Then re-run:
```
xmake b -vy crossdesk
```
#### Notice
If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment.
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
## About Xmake
#### Installing Xmake
You can install Xmake using one of the following methods:
Using curl:
```
curl -fsSL https://xmake.io/shget.text | bash
```
Using wget:
```
wget https://xmake.io/shget.text -O - | bash
```
Using powershell:
```
irm https://xmake.io/psget.text | iex
```
#### Build Options
```
# Switch build mode
xmake f -m debug/release
# Optional build parameters
-r : Rebuild the target
-v : Show detailed build logs
-y : Automatically confirm prompts
# Example
xmake b -vy crossdesk
```
#### Run Options
```
# Run in debug mode
xmake r -d crossdesk
```
For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) .
## Self-Hosted Server
It is recommended to deploy CrossDesk Server using Docker.
```
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
-e CROSSDESK_SERVER_PORT=xxxx \
-e COTURN_PORT=xxxx \
-e MIN_PORT=xxxxx \
-e MAX_PORT=xxxxx \
-v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:v1.0.0
```
The parameters you need to pay attention to are as follows:
- **EXTERNAL_IP**: The server's public IP, corresponding to the **Server Address** in the CrossDesk client **Self-Hosted Server Configuration**.
- **INTERNAL_IP**: The server's internal IP.
- **CROSSDESK_SERVER_PORT**: The port used by the self-hosted server, corresponding to the **Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
- **COTURN_PORT**: The port used by Coturn, corresponding to the **Relay Server Port** in the CrossDesk client **Self-Hosted Server Configuration**.
- **MIN_PORT** and **MAX_PORT**: The range of ports used by the self-hosted server, corresponding to the **Minimum Port** and **Maximum Port** in the CrossDesk client **Self-Hosted Server Configuration**. Example: 50000-60000. It depends on the number of devices connected to the server.
- **/path/to/your/certs**: Directory for certificate files.
- **/path/to/your/db**: CrossDesk Server device management database.
- **/path/to/your/logs**: Log directory.
**Note**:
- **/path/to/your/ is an example path; please replace it with your actual path. The mounted directories must be created in advance, otherwise the container will fail.**
- **The server must open the following ports: 3478/udp, 3478/tcp, 30000-60000/udp, CROSSDESK_SERVER_PORT/tcp.**
## Certificate Files
The client needs to load the root certificate, and the server needs to load the server private key and server certificate.
If you already have an SSL certificate, you can skip the following certificate generation steps.
For users without a certificate, you can use the script below to generate the certificate files:
```
# Create certificate generation script
vim generate_certs.sh
```
Copy the following into the script:
```
#!/bin/bash
set -e
# Check arguments
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <SERVER_IP>"
exit 1
fi
SERVER_IP="$1"
# Filenames
ROOT_KEY="crossdesk.cn_root.key"
ROOT_CERT="crossdesk.cn_root.crt"
SERVER_KEY="crossdesk.cn.key"
SERVER_CSR="crossdesk.cn.csr"
SERVER_CERT="crossdesk.cn_bundle.crt"
FULLCHAIN_CERT="crossdesk.cn_fullchain.crt"
# Certificate subject
SUBJ="/C=CN/ST=Zhejiang/L=Hangzhou/O=CrossDesk/OU=CrossDesk/CN=$SERVER_IP"
# 1. Generate root certificate
echo "Generating root private key..."
openssl genrsa -out "$ROOT_KEY" 4096
echo "Generating self-signed root certificate..."
openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_CERT" -subj "$SUBJ"
# 2. Generate server private key
echo "Generating server private key..."
openssl genrsa -out "$SERVER_KEY" 2048
# 3. Generate server CSR
echo "Generating server CSR..."
openssl req -new -key "$SERVER_KEY" -out "$SERVER_CSR" -subj "$SUBJ"
# 4. Create temporary OpenSSL config file with SAN
SAN_CONF="san.cnf"
cat > $SAN_CONF <<EOL
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no
[ req_distinguished_name ]
C = CN
ST = Zhejiang
L = Hangzhou
O = CrossDesk
OU = CrossDesk
CN = $SERVER_IP
[ req_ext ]
subjectAltName = IP:$SERVER_IP
EOL
# 5. Sign server certificate with root certificate (including SAN)
echo "Signing server certificate with root certificate..."
openssl x509 -req -in "$SERVER_CSR" -CA "$ROOT_CERT" -CAkey "$ROOT_KEY" -CAcreateserial \
-out "$SERVER_CERT" -days 3650 -sha256 -extfile "$SAN_CONF" -extensions req_ext
# 6. Generate full chain certificate
cat "$SERVER_CERT" "$ROOT_CERT" > "$FULLCHAIN_CERT"
# 7. Clean up intermediate files
rm -f "$ROOT_CERT.srl" "$SAN_CONF" "$ROOT_KEY" "$SERVER_CSR" "FULLCHAIN_CERT"
echo "Generation complete. Deployment files:"
echo " Client root certificate: $ROOT_CERT"
echo " Server private key: $SERVER_KEY"
echo " Server certificate: $SERVER_CERT"
```
Execute:
```
chmod +x generate_certs.sh
./generate_certs.sh EXTERNAL_IP
# example ./generate_certs.sh 111.111.111.111
```
Expected output:
```
Generating root private key...
Generating self-signed root certificate...
Generating server private key...
Generating server CSR...
Signing server certificate with root certificate...
Certificate request self-signature ok
subject=C = CN, ST = Zhejiang, L = Hangzhou, O = CrossDesk, OU = CrossDesk, CN = xxx.xxx.xxx.xxx
cleaning up intermediate files...
Generation complete. Deployment files::
Client root certificate:: crossdesk.cn_root.crt
Server private key: crossdesk.cn.key
Server certificate: crossdesk.cn_bundle.crt
```
#### Server Side
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
#### Client Side
1. Click the settings icon in the top-right corner to enter the settings page.<br>
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
2. Click **Self-Hosted Server Configuration**.<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
3. In the **Certificate File Path** selection, locate and select the **crossdesk.cn_root.crt** file.<br><br>
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
# FAQ
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .

View File

@@ -1,19 +0,0 @@
[signal server]
ip = 150.158.81.30
port = 9099
[stun server]
ip = 150.158.81.30
port = 3478
[turn server]
ip = 150.158.81.30
port = 3478
username = dijunkun
password = dijunkunpw
[hardware acceleration]
turn_on = false
[av1 encoding]
turn_on = true

33
docs/FAQ.md Normal file
View File

@@ -0,0 +1,33 @@
# 常见问题FAQ
欢迎来到 **CrossDesk 常见问题** 页面!
这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。
---
### Q1. 对等连接失败
**A:**
打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。
<img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" />
由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8)
### Q2. Windows 无 CUDA 环境下编译
**A:**
运行下面的命令安装 CUDA 编译环境。
```
xmake require -vy "cuda 12.6.3"
```
安装完成后执行
```
xmake require --info "cuda 12.6.3"
```
输出如下
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir重新执行 xmake b -vy crossdesk 即可。
[Issue #6](https://github.com/kunkundi/crossdesk/issues/6)
---

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
icons/macos/crossdesk.icns Normal file

Binary file not shown.

BIN
icons/windows/crossdesk.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

120
scripts/linux/pkg_amd64.sh Normal file
View File

@@ -0,0 +1,120 @@
#!/bin/bash
set -e
PKG_NAME="crossdesk"
APP_NAME="CrossDesk"
APP_VERSION="$1"
ARCHITECTURE="amd64"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client."
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
DEBIAN_DIR="$DEB_DIR/DEBIAN"
BIN_DIR="$DEB_DIR/usr/bin"
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
rm -rf "$DEB_DIR"
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR"
cp build/linux/x86_64/release/crossdesk "$BIN_DIR/$PKG_NAME"
chmod +x "$BIN_DIR/$PKG_NAME"
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
for size in 16 24 32 48 64 96 128 256; do
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
cp "icons/linux/crossdesk_${size}x${size}.png" \
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
done
cat > "$DEBIAN_DIR/control" << EOF
Package: $PKG_NAME
Version: $APP_VERSION
Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER
Description: $DESCRIPTION
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
libsndio7.0, libxcb-shm0, libpulse0
Recommends: nvidia-cuda-toolkit
Priority: optional
Section: utils
EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
[Desktop Entry]
Version=$APP_VERSION
Name=$APP_NAME
Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME
Icon=$PKG_NAME
Terminal=false
Type=Application
Categories=Utility;
EOF
cat > "$DEBIAN_DIR/postrm" << EOF
#!/bin/bash
set -e
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
rm -f /usr/bin/$PKG_NAME || true
rm -f /usr/bin/$APP_NAME || true
rm -f /usr/share/applications/$PKG_NAME.desktop || true
rm -rf /opt/$PKG_NAME/certs || true
for size in 16 24 32 48 64 96 128 256; do
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
done
fi
exit 0
EOF
chmod +x "$DEBIAN_DIR/postrm"
cat > "$DEBIAN_DIR/postinst" << 'EOF'
#!/bin/bash
set -e
CERT_SRC="/opt/crossdesk/certs"
CERT_FILE="crossdesk.cn_root.crt"
for user_home in /home/*; do
[ -d "$user_home" ] || continue
username=$(basename "$user_home")
config_dir="$user_home/.config/CrossDesk/certs"
target="$config_dir/$CERT_FILE"
if [ ! -f "$target" ]; then
mkdir -p "$config_dir" || true
cp "$CERT_SRC/$CERT_FILE" "$target" || true
chown -R "$username:$username" "$user_home/.config/CrossDesk" || true
echo "✔ Installed cert for $username at $target"
fi
done
if [ -d "/root" ]; then
config_dir="/root/.config/CrossDesk/certs"
mkdir -p "$config_dir" || true
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true
chown -R root:root /root/.config/CrossDesk || true
fi
exit 0
EOF
chmod +x "$DEBIAN_DIR/postinst"
dpkg-deb --build "$DEB_DIR"
OUTPUT_FILE="${PKG_NAME}-linux-${ARCHITECTURE}-${APP_VERSION}.deb"
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
rm -rf "$DEB_DIR"
echo "✅ Deb package created: $OUTPUT_FILE"

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

@@ -0,0 +1,120 @@
#!/bin/bash
set -e
PKG_NAME="crossdesk"
APP_NAME="CrossDesk"
APP_VERSION="$1"
ARCHITECTURE="arm64"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client."
DEB_DIR="${PKG_NAME}-${APP_VERSION}"
DEBIAN_DIR="$DEB_DIR/DEBIAN"
BIN_DIR="$DEB_DIR/usr/bin"
CERT_SRC_DIR="$DEB_DIR/opt/$PKG_NAME/certs"
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
rm -rf "$DEB_DIR"
mkdir -p "$DEBIAN_DIR" "$BIN_DIR" "$CERT_SRC_DIR" "$DESKTOP_DIR"
cp build/linux/arm64/release/crossdesk "$BIN_DIR"
chmod +x "$BIN_DIR/$PKG_NAME"
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
cp certs/crossdesk.cn_root.crt "$CERT_SRC_DIR/crossdesk.cn_root.crt"
for size in 16 24 32 48 64 96 128 256; do
mkdir -p "$ICON_BASE_DIR/${size}x${size}/apps"
cp "icons/linux/crossdesk_${size}x${size}.png" \
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
done
cat > "$DEBIAN_DIR/control" << EOF
Package: $PKG_NAME
Version: $APP_VERSION
Architecture: $ARCHITECTURE
Maintainer: $MAINTAINER
Description: $DESCRIPTION
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxcb1,
libxcb-randr0, libxcb-xtest0, libxcb-xinerama0, libxcb-shape0,
libxcb-xkb1, libxcb-xfixes0, libxv1, libxtst6, libasound2,
libsndio7.0, libxcb-shm0, libpulse0
Priority: optional
Section: utils
EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" << EOF
[Desktop Entry]
Version=$APP_VERSION
Name=$APP_NAME
Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME
Icon=$PKG_NAME
Terminal=false
Type=Application
Categories=Utility;
EOF
cat > "$DEBIAN_DIR/postrm" << EOF
#!/bin/bash
set -e
if [ "\$1" = "remove" ] || [ "\$1" = "purge" ]; then
rm -f /usr/bin/$PKG_NAME || true
rm -f /usr/bin/$APP_NAME || true
rm -f /usr/share/applications/$PKG_NAME.desktop || true
rm -rf /opt/$PKG_NAME/certs || true
for size in 16 24 32 48 64 96 128 256; do
rm -f /usr/share/icons/hicolor/\${size}x\${size}/apps/$PKG_NAME.png || true
done
fi
exit 0
EOF
chmod +x "$DEBIAN_DIR/postrm"
cat > "$DEBIAN_DIR/postinst" << 'EOF'
#!/bin/bash
set -e
CERT_SRC="/opt/crossdesk/certs"
CERT_FILE="crossdesk.cn_root.crt"
for user_home in /home/*; do
[ -d "$user_home" ] || continue
username=$(basename "$user_home")
config_dir="$user_home/.config/CrossDesk/certs"
target="$config_dir/$CERT_FILE"
if [ ! -f "$target" ]; then
mkdir -p "$config_dir" || true
cp "$CERT_SRC/$CERT_FILE" "$target" || true
chown -R "$username:$username" "$user_home/.config/CrossDesk" || true
echo "✔ Installed cert for $username at $target"
fi
done
if [ -d "/root" ]; then
config_dir="/root/.config/CrossDesk/certs"
mkdir -p "$config_dir" || true
cp "$CERT_SRC/$CERT_FILE" "$config_dir/$CERT_FILE" || true
chown -R root:root /root/.config/CrossDesk || true
fi
exit 0
EOF
chmod +x "$DEBIAN_DIR/postinst"
dpkg-deb --build "$DEB_DIR"
OUTPUT_FILE="crossdesk-linux-arm64-$APP_VERSION.deb"
mv "$DEB_DIR.deb" "$OUTPUT_FILE"
rm -rf "$DEB_DIR"
echo "✅ Deb package created: $OUTPUT_FILE"

125
scripts/macosx/pkg_arm64.sh Normal file
View File

@@ -0,0 +1,125 @@
#!/bin/bash
set -e
APP_NAME="crossdesk"
APP_NAME_UPPER="CrossDesk"
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
APP_VERSION="$1"
PLATFORM="macos"
ARCH="arm64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/macos/crossdesk.icns"
MACOS_MIN_VERSION="10.12"
CERTS_SOURCE="certs"
CERT_NAME="crossdesk.cn_root.crt"
APP_BUNDLE="${APP_NAME_UPPER}.app"
CONTENTS_DIR="${APP_BUNDLE}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
VOL_NAME="Install ${APP_NAME_UPPER}"
echo "delete old files"
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
mkdir -p build_pkg_temp
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
if [ -f "${ICON_PATH}" ]; then
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
else
ICON_KEY=""
fi
echo "generate Info.plist"
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleIdentifier</key>
<string>${IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${APP_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${APP_VERSION}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
${ICON_KEY}
<key>LSMinimumSystemVersion</key>
<string>${MACOS_MIN_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>应用需要访问摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>应用需要访问麦克风</string>
<key>NSAppleEventsUsageDescription</key>
<string>应用需要发送 Apple 事件</string>
<key>NSScreenCaptureUsageDescription</key>
<string>应用需要录屏权限以捕获屏幕内容</string>
</dict>
</plist>
EOF
echo ".app created successfully."
echo "building pkg..."
pkgbuild \
--identifier "${IDENTIFIER}" \
--version "${APP_VERSION}" \
--install-location "/Applications" \
--component "${APP_BUNDLE}" \
build_pkg_temp/${APP_NAME}-component.pkg
mkdir -p scripts
cat > scripts/postinstall <<'EOF'
#!/bin/bash
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
mkdir -p "$DEST"
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
exit 0
EOF
chmod +x scripts/postinstall
pkgbuild \
--root "${CERTS_SOURCE}" \
--identifier "${IDENTIFIER}.certs" \
--version "${APP_VERSION}" \
--install-location "/Library/Application Support/CrossDesk/certs" \
--scripts scripts \
build_pkg_temp/${APP_NAME}-certs.pkg
productbuild \
--package build_pkg_temp/${APP_NAME}-component.pkg \
--package build_pkg_temp/${APP_NAME}-certs.pkg \
"${PKG_NAME}"
echo "PKG package created: ${PKG_NAME}"
rm -rf build_pkg_temp scripts ${APP_BUNDLE}
echo "PKG package created successfully."
echo "package ${APP_BUNDLE}"
echo "installer ${PKG_NAME}"

125
scripts/macosx/pkg_x64.sh Normal file
View File

@@ -0,0 +1,125 @@
#!/bin/bash
set -e
APP_NAME="crossdesk"
APP_NAME_UPPER="CrossDesk"
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
APP_VERSION="$1"
PLATFORM="macos"
ARCH="x64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/macos/crossdesk.icns"
MACOS_MIN_VERSION="10.12"
CERTS_SOURCE="certs"
CERT_NAME="crossdesk.cn_root.crt"
APP_BUNDLE="${APP_NAME_UPPER}.app"
CONTENTS_DIR="${APP_BUNDLE}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
VOL_NAME="Install ${APP_NAME_UPPER}"
echo "delete old files"
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
mkdir -p build_pkg_temp
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
if [ -f "${ICON_PATH}" ]; then
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
else
ICON_KEY=""
fi
echo "generate Info.plist"
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleIdentifier</key>
<string>${IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${APP_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${APP_VERSION}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
${ICON_KEY}
<key>LSMinimumSystemVersion</key>
<string>${MACOS_MIN_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>应用需要访问摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>应用需要访问麦克风</string>
<key>NSAppleEventsUsageDescription</key>
<string>应用需要发送 Apple 事件</string>
<key>NSScreenCaptureUsageDescription</key>
<string>应用需要录屏权限以捕获屏幕内容</string>
</dict>
</plist>
EOF
echo ".app created successfully."
echo "building pkg..."
pkgbuild \
--identifier "${IDENTIFIER}" \
--version "${APP_VERSION}" \
--install-location "/Applications" \
--component "${APP_BUNDLE}" \
build_pkg_temp/${APP_NAME}-component.pkg
mkdir -p scripts
cat > scripts/postinstall <<'EOF'
#!/bin/bash
USER_HOME=$( /usr/bin/stat -f "%Su" /dev/console )
HOME_DIR=$( /usr/bin/dscl . -read /Users/$USER_HOME NFSHomeDirectory | awk '{print $2}' )
DEST="$HOME_DIR/Library/Application Support/CrossDesk/certs"
mkdir -p "$DEST"
cp -R "/Library/Application Support/CrossDesk/certs/"* "$DEST/"
exit 0
EOF
chmod +x scripts/postinstall
pkgbuild \
--root "${CERTS_SOURCE}" \
--identifier "${IDENTIFIER}.certs" \
--version "${APP_VERSION}" \
--install-location "/Library/Application Support/CrossDesk/certs" \
--scripts scripts \
build_pkg_temp/${APP_NAME}-certs.pkg
productbuild \
--package build_pkg_temp/${APP_NAME}-component.pkg \
--package build_pkg_temp/${APP_NAME}-certs.pkg \
"${PKG_NAME}"
echo "PKG package created: ${PKG_NAME}"
rm -rf build_pkg_temp scripts ${APP_BUNDLE}
echo "PKG package created successfully."
echo "package ${APP_BUNDLE}"
echo "installer ${PKG_NAME}"

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- 应用程序标识 -->
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="CrossDesk"
type="win32" />
<!-- 描述信息 -->
<description>CrossDesk Application</description>
<!-- 权限:要求管理员运行 -->
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<!-- DPI 感知设置:支持高分屏 -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- Windows Vista/7 风格 DPI 感知 -->
<dpiAware>true/pm</dpiAware>
<!-- Windows 10/11 高级 DPI 感知 -->
<dpiAwareness>PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<!-- Windows 兼容性声明 -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 支持 Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- 支持 Windows 11向下兼容 Win10 GUID -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

Binary file not shown.

View File

@@ -0,0 +1,164 @@
; Set search path
!addincludedir "${__FILEDIR__}"
; Installer initial constants
!define PRODUCT_NAME "CrossDesk"
!define PRODUCT_VERSION "${VERSION}"
!define PRODUCT_PUBLISHER "CrossDesk"
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
!define APP_NAME "CrossDesk"
!define UNINSTALL_REG_KEY "CrossDesk"
; Installer icon path
!define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico"
; Certificate path
!define CERT_FILE "${__FILEDIR__}\..\..\certs\crossdesk.cn_root.crt"
; Compression settings
SetCompressor /FINAL lzma
; Request admin privileges (needed to write HKLM)
RequestExecutionLevel admin
; ------ MUI Modern UI Definition ------
!include "MUI.nsh"
!define MUI_ABORTWARNING
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
; Add run-after-install option
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}"
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
; ------ End of MUI Definition ------
; Include LogicLib for process handling
!include "LogicLib.nsh"
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "crossdesk-win-x64-${PRODUCT_VERSION}.exe"
InstallDir "$PROGRAMFILES\CrossDesk"
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" "InstallDir"
ShowInstDetails show
Section "MainSection"
; Check if CrossDesk is running
StrCpy $1 "crossdesk.exe"
nsProcess::_FindProcess "$1"
Pop $R0
${If} $R0 = 0 ;
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and continue the installation?" IDYES closeApp IDNO cancelInstall
${Else}
Goto installApp
${EndIf}
closeApp:
nsProcess::_KillProcess "$1"
Pop $R0
Sleep 500
Goto installApp
cancelInstall:
SetDetailsPrint both
MessageBox MB_ICONEXCLAMATION|MB_OK "Installation has been aborted."
Abort
installApp:
SetOutPath "$INSTDIR"
SetOverwrite ifnewer
; Main application executable path
File /oname=crossdesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
; Copy icon file to installation directory
File "${MUI_ICON}"
; Write uninstall information
WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\crossdesk.ico"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "InstallDir" "$INSTDIR"
SectionEnd
; After installation
Section -Post
ExecWait '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\mt.exe" -manifest "$INSTDIR\crossdesk.manifest" -outputresource:"$INSTDIR\crossdesk.exe";1'
SectionEnd
Section "Cert"
SetOutPath "$APPDATA\CrossDesk\certs"
File /r "${CERT_FILE}"
SectionEnd
Section -AdditionalIcons
; Desktop shortcut
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
; Start menu shortcut
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\crossdesk.exe" "" "$INSTDIR\crossdesk.ico"
SectionEnd
Section "Uninstall"
; Check if CrossDesk is running
StrCpy $1 "crossdesk.exe"
nsProcess::_FindProcess "$1"
Pop $R0
${If} $R0 = 0
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and uninstall?" IDYES closeApp IDNO cancelUninstall
${Else}
Goto uninstallApp
${EndIf}
closeApp:
nsProcess::_KillProcess "$1"
Pop $R0
Sleep 500
Goto uninstallApp
cancelUninstall:
SetDetailsPrint both
MessageBox MB_ICONEXCLAMATION|MB_OK "Uninstallation has been aborted."
Abort
uninstallApp:
; Delete main executable and uninstaller
Delete "$INSTDIR\crossdesk.exe"
Delete "$INSTDIR\uninstall.exe"
; Recursively delete installation directory
RMDir /r "$INSTDIR"
; Delete desktop and start menu shortcuts
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
; Delete registry uninstall entry
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
; Delete remembered install dir
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
; Recursively delete CrossDesk folder in user AppData
RMDir /r "$APPDATA\CrossDesk"
RMDir /r "$LOCALAPPDATA\CrossDesk"
SectionEnd
; ------ Functions ------
Function LaunchApp
Exec "$INSTDIR\crossdesk.exe"
FunctionEnd

View File

@@ -1,5 +1,5 @@
#ifdef _WIN32
#ifdef REMOTE_DESK_DEBUG
#ifdef DESK_PORT_DEBUG
#pragma comment(linker, "/subsystem:\"console\"")
#else
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
@@ -9,10 +9,8 @@
#include "rd_log.h"
#include "render.h"
int main(int argc, char *argv[]) {
LOG_INFO("Remote desk");
Render render;
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
crossdesk::Render render;
render.Run();
return 0;

46
src/common/display_info.h Normal file
View File

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

View File

@@ -19,19 +19,21 @@
#include <unistd.h>
#endif
namespace crossdesk {
std::string GetMac() {
char mac_addr[16];
int len = 0;
#ifdef _WIN32
IP_ADAPTER_INFO adapterInfo[16];
DWORD bufferSize = sizeof(adapterInfo);
DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize);
if (result == ERROR_SUCCESS) {
PIP_ADAPTER_INFO adapter = adapterInfo;
while (adapter) {
for (UINT i = 0; i < adapter->AddressLength; i++) {
len += sprintf(mac_addr + len, "%.2X", adapter->Address[i]);
len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
adapter->Address[i]);
}
break;
}
@@ -100,3 +102,27 @@ std::string GetMac() {
#endif
return mac_addr;
}
std::string GetHostName() {
char hostname[256];
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed." << std::endl;
return "";
}
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
WSACleanup();
return "";
}
WSACleanup();
#else
if (gethostname(hostname, sizeof(hostname)) == -1) {
LOG_ERROR("gethostname failed");
return "";
}
#endif
return hostname;
}
} // namespace crossdesk

View File

@@ -9,6 +9,10 @@
#include <iostream>
std::string GetMac();
namespace crossdesk {
std::string GetMac();
std::string GetHostName();
} // namespace crossdesk
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +1,269 @@
#include "config_center.h"
ConfigCenter::ConfigCenter() {}
namespace crossdesk {
ConfigCenter::ConfigCenter(const std::string& config_path,
const std::string& cert_file_path)
: config_path_(config_path),
cert_file_path_(cert_file_path),
cert_file_path_default_(cert_file_path) {
ini_.SetUnicode(true);
Load();
}
ConfigCenter::~ConfigCenter() {}
int ConfigCenter::Load() {
SI_Error rc = ini_.LoadFile(config_path_.c_str());
if (rc < 0) {
Save();
return -1;
}
language_ = static_cast<LANGUAGE>(
ini_.GetLongValue(section_, "language", static_cast<long>(language_)));
video_quality_ = static_cast<VIDEO_QUALITY>(ini_.GetLongValue(
section_, "video_quality", static_cast<long>(video_quality_)));
video_frame_rate_ = static_cast<VIDEO_FRAME_RATE>(ini_.GetLongValue(
section_, "video_frame_rate", static_cast<long>(video_frame_rate_)));
video_encode_format_ = static_cast<VIDEO_ENCODE_FORMAT>(
ini_.GetLongValue(section_, "video_encode_format",
static_cast<long>(video_encode_format_)));
hardware_video_codec_ = ini_.GetBoolValue(section_, "hardware_video_codec",
hardware_video_codec_);
enable_turn_ = ini_.GetBoolValue(section_, "enable_turn", enable_turn_);
enable_srtp_ = ini_.GetBoolValue(section_, "enable_srtp", enable_srtp_);
signal_server_host_ = ini_.GetValue(section_, "signal_server_host",
signal_server_host_.c_str());
signal_server_port_ = static_cast<int>(
ini_.GetLongValue(section_, "signal_server_port", signal_server_port_));
coturn_server_port_ = static_cast<int>(
ini_.GetLongValue(section_, "coturn_server_port", coturn_server_port_));
cert_file_path_ =
ini_.GetValue(section_, "cert_file_path", cert_file_path_.c_str());
enable_self_hosted_ =
ini_.GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
enable_minimize_to_tray_ = ini_.GetBoolValue(
section_, "enable_minimize_to_tray", enable_minimize_to_tray_);
return 0;
}
int ConfigCenter::Save() {
ini_.SetLongValue(section_, "language", static_cast<long>(language_));
ini_.SetLongValue(section_, "video_quality",
static_cast<long>(video_quality_));
ini_.SetLongValue(section_, "video_frame_rate",
static_cast<long>(video_frame_rate_));
ini_.SetLongValue(section_, "video_encode_format",
static_cast<long>(video_encode_format_));
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
ini_.SetLongValue(section_, "signal_server_port",
static_cast<long>(signal_server_port_));
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
ini_.SetBoolValue(section_, "enable_minimize_to_tray",
enable_minimize_to_tray_);
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
// setters
int ConfigCenter::SetLanguage(LANGUAGE language) {
language_ = language;
ini_.SetLongValue(section_, "language", static_cast<long>(language_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) {
video_quality_ = video_quality;
ini_.SetLongValue(section_, "video_quality",
static_cast<long>(video_quality_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate) {
video_frame_rate_ = video_frame_rate;
ini_.SetLongValue(section_, "video_frame_rate",
static_cast<long>(video_frame_rate_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetVideoEncodeFormat(
VIDEO_ENCODE_FORMAT video_encode_format) {
video_encode_format_ = video_encode_format;
ini_.SetLongValue(section_, "video_encode_format",
static_cast<long>(video_encode_format_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetHardwareVideoCodec(bool hardware_video_codec) {
hardware_video_codec_ = hardware_video_codec;
ini_.SetBoolValue(section_, "hardware_video_codec", hardware_video_codec_);
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetTurn(bool enable_turn) {
enable_turn_ = enable_turn;
ini_.SetBoolValue(section_, "enable_turn", enable_turn_);
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; }
int ConfigCenter::SetSrtp(bool enable_srtp) {
enable_srtp_ = enable_srtp;
ini_.SetBoolValue(section_, "enable_srtp", enable_srtp_);
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() {
int ConfigCenter::SetServerHost(const std::string& signal_server_host) {
signal_server_host_ = signal_server_host;
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetServerPort(int signal_server_port) {
signal_server_port_ = signal_server_port;
ini_.SetLongValue(section_, "signal_server_port",
static_cast<long>(signal_server_port_));
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetCoturnServerPort(int coturn_server_port) {
coturn_server_port_ = coturn_server_port;
SI_Error rc = ini_.SetLongValue(section_, "coturn_server_port",
static_cast<long>(coturn_server_port_));
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) {
cert_file_path_ = cert_file_path;
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
enable_self_hosted_ = enable_self_hosted;
SI_Error rc = ini_.SaveFile(config_path_.c_str());
if (rc < 0) {
return -1;
}
return 0;
}
int ConfigCenter::SetMinimizeToTray(bool enable_minimize_to_tray) {
enable_minimize_to_tray_ = enable_minimize_to_tray;
return 0;
}
// getters
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() const { return language_; }
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() const {
return video_quality_;
}
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() {
ConfigCenter::VIDEO_FRAME_RATE ConfigCenter::GetVideoFrameRate() const {
return video_frame_rate_;
}
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() const {
return video_encode_format_;
}
bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; }
bool ConfigCenter::IsHardwareVideoCodec() const {
return hardware_video_codec_;
}
bool ConfigCenter::IsEnableTurn() { return enable_turn_; }
bool ConfigCenter::IsEnableTurn() const { return enable_turn_; }
bool ConfigCenter::IsEnableSrtp() const { return enable_srtp_; }
std::string ConfigCenter::GetSignalServerHost() const {
return signal_server_host_;
}
int ConfigCenter::GetSignalServerPort() const { return signal_server_port_; }
int ConfigCenter::GetCoturnServerPort() const { return coturn_server_port_; }
std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; }
std::string ConfigCenter::GetDefaultServerHost() const {
return signal_server_host_default_;
}
int ConfigCenter::GetDefaultSignalServerPort() const {
return server_port_default_;
}
int ConfigCenter::GetDefaultCoturnServerPort() const {
return coturn_server_port_default_;
}
std::string ConfigCenter::GetDefaultCertFilePath() const {
return cert_file_path_default_;
}
bool ConfigCenter::IsSelfHosted() const { return enable_self_hosted_; }
bool ConfigCenter::IsMinimizeToTray() const { return enable_minimize_to_tray_; }
} // namespace crossdesk

View File

@@ -7,37 +7,85 @@
#ifndef _CONFIG_CENTER_H_
#define _CONFIG_CENTER_H_
#include <string>
#include "SimpleIni.h"
namespace crossdesk {
class ConfigCenter {
public:
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 };
enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 };
enum class VIDEO_ENCODE_FORMAT { AV1 = 0, H264 = 1 };
enum class VIDEO_FRAME_RATE { FPS_30 = 0, FPS_60 = 1 };
enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 };
public:
ConfigCenter();
explicit ConfigCenter(
const std::string& config_path = "config.ini",
const std::string& cert_file_path = "crossdesk.cn_root.crt");
~ConfigCenter();
public:
// write config
int SetLanguage(LANGUAGE language);
int SetVideoQuality(VIDEO_QUALITY video_quality);
int SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate);
int SetVideoEncodeFormat(VIDEO_ENCODE_FORMAT video_encode_format);
int SetHardwareVideoCodec(bool hardware_video_codec);
int SetTurn(bool enable_turn);
int SetSrtp(bool enable_srtp);
int SetServerHost(const std::string& signal_server_host);
int SetServerPort(int signal_server_port);
int SetCoturnServerPort(int coturn_server_port);
int SetCertFilePath(const std::string& cert_file_path);
int SetSelfHosted(bool enable_self_hosted);
int SetMinimizeToTray(bool enable_minimize_to_tray);
public:
LANGUAGE GetLanguage();
VIDEO_QUALITY GetVideoQuality();
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat();
bool IsHardwareVideoCodec();
bool IsEnableTurn();
// read config
LANGUAGE GetLanguage() const;
VIDEO_QUALITY GetVideoQuality() const;
VIDEO_FRAME_RATE GetVideoFrameRate() const;
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat() const;
bool IsHardwareVideoCodec() const;
bool IsEnableTurn() const;
bool IsEnableSrtp() const;
std::string GetSignalServerHost() const;
int GetSignalServerPort() const;
int GetCoturnServerPort() const;
std::string GetCertFilePath() const;
std::string GetDefaultServerHost() const;
int GetDefaultSignalServerPort() const;
int GetDefaultCoturnServerPort() const;
std::string GetDefaultCertFilePath() const;
bool IsSelfHosted() const;
bool IsMinimizeToTray() const;
int Load();
int Save();
private:
// Default value should be same with parameters in localization.h
std::string config_path_;
std::string cert_file_path_;
CSimpleIniA ini_;
const char* section_ = "Settings";
LANGUAGE language_ = LANGUAGE::CHINESE;
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1;
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
bool hardware_video_codec_ = false;
bool enable_turn_ = false;
bool enable_srtp_ = false;
std::string signal_server_host_ = "api.crossdesk.cn";
std::string signal_server_host_default_ = "api.crossdesk.cn";
int signal_server_port_ = 9099;
int server_port_default_ = 9099;
int coturn_server_port_ = 3478;
int coturn_server_port_default_ = 3478;
std::string cert_file_path_default_ = "";
bool enable_self_hosted_ = false;
bool enable_minimize_to_tray_ = false;
};
} // namespace crossdesk
#endif

View File

@@ -9,12 +9,33 @@
#include <stdio.h>
typedef enum { mouse = 0, keyboard, audio_capture } ControlType;
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
#include "display_info.h"
namespace crossdesk {
typedef enum {
mouse = 0,
keyboard,
audio_capture,
host_infomation,
display_id,
} ControlType;
typedef enum {
move = 0,
left_down,
left_up,
right_down,
right_up,
middle_down,
middle_up,
wheel_vertical,
wheel_horizontal
} MouseFlag;
typedef enum { key_down = 0, key_up } KeyFlag;
typedef struct {
size_t x;
size_t y;
float x;
float y;
int s;
MouseFlag flag;
} Mouse;
@@ -23,23 +44,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();
};
} // namespace crossdesk
#endif

View File

@@ -8,8 +8,11 @@
#define _DEVICE_CONTROLLER_FACTORY_H_
#include "device_controller.h"
#include "keyboard_capturer.h"
#include "mouse_controller.h"
namespace crossdesk {
class DeviceControllerFactory {
public:
enum Device { Mouse = 0, Keyboard };
@@ -23,11 +26,11 @@ class DeviceControllerFactory {
case Mouse:
return new MouseController();
case Keyboard:
return nullptr;
return new KeyboardCapturer();
default:
return nullptr;
}
}
};
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,72 @@
#include "keyboard_capturer.h"
#include "keyboard_converter.h"
#include "rd_log.h"
namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr;
static int KeyboardEventHandler(Display* display, XEvent* event) {
if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) {
KeySym keySym = XKeycodeToKeysym(display, event->xkey.keycode, 0);
int key_code = XKeysymToKeycode(display, keySym);
bool is_key_down = (event->xkey.type == KeyPress);
if (g_on_key_action) {
g_on_key_action(key_code, is_key_down, g_user_ptr);
}
}
return 0;
}
KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) {
display_ = XOpenDisplay(nullptr);
if (!display_) {
LOG_ERROR("Failed to open X display.");
}
}
KeyboardCapturer::~KeyboardCapturer() {
if (display_) {
XCloseDisplay(display_);
}
}
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
g_on_key_action = on_key_action;
g_user_ptr = user_ptr;
XSelectInput(display_, DefaultRootWindow(display_),
KeyPressMask | KeyReleaseMask);
while (running_) {
XEvent event;
XNextEvent(display_, &event);
KeyboardEventHandler(display_, &event);
}
return 0;
}
int KeyboardCapturer::Unhook() {
running_ = false;
return 0;
}
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
if (!display_) {
LOG_ERROR("Display not initialized.");
return -1;
}
if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) {
int x11_key_code = vkCodeToX11KeySym[key_code];
KeyCode keycode = XKeysymToKeycode(display_, x11_key_code);
XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime);
XFlush(display_);
}
return 0;
}
} // namespace crossdesk

View File

@@ -0,0 +1,34 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-22
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _KEYBOARD_CAPTURER_H_
#define _KEYBOARD_CAPTURER_H_
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include "device_controller.h"
namespace crossdesk {
class KeyboardCapturer : public DeviceController {
public:
KeyboardCapturer();
virtual ~KeyboardCapturer();
public:
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
private:
Display* display_;
Window root_;
bool running_;
};
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,170 @@
#include "keyboard_capturer.h"
#include "keyboard_converter.h"
#include "rd_log.h"
namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr;
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
CGEventRef event, void* userInfo) {
KeyboardCapturer* keyboard_capturer = (KeyboardCapturer*)userInfo;
if (!keyboard_capturer) {
LOG_ERROR("keyboard_capturer is nullptr");
return event;
}
int vk_code = 0;
if (type == kCGEventKeyDown || type == kCGEventKeyUp) {
CGKeyCode key_code = static_cast<CGKeyCode>(
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
if (CGKeyCodeToVkCode.find(key_code) != CGKeyCodeToVkCode.end()) {
g_on_key_action(CGKeyCodeToVkCode[key_code], type == kCGEventKeyDown,
g_user_ptr);
}
} else if (type == kCGEventFlagsChanged) {
CGEventFlags current_flags = CGEventGetFlags(event);
CGKeyCode key_code = static_cast<CGKeyCode>(
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
// caps lock
bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0;
if (caps_lock_state != keyboard_capturer->caps_lock_flag_) {
keyboard_capturer->caps_lock_flag_ = caps_lock_state;
if (keyboard_capturer->caps_lock_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
}
// shift
bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0;
if (shift_state != keyboard_capturer->shift_flag_) {
keyboard_capturer->shift_flag_ = shift_state;
if (keyboard_capturer->shift_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
}
// control
bool control_state = (current_flags & kCGEventFlagMaskControl) != 0;
if (control_state != keyboard_capturer->control_flag_) {
keyboard_capturer->control_flag_ = control_state;
if (keyboard_capturer->control_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
}
// option
bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0;
if (option_state != keyboard_capturer->option_flag_) {
keyboard_capturer->option_flag_ = option_state;
if (keyboard_capturer->option_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
}
// command
bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0;
if (command_state != keyboard_capturer->command_flag_) {
keyboard_capturer->command_flag_ = command_state;
if (keyboard_capturer->command_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
}
}
return nullptr;
}
KeyboardCapturer::KeyboardCapturer() {}
KeyboardCapturer::~KeyboardCapturer() {}
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
g_on_key_action = on_key_action;
g_user_ptr = user_ptr;
CGEventMask eventMask = (1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) |
(1 << kCGEventFlagsChanged);
event_tap_ = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionDefault, eventMask,
eventCallback, this);
if (!event_tap_) {
LOG_ERROR("CGEventTapCreate failed");
return -1;
}
run_loop_source_ =
CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event_tap_, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_,
kCFRunLoopCommonModes);
CGEventTapEnable(event_tap_, true);
return 0;
}
int KeyboardCapturer::Unhook() {
CFRelease(run_loop_source_);
CFRelease(event_tap_);
return 0;
}
inline bool IsFunctionKey(int key_code) {
switch (key_code) {
case 0x7A:
case 0x78:
case 0x63:
case 0x76:
case 0x60:
case 0x61:
case 0x62:
case 0x64:
case 0x65:
case 0x6D:
case 0x67:
case 0x6F:
return true;
default:
return false;
}
}
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) {
CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code];
CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down);
CGEventRef clearFlags =
CGEventCreateKeyboardEvent(NULL, (CGKeyCode)0, true);
CGEventSetFlags(clearFlags, 0);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
// F1-F12 keys often require the FN key to be pressed on Mac keyboards, so
// we simulate the FN key release when an F1-F12 key is released.
if (IsFunctionKey(cg_key_code) && !is_down) {
CGEventRef fn_release_event =
CGEventCreateKeyboardEvent(NULL, fn_key_code_, false);
CGEventPost(kCGHIDEventTap, fn_release_event);
CFRelease(fn_release_event);
}
}
return 0;
}
} // namespace crossdesk

View File

@@ -0,0 +1,39 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-22
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _KEYBOARD_CAPTURER_H_
#define _KEYBOARD_CAPTURER_H_
#include <ApplicationServices/ApplicationServices.h>
#include "device_controller.h"
namespace crossdesk {
class KeyboardCapturer : public DeviceController {
public:
KeyboardCapturer();
virtual ~KeyboardCapturer();
public:
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
private:
CFMachPortRef event_tap_;
CFRunLoopSourceRef run_loop_source_;
public:
bool caps_lock_flag_ = false;
bool shift_flag_ = false;
bool control_flag_ = false;
bool option_flag_ = false;
bool command_flag_ = false;
int fn_key_code_ = 0x3F;
};
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,59 @@
#include "keyboard_capturer.h"
#include "rd_log.h"
namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr;
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION && g_on_key_action) {
KBDLLHOOKSTRUCT* kbData = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
g_on_key_action(kbData->vkCode, true, g_user_ptr);
} else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
g_on_key_action(kbData->vkCode, false, g_user_ptr);
}
return 1;
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
KeyboardCapturer::KeyboardCapturer() {}
KeyboardCapturer::~KeyboardCapturer() {}
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
g_on_key_action = on_key_action;
g_user_ptr = user_ptr;
keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
if (!keyboard_hook_) {
LOG_ERROR("Failed to install keyboard hook");
return -1;
}
return 0;
}
int KeyboardCapturer::Unhook() {
UnhookWindowsHookEx(keyboard_hook_);
return 0;
}
// apply remote keyboard commands to the local machine
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = (WORD)key_code;
if (!is_down) {
input.ki.dwFlags = KEYEVENTF_KEYUP;
}
SendInput(1, &input, sizeof(INPUT));
return 0;
}
} // namespace crossdesk

View File

@@ -0,0 +1,31 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-22
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _KEYBOARD_CAPTURER_H_
#define _KEYBOARD_CAPTURER_H_
#include <Windows.h>
#include "device_controller.h"
namespace crossdesk {
class KeyboardCapturer : public DeviceController {
public:
KeyboardCapturer();
virtual ~KeyboardCapturer();
public:
virtual int Hook(OnKeyAction on_key_action, void* user_ptr);
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
private:
HHOOK keyboard_hook_ = nullptr;
};
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,742 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-25
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _KEYBOARD_CONVERTER_H_
#define _KEYBOARD_CONVERTER_H_
#include <map>
namespace crossdesk {
// Windows vkCode to macOS CGKeyCode (104 keys)
std::map<int, int> vkCodeToCGKeyCode = {
// A-Z
{0x41, 0x00}, // A
{0x42, 0x0B}, // B
{0x43, 0x08}, // C
{0x44, 0x02}, // D
{0x45, 0x0E}, // E
{0x46, 0x03}, // F
{0x47, 0x05}, // G
{0x48, 0x04}, // H
{0x49, 0x22}, // I
{0x4A, 0x26}, // J
{0x4B, 0x28}, // K
{0x4C, 0x25}, // L
{0x4D, 0x2E}, // M
{0x4E, 0x2D}, // N
{0x4F, 0x1F}, // O
{0x50, 0x23}, // P
{0x51, 0x0C}, // Q
{0x52, 0x0F}, // R
{0x53, 0x01}, // S
{0x54, 0x11}, // T
{0x55, 0x20}, // U
{0x56, 0x09}, // V
{0x57, 0x0D}, // W
{0x58, 0x07}, // X
{0x59, 0x10}, // Y
{0x5A, 0x06}, // Z
// 0-9
{0x30, 0x1D}, // 0
{0x31, 0x12}, // 1
{0x32, 0x13}, // 2
{0x33, 0x14}, // 3
{0x34, 0x15}, // 4
{0x35, 0x17}, // 5
{0x36, 0x16}, // 6
{0x37, 0x1A}, // 7
{0x38, 0x1C}, // 8
{0x39, 0x19}, // 9
// F1-F12
{0x70, 0x7A}, // F1
{0x71, 0x78}, // F2
{0x72, 0x63}, // F3
{0x73, 0x76}, // F4
{0x74, 0x60}, // F5
{0x75, 0x61}, // F6
{0x76, 0x62}, // F7
{0x77, 0x64}, // F8
{0x78, 0x65}, // F9
{0x79, 0x6D}, // F10
{0x7A, 0x67}, // F11
{0x7B, 0x6F}, // F12
// control keys
{0x1B, 0x35}, // Escape
{0x0D, 0x24}, // Enter
{0x20, 0x31}, // Space
{0x08, 0x33}, // Backspace
{0x09, 0x30}, // Tab
{0x2C, 0x74}, // Print Screen
{0x2D, 0x72}, // Insert
{0x2E, 0x75}, // Delete
{0x24, 0x73}, // Home
{0x23, 0x77}, // End
{0x21, 0x79}, // Page Up
{0x22, 0x7A}, // Page Down
// arrow keys
{0x25, 0x7B}, // Left Arrow
{0x27, 0x7C}, // Right Arrow
{0x26, 0x7E}, // Up Arrow
{0x28, 0x7D}, // Down Arrow
// numpad
{0x60, 0x52}, // Numpad 0
{0x61, 0x53}, // Numpad 1
{0x62, 0x54}, // Numpad 2
{0x63, 0x55}, // Numpad 3
{0x64, 0x56}, // Numpad 4
{0x65, 0x57}, // Numpad 5
{0x66, 0x58}, // Numpad 6
{0x67, 0x59}, // Numpad 7
{0x68, 0x5B}, // Numpad 8
{0x69, 0x5C}, // Numpad 9
{0x6E, 0x41}, // Numpad .
{0x6F, 0x4B}, // Numpad /
{0x6A, 0x43}, // Numpad *
{0x6D, 0x4E}, // Numpad -
{0x6B, 0x45}, // Numpad +
// symbol keys
{0xBA, 0x29}, // ; (Semicolon)
{0xDE, 0x27}, // ' (Quote)
{0xC0, 0x32}, // ` (Backtick)
{0xBC, 0x2B}, // , (Comma)
{0xBE, 0x2F}, // . (Period)
{0xBF, 0x2C}, // / (Slash)
{0xDC, 0x2A}, // \ (Backslash)
{0xDB, 0x21}, // [ (Left Bracket)
{0xDD, 0x1E}, // ] (Right Bracket)
{0xBD, 0x1B}, // - (Minus)
{0xBB, 0x18}, // = (Equals)
// modifier keys
{0x14, 0x39}, // Caps Lock
{0xA0, 0x38}, // Shift (Left)
{0xA1, 0x3C}, // Shift (Right)
{0xA2, 0x3B}, // Ctrl (Left)
{0xA3, 0x3E}, // Ctrl (Right)
{0xA4, 0x3A}, // Alt (Left)
{0xA5, 0x3D}, // Alt (Right)
{0x5B, 0x37}, // Left Command (Windows key)
{0x5C, 0x36}, // Right Command
};
// macOS CGKeyCode to Windows vkCode
std::map<int, int> CGKeyCodeToVkCode = {
// A-Z
{0x00, 0x41}, // A
{0x0B, 0x42}, // B
{0x08, 0x43}, // C
{0x02, 0x44}, // D
{0x0E, 0x45}, // E
{0x03, 0x46}, // F
{0x05, 0x47}, // G
{0x04, 0x48}, // H
{0x22, 0x49}, // I
{0x26, 0x4A}, // J
{0x28, 0x4B}, // K
{0x25, 0x4C}, // L
{0x2E, 0x4D}, // M
{0x2D, 0x4E}, // N
{0x1F, 0x4F}, // O
{0x23, 0x50}, // P
{0x0C, 0x51}, // Q
{0x0F, 0x52}, // R
{0x01, 0x53}, // S
{0x11, 0x54}, // T
{0x20, 0x55}, // U
{0x09, 0x56}, // V
{0x0D, 0x57}, // W
{0x07, 0x58}, // X
{0x10, 0x59}, // Y
{0x06, 0x5A}, // Z
// 0-9
{0x1D, 0x30}, // 0
{0x12, 0x31}, // 1
{0x13, 0x32}, // 2
{0x14, 0x33}, // 3
{0x15, 0x34}, // 4
{0x17, 0x35}, // 5
{0x16, 0x36}, // 6
{0x1A, 0x37}, // 7
{0x1C, 0x38}, // 8
{0x19, 0x39}, // 9
// F1-F12
{0x7A, 0x70}, // F1
{0x78, 0x71}, // F2
{0x63, 0x72}, // F3
{0x76, 0x73}, // F4
{0x60, 0x74}, // F5
{0x61, 0x75}, // F6
{0x62, 0x76}, // F7
{0x64, 0x77}, // F8
{0x65, 0x78}, // F9
{0x6D, 0x79}, // F10
{0x67, 0x7A}, // F11
{0x6F, 0x7B}, // F12
// control keys
{0x35, 0x1B}, // Escape
{0x24, 0x0D}, // Enter
{0x31, 0x20}, // Space
{0x33, 0x08}, // Backspace
{0x30, 0x09}, // Tab
{0x74, 0x2C}, // Print Screen
{0x72, 0x2D}, // Insert
{0x75, 0x2E}, // Delete
{0x73, 0x24}, // Home
{0x77, 0x23}, // End
{0x79, 0x21}, // Page Up
{0x7A, 0x22}, // Page Down
// arrow keys
{0x7B, 0x25}, // Left Arrow
{0x7C, 0x27}, // Right Arrow
{0x7E, 0x26}, // Up Arrow
{0x7D, 0x28}, // Down Arrow
// numpad
{0x52, 0x60}, // Numpad 0
{0x53, 0x61}, // Numpad 1
{0x54, 0x62}, // Numpad 2
{0x55, 0x63}, // Numpad 3
{0x56, 0x64}, // Numpad 4
{0x57, 0x65}, // Numpad 5
{0x58, 0x66}, // Numpad 6
{0x59, 0x67}, // Numpad 7
{0x5B, 0x68}, // Numpad 8
{0x5C, 0x69}, // Numpad 9
{0x41, 0x6E}, // Numpad .
{0x4B, 0x6F}, // Numpad /
{0x43, 0x6A}, // Numpad *
{0x4E, 0x6D}, // Numpad -
{0x45, 0x6B}, // Numpad +
// symbol keys
{0x29, 0xBA}, // ; (Semicolon)
{0x27, 0xDE}, // ' (Quote)
{0x32, 0xC0}, // ` (Backtick)
{0x2B, 0xBC}, // , (Comma)
{0x2F, 0xBE}, // . (Period)
{0x2C, 0xBF}, // / (Slash)
{0x2A, 0xDC}, // \ (Backslash)
{0x21, 0xDB}, // [ (Left Bracket)
{0x1E, 0xDD}, // ] (Right Bracket)
{0x1B, 0xBD}, // - (Minus)
{0x18, 0xBB}, // = (Equals)
// modifier keys
{0x39, 0x14}, // Caps Lock
{0x38, 0xA0}, // Shift (Left)
{0x3C, 0xA1}, // Shift (Right)
{0x3B, 0xA2}, // Control (Left)
{0x3E, 0xA3}, // Control (Right)
{0x3A, 0xA4}, // Alt (Left)
{0x3D, 0xA5}, // Alt (Right)
{0x37, 0x5B}, // Left Command (Windows key)
{0x36, 0x5C}, // Right Command
};
// Windows vkCode to X11 KeySym
std::map<int, int> vkCodeToX11KeySym = {
// A-Z
{0x41, 0x0041}, // A
{0x42, 0x0042}, // B
{0x43, 0x0043}, // C
{0x44, 0x0044}, // D
{0x45, 0x0045}, // E
{0x46, 0x0046}, // F
{0x47, 0x0047}, // G
{0x48, 0x0048}, // H
{0x49, 0x0049}, // I
{0x4A, 0x004A}, // J
{0x4B, 0x004B}, // K
{0x4C, 0x004C}, // L
{0x4D, 0x004D}, // M
{0x4E, 0x004E}, // N
{0x4F, 0x004F}, // O
{0x50, 0x0050}, // P
{0x51, 0x0051}, // Q
{0x52, 0x0052}, // R
{0x53, 0x0053}, // S
{0x54, 0x0054}, // T
{0x55, 0x0055}, // U
{0x56, 0x0056}, // V
{0x57, 0x0057}, // W
{0x58, 0x0058}, // X
{0x59, 0x0059}, // Y
{0x5A, 0x005A}, // Z
// 0-9
{0x30, 0x0030}, // 0
{0x31, 0x0031}, // 1
{0x32, 0x0032}, // 2
{0x33, 0x0033}, // 3
{0x34, 0x0034}, // 4
{0x35, 0x0035}, // 5
{0x36, 0x0036}, // 6
{0x37, 0x0037}, // 7
{0x38, 0x0038}, // 8
{0x39, 0x0039}, // 9
// F1-F12
{0x70, 0xFFBE}, // F1
{0x71, 0xFFBF}, // F2
{0x72, 0xFFC0}, // F3
{0x73, 0xFFC1}, // F4
{0x74, 0xFFC2}, // F5
{0x75, 0xFFC3}, // F6
{0x76, 0xFFC4}, // F7
{0x77, 0xFFC5}, // F8
{0x78, 0xFFC6}, // F9
{0x79, 0xFFC7}, // F10
{0x7A, 0xFFC8}, // F11
{0x7B, 0xFFC9}, // F12
// control keys
{0x1B, 0xFF1B}, // Escape
{0x0D, 0xFF0D}, // Enter
{0x20, 0x0020}, // Space
{0x08, 0xFF08}, // Backspace
{0x09, 0xFF09}, // Tab
{0x2C, 0xFF15}, // Print Screen
{0x91, 0xFF14}, // Scroll Lock
{0x13, 0xFF13}, // Pause/Break
{0x2D, 0xFF63}, // Insert
{0x2E, 0xFFFF}, // Delete
{0x24, 0xFF50}, // Home
{0x23, 0xFF57}, // End
{0x21, 0xFF55}, // Page Up
{0x22, 0xFF56}, // Page Down
// arrow keys
{0x25, 0xFF51}, // Left Arrow
{0x27, 0xFF53}, // Right Arrow
{0x26, 0xFF52}, // Up Arrow
{0x28, 0xFF54}, // Down Arrow
// numpad
{0x60, 0x0030}, // Numpad 0
{0x61, 0x0031}, // Numpad 1
{0x62, 0x0032}, // Numpad 2
{0x63, 0x0033}, // Numpad 3
{0x64, 0x0034}, // Numpad 4
{0x65, 0x0035}, // Numpad 5
{0x66, 0x0036}, // Numpad 6
{0x67, 0x0037}, // Numpad 7
{0x68, 0x0038}, // Numpad 8
{0x69, 0x0039}, // Numpad 9
{0x6E, 0x003A}, // Numpad .
{0x6F, 0x002F}, // Numpad /
{0x6A, 0x002A}, // Numpad *
{0x6D, 0x002D}, // Numpad -
{0x6B, 0x002B}, // Numpad +
// symbol keys
{0xBA, 0x003B}, // ; (Semicolon)
{0xDE, 0x0027}, // ' (Quote)
{0xC0, 0x007E}, // ` (Grave)
{0xBC, 0x002C}, // , (Comma)
{0xBE, 0x002E}, // . (Period)
{0xBF, 0x002F}, // / (Slash)
{0xDC, 0x005C}, // \ (Backslash)
{0xDB, 0x005B}, // [ (Left Bracket)
{0xDD, 0x005D}, // ] (Right Bracket)
{0xBD, 0x002D}, // - (Minus)
{0xBB, 0x003D}, // = (Equals)
// modifier keys
{0x14, 0xFFE5}, // Caps Lock
{0xA0, 0xFFE1}, // Shift (Left)
{0xA1, 0xFFE2}, // Shift (Right)
{0xA2, 0xFFE3}, // Ctrl (Left)
{0xA3, 0xFFE4}, // Ctrl (Right)
{0xA4, 0xFFE9}, // Alt (Left)
{0xA5, 0xFFEA}, // Alt (Right)
{0x5B, 0xFFEB}, // Left Command (Windows key)
{0x5C, 0xFFEC}, // Right Command
};
// X11 KeySym to Windows vkCode
// std::map<int, int> x11KeySymToVkCode = []() {
// std::map<int, int> result;
// for (const auto& pair : vkCodeToX11KeySym) {
// result[pair.second] = pair.first;
// }
// return result;
// }();
std::map<int, int> x11KeySymToVkCode = {
// A-Z
{0x0041, 0x41}, // A
{0x0042, 0x42}, // B
{0x0043, 0x43}, // C
{0x0044, 0x44}, // D
{0x0045, 0x45}, // E
{0x0046, 0x46}, // F
{0x0047, 0x47}, // G
{0x0048, 0x48}, // H
{0x0049, 0x49}, // I
{0x004A, 0x4A}, // J
{0x004B, 0x4B}, // K
{0x004C, 0x4C}, // L
{0x004D, 0x4D}, // M
{0x004E, 0x4E}, // N
{0x004F, 0x4F}, // O
{0x0050, 0x50}, // P
{0x0051, 0x51}, // Q
{0x0052, 0x52}, // R
{0x0053, 0x53}, // S
{0x0054, 0x54}, // T
{0x0055, 0x55}, // U
{0x0056, 0x56}, // V
{0x0057, 0x57}, // W
{0x0058, 0x58}, // X
{0x0059, 0x59}, // Y
{0x005A, 0x5A}, // Z
// 0-9
{0x0030, 0x30}, // 0
{0x0031, 0x31}, // 1
{0x0032, 0x32}, // 2
{0x0033, 0x33}, // 3
{0x0034, 0x34}, // 4
{0x0035, 0x35}, // 5
{0x0036, 0x36}, // 6
{0x0037, 0x37}, // 7
{0x0038, 0x38}, // 8
{0x0039, 0x39}, // 9
// F1-F12
{0xFFBE, 0x70}, // F1
{0xFFBF, 0x71}, // F2
{0xFFC0, 0x72}, // F3
{0xFFC1, 0x73}, // F4
{0xFFC2, 0x74}, // F5
{0xFFC3, 0x75}, // F6
{0xFFC4, 0x76}, // F7
{0xFFC5, 0x77}, // F8
{0xFFC6, 0x78}, // F9
{0xFFC7, 0x79}, // F10
{0xFFC8, 0x7A}, // F11
{0xFFC9, 0x7B}, // F12
// control keys
{0xFF1B, 0x1B}, // Escape
{0xFF0D, 0x0D}, // Enter
{0x0020, 0x20}, // Space
{0xFF08, 0x08}, // Backspace
{0xFF09, 0x09}, // Tab
{0xFF15, 0x2C}, // Print Screen
{0xFF14, 0x91}, // Scroll Lock
{0xFF13, 0x13}, // Pause/Break
{0xFF63, 0x2D}, // Insert
{0xFFFF, 0x2E}, // Delete
{0xFF50, 0x24}, // Home
{0xFF57, 0x23}, // End
{0xFF55, 0x21}, // Page Up
{0xFF56, 0x22}, // Page Down
// arrow keys
{0xFF51, 0x25}, // Left Arrow
{0xFF53, 0x27}, // Right Arrow
{0xFF52, 0x26}, // Up Arrow
{0xFF54, 0x28}, // Down Arrow
// numpad
{0x0030, 0x60}, // Numpad 0
{0x0031, 0x61}, // Numpad 1
{0x0032, 0x62}, // Numpad 2
{0x0033, 0x63}, // Numpad 3
{0x0034, 0x64}, // Numpad 4
{0x0035, 0x65}, // Numpad 5
{0x0036, 0x66}, // Numpad 6
{0x0037, 0x67}, // Numpad 7
{0x0038, 0x68}, // Numpad 8
{0x0039, 0x69}, // Numpad 9
{0x003A, 0x6E}, // Numpad .
{0x002F, 0x6F}, // Numpad /
{0x002A, 0x6A}, // Numpad *
{0x002D, 0x6D}, // Numpad -
{0x002B, 0x6B}, // Numpad +
// symbol keys
{0x003B, 0xBA}, // ; (Semicolon)
{0x0027, 0xDE}, // ' (Quote)
{0x007E, 0xC0}, // ` (Grave)
{0x002C, 0xBC}, // , (Comma)
{0x002E, 0xBE}, // . (Period)
{0x002F, 0xBF}, // / (Slash)
{0x005C, 0xDC}, // \ (Backslash)
{0x005B, 0xDB}, // [ (Left Bracket)
{0x005D, 0xDD}, // ] (Right Bracket)
{0x002D, 0xBD}, // - (Minus)
{0x003D, 0xBB}, // = (Equals)
// modifier keys
{0xFFE5, 0x14}, // Caps Lock
{0xFFE1, 0xA0}, // Shift (Left)
{0xFFE2, 0xA1}, // Shift (Right)
{0xFFE3, 0xA2}, // Ctrl (Left)
{0xFFE4, 0xA3}, // Ctrl (Right)
{0xFFE9, 0xA4}, // Alt (Left)
{0xFFEA, 0xA5}, // Alt (Right)
{0xFFEB, 0x5B}, // Left Command (Windows key)
{0xFFEC, 0x5C}, // Right Command
};
// macOS CGKeyCode to X11 KeySym
std::map<int, int> cgKeyCodeToX11KeySym = {
// A-Z
{0x00, 0x0041}, // A
{0x0B, 0x0042}, // B
{0x08, 0x0043}, // C
{0x02, 0x0044}, // D
{0x0E, 0x0045}, // E
{0x03, 0x0046}, // F
{0x05, 0x0047}, // G
{0x04, 0x0048}, // H
{0x22, 0x0049}, // I
{0x26, 0x004A}, // J
{0x28, 0x004B}, // K
{0x25, 0x004C}, // L
{0x2E, 0x004D}, // M
{0x2D, 0x004E}, // N
{0x1F, 0x004F}, // O
{0x23, 0x0050}, // P
{0x0C, 0x0051}, // Q
{0x0F, 0x0052}, // R
{0x01, 0x0053}, // S
{0x11, 0x0054}, // T
{0x20, 0x0055}, // U
{0x09, 0x0056}, // V
{0x0D, 0x0057}, // W
{0x07, 0x0058}, // X
{0x10, 0x0059}, // Y
{0x06, 0x005A}, // Z
// 0-9
{0x1D, 0x0030}, // 0
{0x12, 0x0031}, // 1
{0x13, 0x0032}, // 2
{0x14, 0x0033}, // 3
{0x15, 0x0034}, // 4
{0x17, 0x0035}, // 5
{0x16, 0x0036}, // 6
{0x1A, 0x0037}, // 7
{0x1C, 0x0038}, // 8
{0x19, 0x0039}, // 9
// F1-F12
{0x7A, 0xFFBE}, // F1
{0x78, 0xFFBF}, // F2
{0x63, 0xFFC0}, // F3
{0x76, 0xFFC1}, // F4
{0x60, 0xFFC2}, // F5
{0x61, 0xFFC3}, // F6
{0x62, 0xFFC4}, // F7
{0x64, 0xFFC5}, // F8
{0x65, 0xFFC6}, // F9
{0x6D, 0xFFC7}, // F10
{0x67, 0xFFC8}, // F11
{0x6F, 0xFFC9}, // F12
// control keys
{0x35, 0xFF1B}, // Escape
{0x24, 0xFF0D}, // Enter
{0x31, 0x0020}, // Space
{0x33, 0xFF08}, // Backspace
{0x30, 0xFF09}, // Tab
{0x74, 0xFF15}, // Print Screen
{0x72, 0xFF63}, // Insert
{0x75, 0xFFFF}, // Delete
{0x73, 0xFF50}, // Home
{0x77, 0xFF57}, // End
{0x79, 0xFF55}, // Page Up
{0x7A, 0xFF56}, // Page Down
// arrow keys
{0x7B, 0xFF51}, // Left Arrow
{0x7C, 0xFF53}, // Right Arrow
{0x7E, 0xFF52}, // Up Arrow
{0x7D, 0xFF54}, // Down Arrow
// numpad
{0x52, 0x0030}, // Numpad 0
{0x53, 0x0031}, // Numpad 1
{0x54, 0x0032}, // Numpad 2
{0x55, 0x0033}, // Numpad 3
{0x56, 0x0034}, // Numpad 4
{0x57, 0x0035}, // Numpad 5
{0x58, 0x0036}, // Numpad 6
{0x59, 0x0037}, // Numpad 7
{0x5B, 0x0038}, // Numpad 8
{0x5C, 0x0039}, // Numpad 9
{0x41, 0x003A}, // Numpad .
{0x4B, 0x002F}, // Numpad /
{0x43, 0x002A}, // Numpad *
{0x4E, 0x002D}, // Numpad -
{0x45, 0x002B}, // Numpad +
// symbol keys
{0x29, 0x003B}, // ; (Semicolon)
{0x27, 0x0027}, // ' (Quote)
{0x32, 0x007E}, // ` (Backtick)
{0x2B, 0x002C}, // , (Comma)
{0x2F, 0x002E}, // . (Period)
{0x2C, 0x002F}, // / (Slash)
{0x2A, 0x005C}, // \ (Backslash)
{0x21, 0x005B}, // [ (Left Bracket)
{0x1E, 0x005D}, // ] (Right Bracket)
{0x1B, 0x002D}, // - (Minus)
{0x18, 0x003D}, // = (Equals)
// modifier keys
{0x39, 0xFFE5}, // Caps Lock
{0x38, 0xFFE1}, // Shift (Left)
{0x3C, 0xFFE2}, // Shift (Right)
{0x3B, 0xFFE3}, // Control (Left)
{0x3E, 0xFFE4}, // Control (Right)
{0x3A, 0xFFE9}, // Alt (Left)
{0x3D, 0xFFEA}, // Alt (Right)
{0x37, 0xFFEB}, // Left Command (Windows key)
{0x36, 0xFFEC}, // Right Command
};
// X11 KeySym to macOS CGKeyCode
// std::map<int, int> x11KeySymToCgKeyCode = []() {
// std::map<int, int> result;
// for (const auto& pair : cgKeyCodeToX11KeySym) {
// result[pair.second] = pair.first;
// }
// return result;
// }();
std::map<int, int> x11KeySymToCgKeyCode = {
// A-Z
{0x0041, 0x00}, // A
{0x0042, 0x0B}, // B
{0x0043, 0x08}, // C
{0x0044, 0x02}, // D
{0x0045, 0x0E}, // E
{0x0046, 0x03}, // F
{0x0047, 0x05}, // G
{0x0048, 0x04}, // H
{0x0049, 0x22}, // I
{0x004A, 0x26}, // J
{0x004B, 0x28}, // K
{0x004C, 0x25}, // L
{0x004D, 0x2E}, // M
{0x004E, 0x2D}, // N
{0x004F, 0x1F}, // O
{0x0050, 0x23}, // P
{0x0051, 0x0C}, // Q
{0x0052, 0x0F}, // R
{0x0053, 0x01}, // S
{0x0054, 0x11}, // T
{0x0055, 0x20}, // U
{0x0056, 0x09}, // V
{0x0057, 0x0D}, // W
{0x0058, 0x07}, // X
{0x0059, 0x10}, // Y
{0x005A, 0x06}, // Z
// 0-9
{0x0030, 0x1D}, // 0
{0x0031, 0x12}, // 1
{0x0032, 0x13}, // 2
{0x0033, 0x14}, // 3
{0x0034, 0x15}, // 4
{0x0035, 0x17}, // 5
{0x0036, 0x16}, // 6
{0x0037, 0x1A}, // 7
{0x0038, 0x1C}, // 8
{0x0039, 0x19}, // 9
// F1-F12
{0xFFBE, 0x7A}, // F1
{0xFFBF, 0x78}, // F2
{0xFFC0, 0x63}, // F3
{0xFFC1, 0x76}, // F4
{0xFFC2, 0x60}, // F5
{0xFFC3, 0x61}, // F6
{0xFFC4, 0x62}, // F7
{0xFFC5, 0x64}, // F8
{0xFFC6, 0x65}, // F9
{0xFFC7, 0x6D}, // F10
{0xFFC8, 0x67}, // F11
{0xFFC9, 0x6F}, // F12
// control keys
{0xFF1B, 0x35}, // Escape
{0xFF0D, 0x24}, // Enter
{0x0020, 0x31}, // Space
{0xFF08, 0x33}, // Backspace
{0xFF09, 0x30}, // Tab
{0xFF15, 0x74}, // Print Screen
{0xFF63, 0x72}, // Insert
{0xFFFF, 0x75}, // Delete
{0xFF50, 0x73}, // Home
{0xFF57, 0x77}, // End
{0xFF55, 0x79}, // Page Up
{0xFF56, 0x7A}, // Page Down
// arrow keys
{0xFF51, 0x7B}, // Left Arrow
{0xFF53, 0x7C}, // Right Arrow
{0xFF52, 0x7E}, // Up Arrow
{0xFF54, 0x7D}, // Down Arrow
// numpad
{0x0030, 0x52}, // Numpad 0
{0x0031, 0x53}, // Numpad 1
{0x0032, 0x54}, // Numpad 2
{0x0033, 0x55}, // Numpad 3
{0x0034, 0x56}, // Numpad 4
{0x0035, 0x57}, // Numpad 5
{0x0036, 0x58}, // Numpad 6
{0x0037, 0x59}, // Numpad 7
{0x0038, 0x5B}, // Numpad 8
{0x0039, 0x5C}, // Numpad 9
{0x003A, 0x41}, // Numpad .
{0x002F, 0x4B}, // Numpad /
{0x002A, 0x43}, // Numpad *
{0x002D, 0x4E}, // Numpad -
{0x002B, 0x45}, // Numpad +
// symbol keys
{0x003B, 0x29}, // ; (Semicolon)
{0x0027, 0x27}, // ' (Quote)
{0x007E, 0x32}, // ` (Backtick)
{0x002C, 0x2B}, // , (Comma)
{0x002E, 0x2F}, // . (Period)
{0x002F, 0x2C}, // / (Slash)
{0x005C, 0x2A}, // \ (Backslash)
{0x005B, 0x21}, // [ (Left Bracket)
{0x005D, 0x1E}, // ] (Right Bracket)
{0x002D, 0x1B}, // - (Minus)
{0x003D, 0x18}, // = (Equals)
// modifier keys
{0xFFE5, 0x39}, // Caps Lock
{0xFFE1, 0x38}, // Shift (Left)
{0xFFE2, 0x3C}, // Shift (Right)
{0xFFE3, 0x3B}, // Control (Left)
{0xFFE4, 0x3E}, // Control (Right)
{0xFFE9, 0x3A}, // Alt (Left)
{0xFFEA, 0x3D}, // Alt (Right)
{0xFFEB, 0x37}, // Left Command
{0xFFEC, 0x36}, // Right Command
};
} // namespace crossdesk
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,861 @@
/*
File: HIToolbox/Events.h
Contains: Event Manager Interfaces.
Copyright: 锟<> 1985-2008 by Apple Computer, Inc., all rights reserved
Bugs?: For bug reports, consult the following page on
the World Wide Web:
http://developer.apple.com/bugreporter/
*/
#ifndef __EVENTS__
#define __EVENTS__
#ifndef __APPLICATIONSERVICES__
#include <ApplicationServices/ApplicationServices.h>
#endif
#include <AvailabilityMacros.h>
#if PRAGMA_ONCE
#pragma once
#endif
#ifdef __cplusplus
extern "C" {
#endif
#pragma pack(push, 2)
typedef UInt16 EventKind;
typedef UInt16 EventMask;
enum {
nullEvent = 0,
mouseDown = 1,
mouseUp = 2,
keyDown = 3,
keyUp = 4,
autoKey = 5,
updateEvt = 6,
diskEvt = 7, /* Not sent in Carbon. See kEventClassVolume in CarbonEvents.h*/
activateEvt = 8,
osEvt = 15,
kHighLevelEvent = 23
};
enum {
mDownMask = 1 << mouseDown, /* mouse button pressed*/
mUpMask = 1 << mouseUp, /* mouse button released*/
keyDownMask = 1 << keyDown, /* key pressed*/
keyUpMask = 1 << keyUp, /* key released*/
autoKeyMask = 1 << autoKey, /* key repeatedly held down*/
updateMask = 1 << updateEvt, /* window needs updating*/
diskMask = 1 << diskEvt, /* disk inserted*/
activMask = 1 << activateEvt, /* activate/deactivate window*/
highLevelEventMask = 0x0400, /* high-level events (includes AppleEvents)*/
osMask = 1 << osEvt, /* operating system events (suspend, resume)*/
everyEvent = 0xFFFF /* all of the above*/
};
enum {
charCodeMask = 0x000000FF,
keyCodeMask = 0x0000FF00,
adbAddrMask = 0x00FF0000,
osEvtMessageMask = (UInt32)0xFF000000
};
enum {
/* OS event messages. Event (sub)code is in the high byte of the message
field.*/
mouseMovedMessage = 0x00FA,
suspendResumeMessage = 0x0001
};
enum {
resumeFlag = 1 /* Bit 0 of message indicates resume vs suspend*/
};
#if CALL_NOT_IN_CARBON
/* convertClipboardFlag is not ever set under Carbon. This is because scrap
* conversion is */
/* not tied to suspend/resume events any longer. Your application should
* instead use the */
/* scrap promise mechanism and fulfill scrap requests only when your promise
* keeper proc */
/* is called. If you need to know if the scrap has changed, you can cache the
* last */
/* ScrapRef you received and compare it with the current ScrapRef */
enum {
convertClipboardFlag =
2 /* Bit 1 in resume message indicates clipboard change*/
};
#endif /* CALL_NOT_IN_CARBON */
/*
CARBON ALERT! BATTLESTATIONS!
The EventModifiers bits defined here are also used in the newer Carbon Event
key modifiers parameters. There are two main differences:
1) The Carbon key modifiers parameter is a UInt32, not a UInt16. Never try
to extract the key modifiers parameter from a Carbon Event into an
EventModifiers type. You will probably get your stack trashed. 2) The Carbon
key modifiers is just that: key modifiers. That parameter will never contain
the button state bit.
*/
typedef UInt16 EventModifiers;
enum {
/* modifiers */
activeFlagBit = 0, /* activate? (activateEvt and mouseDown)*/
btnStateBit = 7, /* state of button?*/
cmdKeyBit = 8, /* command key down?*/
shiftKeyBit = 9, /* shift key down?*/
alphaLockBit = 10, /* alpha lock down?*/
optionKeyBit = 11, /* option key down?*/
controlKeyBit = 12, /* control key down?*/
rightShiftKeyBit = 13, /* right shift key down? Not supported on Mac OS X.*/
rightOptionKeyBit = 14, /* right Option key down? Not supported on Mac OS X.*/
rightControlKeyBit =
15 /* right Control key down? Not supported on Mac OS X.*/
};
enum {
activeFlag = 1 << activeFlagBit,
btnState = 1 << btnStateBit,
cmdKey = 1 << cmdKeyBit,
shiftKey = 1 << shiftKeyBit,
alphaLock = 1 << alphaLockBit,
optionKey = 1 << optionKeyBit,
controlKey = 1 << controlKeyBit,
rightShiftKey = 1 << rightShiftKeyBit, /* Not supported on Mac OS X.*/
rightOptionKey = 1 << rightOptionKeyBit, /* Not supported on Mac OS X.*/
rightControlKey = 1 << rightControlKeyBit /* Not supported on Mac OS X.*/
};
/* MacRoman character codes*/
enum {
kNullCharCode = 0,
kHomeCharCode = 1,
kEnterCharCode = 3,
kEndCharCode = 4,
kHelpCharCode = 5,
kBellCharCode = 7,
kBackspaceCharCode = 8,
kTabCharCode = 9,
kLineFeedCharCode = 10,
kVerticalTabCharCode = 11,
kPageUpCharCode = 11,
kFormFeedCharCode = 12,
kPageDownCharCode = 12,
kReturnCharCode = 13,
kFunctionKeyCharCode = 16,
kCommandCharCode = 17, /* glyph available only in system fonts*/
kCheckCharCode = 18, /* glyph available only in system fonts*/
kDiamondCharCode = 19, /* glyph available only in system fonts*/
kAppleLogoCharCode = 20, /* glyph available only in system fonts*/
kEscapeCharCode = 27,
kClearCharCode = 27,
kLeftArrowCharCode = 28,
kRightArrowCharCode = 29,
kUpArrowCharCode = 30,
kDownArrowCharCode = 31,
kSpaceCharCode = 32,
kDeleteCharCode = 127,
kBulletCharCode = 165,
kNonBreakingSpaceCharCode = 202
};
/* useful Unicode code points*/
enum {
kShiftUnicode = 0x21E7, /* Unicode UPWARDS WHITE ARROW*/
kControlUnicode = 0x2303, /* Unicode UP ARROWHEAD*/
kOptionUnicode = 0x2325, /* Unicode OPTION KEY*/
kCommandUnicode = 0x2318, /* Unicode PLACE OF INTEREST SIGN*/
kPencilUnicode = 0x270E, /* Unicode LOWER RIGHT PENCIL; actually pointed left
until Mac OS X 10.3*/
kPencilLeftUnicode = 0xF802, /* Unicode LOWER LEFT PENCIL; available in Mac OS
X 10.3 and later*/
kCheckUnicode = 0x2713, /* Unicode CHECK MARK*/
kDiamondUnicode = 0x25C6, /* Unicode BLACK DIAMOND*/
kBulletUnicode = 0x2022, /* Unicode BULLET*/
kAppleLogoUnicode = 0xF8FF /* Unicode APPLE LOGO*/
};
/*
* Summary:
* Virtual keycodes
*
* Discussion:
* These constants are the virtual keycodes defined originally in
* Inside Mac Volume V, pg. V-191. They identify physical keys on a
* keyboard. Those constants with "ANSI" in the name are labeled
* according to the key position on an ANSI-standard US keyboard.
* For example, kVK_ANSI_A indicates the virtual keycode for the key
* with the letter 'A' in the US keyboard layout. Other keyboard
* layouts may have the 'A' key label on a different physical key;
* in this case, pressing 'A' will generate a different virtual
* keycode.
*/
enum {
kVK_ANSI_A = 0x00,
kVK_ANSI_S = 0x01,
kVK_ANSI_D = 0x02,
kVK_ANSI_F = 0x03,
kVK_ANSI_H = 0x04,
kVK_ANSI_G = 0x05,
kVK_ANSI_Z = 0x06,
kVK_ANSI_X = 0x07,
kVK_ANSI_C = 0x08,
kVK_ANSI_V = 0x09,
kVK_ANSI_B = 0x0B,
kVK_ANSI_Q = 0x0C,
kVK_ANSI_W = 0x0D,
kVK_ANSI_E = 0x0E,
kVK_ANSI_R = 0x0F,
kVK_ANSI_Y = 0x10,
kVK_ANSI_T = 0x11,
kVK_ANSI_1 = 0x12,
kVK_ANSI_2 = 0x13,
kVK_ANSI_3 = 0x14,
kVK_ANSI_4 = 0x15,
kVK_ANSI_6 = 0x16,
kVK_ANSI_5 = 0x17,
kVK_ANSI_Equal = 0x18,
kVK_ANSI_9 = 0x19,
kVK_ANSI_7 = 0x1A,
kVK_ANSI_Minus = 0x1B,
kVK_ANSI_8 = 0x1C,
kVK_ANSI_0 = 0x1D,
kVK_ANSI_RightBracket = 0x1E,
kVK_ANSI_O = 0x1F,
kVK_ANSI_U = 0x20,
kVK_ANSI_LeftBracket = 0x21,
kVK_ANSI_I = 0x22,
kVK_ANSI_P = 0x23,
kVK_ANSI_L = 0x25,
kVK_ANSI_J = 0x26,
kVK_ANSI_Quote = 0x27,
kVK_ANSI_K = 0x28,
kVK_ANSI_Semicolon = 0x29,
kVK_ANSI_Backslash = 0x2A,
kVK_ANSI_Comma = 0x2B,
kVK_ANSI_Slash = 0x2C,
kVK_ANSI_N = 0x2D,
kVK_ANSI_M = 0x2E,
kVK_ANSI_Period = 0x2F,
kVK_ANSI_Grave = 0x32,
kVK_ANSI_KeypadDecimal = 0x41,
kVK_ANSI_KeypadMultiply = 0x43,
kVK_ANSI_KeypadPlus = 0x45,
kVK_ANSI_KeypadClear = 0x47,
kVK_ANSI_KeypadDivide = 0x4B,
kVK_ANSI_KeypadEnter = 0x4C,
kVK_ANSI_KeypadMinus = 0x4E,
kVK_ANSI_KeypadEquals = 0x51,
kVK_ANSI_Keypad0 = 0x52,
kVK_ANSI_Keypad1 = 0x53,
kVK_ANSI_Keypad2 = 0x54,
kVK_ANSI_Keypad3 = 0x55,
kVK_ANSI_Keypad4 = 0x56,
kVK_ANSI_Keypad5 = 0x57,
kVK_ANSI_Keypad6 = 0x58,
kVK_ANSI_Keypad7 = 0x59,
kVK_ANSI_Keypad8 = 0x5B,
kVK_ANSI_Keypad9 = 0x5C
};
/* keycodes for keys that are independent of keyboard layout*/
enum {
kVK_Return = 0x24,
kVK_Tab = 0x30,
kVK_Space = 0x31,
kVK_Delete = 0x33,
kVK_Escape = 0x35,
kVK_Command = 0x37,
kVK_Shift = 0x38,
kVK_CapsLock = 0x39,
kVK_Option = 0x3A,
kVK_Control = 0x3B,
kVK_RightCommand = 0x36,
kVK_RightShift = 0x3C,
kVK_RightOption = 0x3D,
kVK_RightControl = 0x3E,
kVK_Function = 0x3F,
kVK_F17 = 0x40,
kVK_VolumeUp = 0x48,
kVK_VolumeDown = 0x49,
kVK_Mute = 0x4A,
kVK_F18 = 0x4F,
kVK_F19 = 0x50,
kVK_F20 = 0x5A,
kVK_F5 = 0x60,
kVK_F6 = 0x61,
kVK_F7 = 0x62,
kVK_F3 = 0x63,
kVK_F8 = 0x64,
kVK_F9 = 0x65,
kVK_F11 = 0x67,
kVK_F13 = 0x69,
kVK_F16 = 0x6A,
kVK_F14 = 0x6B,
kVK_F10 = 0x6D,
kVK_ContextualMenu = 0x6E,
kVK_F12 = 0x6F,
kVK_F15 = 0x71,
kVK_Help = 0x72,
kVK_Home = 0x73,
kVK_PageUp = 0x74,
kVK_ForwardDelete = 0x75,
kVK_F4 = 0x76,
kVK_End = 0x77,
kVK_F2 = 0x78,
kVK_PageDown = 0x79,
kVK_F1 = 0x7A,
kVK_LeftArrow = 0x7B,
kVK_RightArrow = 0x7C,
kVK_DownArrow = 0x7D,
kVK_UpArrow = 0x7E
};
/* ISO keyboards only*/
enum { kVK_ISO_Section = 0x0A };
/* JIS keyboards only*/
enum {
kVK_JIS_Yen = 0x5D,
kVK_JIS_Underscore = 0x5E,
kVK_JIS_KeypadComma = 0x5F,
kVK_JIS_Eisu = 0x66,
kVK_JIS_Kana = 0x68
};
struct EventRecord {
EventKind what;
unsigned long message;
UInt32 when;
Point where;
EventModifiers modifiers;
};
typedef struct EventRecord EventRecord;
typedef CALLBACK_API(void, FKEYProcPtr)(void);
typedef STACK_UPP_TYPE(FKEYProcPtr) FKEYUPP;
/*
* NewFKEYUPP()
*
* Availability:
* Mac OS X: not available
* CarbonLib: not available
* Non-Carbon CFM: available as macro/inline
*/
/*
* DisposeFKEYUPP()
*
* Availability:
* Mac OS X: not available
* CarbonLib: not available
* Non-Carbon CFM: available as macro/inline
*/
/*
* InvokeFKEYUPP()
*
* Availability:
* Mac OS X: not available
* CarbonLib: not available
* Non-Carbon CFM: available as macro/inline
*/
#if !__LP64__
/*
* GetMouse() *** DEPRECATED ***
*
* Deprecated:
* Use HIGetMousePosition instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.5 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern void GetMouse(Point *mouseLoc)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
#endif /* !__LP64__ */
/*
* Button() *** DEPRECATED ***
*
* Deprecated:
* Use GetCurrentButtonState or GetCurrentEventButtonState instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework but
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
* CFM: in InterfaceLib 7.1 and later
*/
extern Boolean Button(void)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
#if !__LP64__
/*
* StillDown()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern Boolean StillDown(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
/*
* WaitMouseUp()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern Boolean WaitMouseUp(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
/*
* KeyTranslate() *** DEPRECATED ***
*
* Deprecated:
* Use UCKeyTranslate instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern UInt32 KeyTranslate(const void *transData, UInt16 keycode, UInt32 *state)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
/*
* GetCaretTime()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern UInt32 GetCaretTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#endif /* !__LP64__ */
/*
QuickTime 3.0 supports GetKeys() on unix and win32
But, on little endian machines you will have to be
careful about bit numberings and/or use a KeyMapByteArray
instead.
*/
#if TARGET_API_MAC_OS8
typedef UInt32 KeyMap[4];
#else
typedef BigEndianUInt32 KeyMap[4];
#endif /* TARGET_API_MAC_OS8 */
typedef UInt8 KeyMapByteArray[16];
/*
* GetKeys()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework
* CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern void GetKeys(KeyMap theKeys) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
/* Obsolete event types & masks */
enum {
networkEvt = 10,
driverEvt = 11,
app1Evt = 12,
app2Evt = 13,
app3Evt = 14,
app4Evt = 15,
networkMask = 0x0400,
driverMask = 0x0800,
app1Mask = 0x1000,
app2Mask = 0x2000,
app3Mask = 0x4000,
app4Mask = 0x8000
};
struct EvQEl {
QElemPtr qLink;
SInt16 qType;
EventKind
evtQWhat; /* this part is identical to the EventRecord as defined above */
unsigned long evtQMessage;
UInt32 evtQWhen;
Point evtQWhere;
EventModifiers evtQModifiers;
};
typedef struct EvQEl EvQEl;
typedef EvQEl *EvQElPtr;
typedef CALLBACK_API(void, GetNextEventFilterProcPtr)(EventRecord *theEvent,
Boolean *result);
typedef STACK_UPP_TYPE(GetNextEventFilterProcPtr) GetNextEventFilterUPP;
/*
* NewGetNextEventFilterUPP()
*
* Availability:
* Mac OS X: not available
* CarbonLib: not available
* Non-Carbon CFM: available as macro/inline
*/
/*
* DisposeGetNextEventFilterUPP()
*
* Availability:
* Mac OS X: not available
* CarbonLib: not available
* Non-Carbon CFM: available as macro/inline
*/
/*
* InvokeGetNextEventFilterUPP()
*
* Availability:
* Mac OS X: not available
* CarbonLib: not available
* Non-Carbon CFM: available as macro/inline
*/
typedef GetNextEventFilterUPP GNEFilterUPP;
#if !__LP64__
/*
* GetDblTime()
*
* Summary:
* Returns the maximum time (in units of 1/60th of a second) allowed
* between two consecutive mouse-down events in order for the second
* click to be considered a double-click.
*
* Discussion:
* In 64-bit applications, you may replace calls to this API with
* calls to NXClickTime (declared in
* <IOKit/hidsystem/event_status_driver.h>) or with +[NSEvent
* doubleClickInterval] (available in Mac OS X 10.6 and later).
*
* Mac OS X threading:
* Not thread safe
*
* Result:
* The maximum time between mouse-downs allowed for a double-click.
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern UInt32 GetDblTime(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
/*
* SetEventMask()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern void SetEventMask(EventMask value)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
/*
* GetNextEvent() *** DEPRECATED ***
*
* Deprecated:
* Use ReceiveNextEvent instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern Boolean GetNextEvent(EventMask eventMask, EventRecord *theEvent)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
/*
* WaitNextEvent() *** DEPRECATED ***
*
* Deprecated:
* Use ReceiveNextEvent instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern Boolean WaitNextEvent(EventMask eventMask, EventRecord *theEvent,
UInt32 sleep, RgnHandle mouseRgn) /* can be NULL */
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
/*
* EventAvail() *** DEPRECATED ***
*
* Deprecated:
* Use FindSpecificEventInQueue instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern Boolean EventAvail(EventMask eventMask, EventRecord *theEvent)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
/*
* PostEvent() *** DEPRECATED ***
*
* Deprecated:
* Use PostEventToQueue or CGEventPost instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern OSErr PostEvent(EventKind eventNum, UInt32 eventMsg)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
#endif /* !__LP64__ */
/*
* FlushEvents() *** DEPRECATED ***
*
* Deprecated:
* Use FlushEventsMatchingListFromQueue,
* FlushSpecificEventsFromQueue, or FlushEventQueue instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework but
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
* CFM: in InterfaceLib 7.1 and later
*/
extern void FlushEvents(EventMask whichMask, EventMask stopMask)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
#if OLDROUTINENAMES
#define KeyTrans(transData, keycode, state) \
KeyTranslate(transData, keycode, state)
#endif /* OLDROUTINENAMES */
#if !__LP64__
/*
* KeyScript() *** DEPRECATED ***
*
* Deprecated:
* Use TISSelectInputSource API for positive verbs (ScriptCode).
* Use TSMDocument properties to restrict input sources:
* kTSMDocumentEnabledInputSourcesPropertyTag
* kTSMDocumentInputSourceOverridePropertyTag
*
* Summary:
* Switch to the specified script's default (last used) input source.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] but deprecated in 10.5 CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern void KeyScript(short code)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_5;
#endif /* !__LP64__ */
/*
* IsCmdChar() *** DEPRECATED ***
*
* Deprecated:
* Use IsUserCancelEventRef or CheckEventQueueForUserCancel instead.
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework but
* deprecated in 10.6 CarbonLib: in CarbonLib 1.0 and later Non-Carbon
* CFM: in InterfaceLib 7.1 and later
*/
extern Boolean IsCmdChar(const EventRecord *event, short test)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_6;
/*
LowMem accessor functions previously in LowMem.h
*/
/*
* LMGetKeyThresh()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework
* CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern SInt16 LMGetKeyThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#if !__LP64__
/*
* LMSetKeyThresh()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern void LMSetKeyThresh(SInt16 value)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#endif /* !__LP64__ */
/*
* LMGetKeyRepThresh()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework
* CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern SInt16 LMGetKeyRepThresh(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#if !__LP64__
/*
* LMSetKeyRepThresh()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern void LMSetKeyRepThresh(SInt16 value)
AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#endif /* !__LP64__ */
/*
* LMGetKbdLast()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework
* CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern UInt8 LMGetKbdLast(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#if !__LP64__
/*
* LMSetKbdLast()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern void LMSetKbdLast(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#endif /* !__LP64__ */
/*
* LMGetKbdType()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework
* CarbonLib: in CarbonLib 1.0 and later
* Non-Carbon CFM: in InterfaceLib 7.1 and later
*/
extern UInt8 LMGetKbdType(void) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#if !__LP64__
/*
* LMSetKbdType()
*
* Mac OS X threading:
* Not thread safe
*
* Availability:
* Mac OS X: in version 10.0 and later in Carbon.framework [32-bit
* only] CarbonLib: in CarbonLib 1.0 and later Non-Carbon CFM: in
* InterfaceLib 7.1 and later
*/
extern void LMSetKbdType(UInt8 value) AVAILABLE_MAC_OS_X_VERSION_10_0_AND_LATER;
#endif /* !__LP64__ */
#pragma pack(pop)
#ifdef __cplusplus
}
#endif
#endif /* __EVENTS__ */

View File

@@ -1,127 +1,127 @@
#include "mouse_controller.h"
#include "log.h"
#include <X11/extensions/XTest.h>
#include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {}
MouseController::~MouseController() {
if (uinput_fd_) {
ioctl(uinput_fd_, UI_DEV_DESTROY);
close(uinput_fd_);
}
}
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 res_uidev = write(uinput_fd_, &uidev, sizeof(uidev));
ioctl(uinput_fd_, UI_DEV_CREATE);
return 0;
}
int MouseController::Destroy() { return 0; }
int MouseController::SendCommand(RemoteAction remote_action) {
int mouse_pos_x = remote_action.m.x;
int mouse_pos_y = remote_action.m.y;
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 event_base, error_base, major_version, minor_version;
if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version,
&minor_version)) {
LOG_ERROR("XTest extension not available");
XCloseDisplay(display_);
return -2;
}
return 0;
}
void MouseController::SimulateKeyDown(int fd, int kval) {
int res_ev = 0;
struct input_event event;
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, 0);
event.type = EV_KEY;
event.value = 1;
event.code = kval;
res_ev = write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
res_ev = write(fd, &event, sizeof(event));
int MouseController::Destroy() {
if (display_) {
XCloseDisplay(display_);
display_ = nullptr;
}
return 0;
}
void MouseController::SimulateKeyUp(int fd, int kval) {
int res_ev = 0;
struct input_event event;
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, 0);
event.type = EV_KEY;
event.value = 0;
event.code = kval;
res_ev = write(fd, &event, sizeof(event));
event.type = EV_SYN;
event.value = 0;
event.code = SYN_REPORT;
res_ev = write(fd, &event, sizeof(event));
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;
}
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));
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));
return 0;
}
void MouseController::SetMousePosition(int x, int y) {
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
XFlush(display_);
}
void MouseController::SimulateKeyDown(int kval) {
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
XFlush(display_);
}
void MouseController::SimulateKeyUp(int kval) {
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
XFlush(display_);
}
void MouseController::SimulateMouseWheel(int direction_button, int count) {
for (int i = 0; i < count; ++i) {
XTestFakeButtonEvent(display_, direction_button, True, CurrentTime);
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
}
XFlush(display_);
}
} // namespace crossdesk

View File

@@ -1,41 +1,43 @@
/*
* @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"
namespace crossdesk {
class MouseController : public DeviceController {
public:
MouseController();
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;
};
} // namespace crossdesk
#endif

View File

@@ -4,51 +4,101 @@
#include "rd_log.h"
namespace crossdesk {
MouseController::MouseController() {}
MouseController::~MouseController() {}
int MouseController::Init(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
pixel_width_ =
CGDisplayModeGetPixelWidth(CGDisplayCopyDisplayMode(CGMainDisplayID()));
pixel_height_ =
CGDisplayModeGetPixelHeight(CGDisplayCopyDisplayMode(CGMainDisplayID()));
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_ / pixel_width_;
int mouse_pos_y = remote_action.m.y * screen_height_ / pixel_height_;
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) {
switch (remote_action.m.flag) {
case MouseFlag::left_down:
mouse_type = kCGEventLeftMouseDown;
} else if (remote_action.m.flag == MouseFlag::left_up) {
left_dragging_ = true;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonLeft);
break;
case MouseFlag::left_up:
mouse_type = kCGEventLeftMouseUp;
} else if (remote_action.m.flag == MouseFlag::right_down) {
left_dragging_ = false;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonLeft);
break;
case MouseFlag::right_down:
mouse_type = kCGEventRightMouseDown;
} else if (remote_action.m.flag == MouseFlag::right_up) {
right_dragging_ = true;
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
kCGMouseButtonRight);
break;
case MouseFlag::right_up:
mouse_type = kCGEventRightMouseUp;
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,
CGPointMake(mouse_pos_x, mouse_pos_y),
kCGMouseButtonLeft);
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
mouse_button);
break;
}
if (mouse_event) {
CGEventPost(kCGHIDEventTap, mouse_event);
CFRelease(mouse_event);
}
}
return 0;
}
} // namespace crossdesk

View File

@@ -7,24 +7,26 @@
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <vector>
#include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController {
public:
MouseController();
virtual ~MouseController();
public:
virtual int Init(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;
int pixel_width_ = 0;
int pixel_height_ = 0;
std::vector<DisplayInfo> display_info_list_;
bool left_dragging_ = false;
bool right_dragging_ = false;
};
} // namespace crossdesk
#endif

View File

@@ -2,38 +2,65 @@
#include "rd_log.h"
namespace crossdesk {
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;
ip.mi.dy = remote_action.m.y;
if (remote_action.m.flag == MouseFlag::left_down) {
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;
} else if (remote_action.m.flag == MouseFlag::left_up) {
break;
case MouseFlag::left_up:
ip.mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
} else if (remote_action.m.flag == MouseFlag::right_down) {
break;
case MouseFlag::right_down:
ip.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE;
} else if (remote_action.m.flag == MouseFlag::right_up) {
break;
case MouseFlag::right_up:
ip.mi.dwFlags = MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE;
} else {
break;
case MouseFlag::middle_down:
ip.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::middle_up:
ip.mi.dwFlags = MOUSEEVENTF_MIDDLEUP | MOUSEEVENTF_ABSOLUTE;
break;
case MouseFlag::wheel_vertical:
ip.mi.dwFlags = MOUSEEVENTF_WHEEL;
ip.mi.mouseData = remote_action.m.s * 120;
break;
case MouseFlag::wheel_horizontal:
ip.mi.dwFlags = MOUSEEVENTF_HWHEEL;
ip.mi.mouseData = remote_action.m.s * 120;
break;
default:
ip.mi.dwFlags = MOUSEEVENTF_MOVE;
break;
}
ip.mi.mouseData = 0;
ip.mi.time = 0;
SetCursorPos(ip.mi.dx, ip.mi.dy);
@@ -45,3 +72,4 @@ int MouseController::SendCommand(RemoteAction remote_action) {
return 0;
}
} // namespace crossdesk

View File

@@ -7,21 +7,24 @@
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <vector>
#include "device_controller.h"
namespace crossdesk {
class MouseController : public DeviceController {
public:
MouseController();
virtual ~MouseController();
public:
virtual int Init(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_;
};
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,206 @@
// virtual_key_codes.h
#ifndef VIRTUAL_KEY_CODES_H
#define VIRTUAL_KEY_CODES_H
#define VK_LBUTTON 0x01 // Left mouse button
#define VK_RBUTTON 0x02 // Right mouse button
#define VK_CANCEL 0x03 // Control-break processing
#define VK_MBUTTON 0x04 // Middle mouse button
#define VK_XBUTTON1 0x05 // X1 mouse button
#define VK_XBUTTON2 0x06 // X2 mouse button
// 0x07 Reserved
#define VK_BACK 0x08 // Backspace key
#define VK_TAB 0x09 // Tab key
// 0x0A-0B Reserved
#define VK_CLEAR 0x0C // Clear key
#define VK_RETURN 0x0D // Enter key
// 0x0E-0F Unassigned
#define VK_SHIFT 0x10 // Shift key
#define VK_CONTROL 0x11 // Ctrl key
#define VK_MENU 0x12 // Alt key
#define VK_PAUSE 0x13 // Pause key
#define VK_CAPITAL 0x14 // Caps lock key
#define VK_KANA 0x15 // IME Kana mode
#define VK_HANGUL 0x15 // IME Hangul mode
#define VK_IME_ON 0x16 // IME On
#define VK_JUNJA 0x17 // IME Junja mode
#define VK_FINAL 0x18 // IME final mode
#define VK_HANJA 0x19 // IME Hanja mode
#define VK_KANJI 0x19 // IME Kanji mode
#define VK_IME_OFF 0x1A // IME Off
#define VK_ESCAPE 0x1B // Esc key
#define VK_CONVERT 0x1C // IME convert
#define VK_NONCONVERT 0x1D // IME nonconvert
#define VK_ACCEPT 0x1E // IME accept
#define VK_MODECHANGE 0x1F // IME mode change request
#define VK_SPACE 0x20 // Spacebar key
#define VK_PRIOR 0x21 // Page up key
#define VK_NEXT 0x22 // Page down key
#define VK_END 0x23 // End key
#define VK_HOME 0x24 // Home key
#define VK_LEFT 0x25 // Left arrow key
#define VK_UP 0x26 // Up arrow key
#define VK_RIGHT 0x27 // Right arrow key
#define VK_DOWN 0x28 // Down arrow key
#define VK_SELECT 0x29 // Select key
#define VK_PRINT 0x2A // Print key
#define VK_EXECUTE 0x2B // Execute key
#define VK_SNAPSHOT 0x2C // Print screen key
#define VK_INSERT 0x2D // Insert key
#define VK_DELETE 0x2E // Delete key
#define VK_HELP 0x2F // Help key
#define VK_0 0x30 // 0 key
#define VK_1 0x31 // 1 key
#define VK_2 0x32 // 2 key
#define VK_3 0x33 // 3 key
#define VK_4 0x34 // 4 key
#define VK_5 0x35 // 5 key
#define VK_6 0x36 // 6 key
#define VK_7 0x37 // 7 key
#define VK_8 0x38 // 8 key
#define VK_9 0x39 // 9 key
// 0x3A-40 Undefined
#define VK_A 0x41 // A key
#define VK_B 0x42 // B key
#define VK_C 0x43 // C key
#define VK_D 0x44 // D key
#define VK_E 0x45 // E key
#define VK_F 0x46 // F key
#define VK_G 0x47 // G key
#define VK_H 0x48 // H key
#define VK_I 0x49 // I key
#define VK_J 0x4A // J key
#define VK_K 0x4B // K key
#define VK_L 0x4C // L key
#define VK_M 0x4D // M key
#define VK_N 0x4E // N key
#define VK_O 0x4F // O key
#define VK_P 0x50 // P key
#define VK_Q 0x51 // Q key
#define VK_R 0x52 // R key
#define VK_S 0x53 // S key
#define VK_T 0x54 // T key
#define VK_U 0x55 // U key
#define VK_V 0x56 // V key
#define VK_W 0x57 // W key
#define VK_X 0x58 // X key
#define VK_Y 0x59 // Y key
#define VK_Z 0x5A // Z key
#define VK_LWIN 0x5B // Left Windows logo key
#define VK_RWIN 0x5C // Right Windows logo key
#define VK_APPS 0x5D // Application key
// 0x5E Reserved
#define VK_SLEEP 0x5F // Computer Sleep key
#define VK_NUMPAD0 0x60 // Numeric keypad 0 key
#define VK_NUMPAD1 0x61 // Numeric keypad 1 key
#define VK_NUMPAD2 0x62 // Numeric keypad 2 key
#define VK_NUMPAD3 0x63 // Numeric keypad 3 key
#define VK_NUMPAD4 0x64 // Numeric keypad 4 key
#define VK_NUMPAD5 0x65 // Numeric keypad 5 key
#define VK_NUMPAD6 0x66 // Numeric keypad 6 key
#define VK_NUMPAD7 0x67 // Numeric keypad 7 key
#define VK_NUMPAD8 0x68 // Numeric keypad 8 key
#define VK_NUMPAD9 0x69 // Numeric keypad 9 key
#define VK_MULTIPLY 0x6A // Multiply key
#define VK_ADD 0x6B // Add key
#define VK_SEPARATOR 0x6C // Separator key
#define VK_SUBTRACT 0x6D // Subtract key
#define VK_DECIMAL 0x6E // Decimal key
#define VK_DIVIDE 0x6F // Divide key
#define VK_F1 0x70 // F1 key
#define VK_F2 0x71 // F2 key
#define VK_F3 0x72 // F3 key
#define VK_F4 0x73 // F4 key
#define VK_F5 0x74 // F5 key
#define VK_F6 0x75 // F6 key
#define VK_F7 0x76 // F7 key
#define VK_F8 0x77 // F8 key
#define VK_F9 0x78 // F9 key
#define VK_F10 0x79 // F10 key
#define VK_F11 0x7A // F11 key
#define VK_F12 0x7B // F12 key
#define VK_F13 0x7C // F13 key
#define VK_F14 0x7D // F14 key
#define VK_F15 0x7E // F15 key
#define VK_F16 0x7F // F16 key
#define VK_F17 0x80 // F17 key
#define VK_F18 0x81 // F18 key
#define VK_F19 0x82 // F19 key
#define VK_F20 0x83 // F20 key
#define VK_F21 0x84 // F21 key
#define VK_F22 0x85 // F22 key
#define VK_F23 0x86 // F23 key
#define VK_F24 0x87 // F24 key
// 0x880x8F Reserved
#define VK_NUMLOCK 0x90 // Num lock key
#define VK_SCROLL 0x91 // Scroll lock key
// 0x920x96 OEM specific
// 0x970x9F Unassigned
#define VK_LSHIFT 0xA0 // Left Shift key
#define VK_RSHIFT 0xA1 // Right Shift key
#define VK_LCONTROL 0xA2 // Left Ctrl key
#define VK_RCONTROL 0xA3 // Right Ctrl key
#define VK_LMENU 0xA4 // Left Alt key
#define VK_RMENU 0xA5 // Right Alt key
#define VK_BROWSER_BACK 0xA6 // Browser Back key
#define VK_BROWSER_FORWARD 0xA7 // Browser Forward key
#define VK_BROWSER_REFRESH 0xA8 // Browser Refresh key
#define VK_BROWSER_STOP 0xA9 // Browser Stop key
#define VK_BROWSER_SEARCH 0xAA // Browser Search key
#define VK_BROWSER_FAVORITES 0xAB // Browser Favorites key
#define VK_BROWSER_HOME 0xAC // Browser Start and Home key
#define VK_VOLUME_MUTE 0xAD // Volume Mute key
#define VK_VOLUME_DOWN 0xAE // Volume Down key
#define VK_VOLUME_UP 0xAF // Volume Up key
#define VK_MEDIA_NEXT_TRACK 0xB0 // Next Track key
#define VK_MEDIA_PREV_TRACK 0xB1 // Previous Track key
#define VK_MEDIA_STOP 0xB2 // Stop Media key
#define VK_MEDIA_PLAY_PAUSE 0xB3 // Play/Pause Media key
#define VK_LAUNCH_MAIL 0xB4 // Start Mail key
#define VK_LAUNCH_MEDIA_SELECT 0xB5 // Select Media key
#define VK_LAUNCH_APP1 0xB6 // Start Application 1 key
#define VK_LAUNCH_APP2 0xB7 // Start Application 2 key
// 0xB80xB9 Reserved
#define VK_OEM_1 0xBA // For US: Semicolon/Colon key
#define VK_OEM_PLUS 0xBB // Equals/Plus key
#define VK_OEM_COMMA 0xBC // Comma/Less Than key
#define VK_OEM_MINUS 0xBD // Dash/Underscore key
#define VK_OEM_PERIOD 0xBE // Period/Greater Than key
#define VK_OEM_2 0xBF // Slash/Question Mark key
#define VK_OEM_3 0xC0 // Grave Accent/Tilde key
// 0xC10xDA Reserved
#define VK_OEM_4 0xDB // Left Brace key
#define VK_OEM_5 0xDC // Backslash/Pipe key
#define VK_OEM_6 0xDD // Right Brace key
#define VK_OEM_7 0xDE // Apostrophe/Quote key
#define VK_OEM_8 0xDF // (Canadian CSA: Right Ctrl key)
// 0xE0 Reserved
#define VK_OEM_102 0xE2 // (European ISO: Backslash/Pipe key)
// 0xE3E4 OEM specific
#define VK_PROCESSKEY 0xE5 // IME PROCESS key
// 0xE6 OEM specific
#define VK_PACKET 0xE7 // Unicode characters as keystrokes
// 0xE8 Unassigned
// 0xE9F5 OEM specific
#define VK_ATTN 0xF6 // Attn key
#define VK_CRSEL 0xF7 // CrSel key
#define VK_EXSEL 0xF8 // ExSel key
#define VK_EREOF 0xF9 // Erase EOF key
#define VK_PLAY 0xFA // Play key
#define VK_ZOOM 0xFB // Zoom key
#define VK_NONAME 0xFC // Reserved
#define VK_PA1 0xFD // PA1 key
#define VK_OEM_CLEAR 0xFE // Clear key
#endif // VIRTUAL_KEY_CODES_H

File diff suppressed because it is too large Load Diff

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,63 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-06-14
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _LAYOUT_STYLE_H_
#define _LAYOUT_STYLE_H_
#define MENU_WINDOW_WIDTH_CN 300
#define MENU_WINDOW_HEIGHT_CN 280
#define LOCAL_WINDOW_WIDTH_CN 300
#define LOCAL_WINDOW_HEIGHT_CN 280
#define REMOTE_WINDOW_WIDTH_CN 300
#define REMOTE_WINDOW_HEIGHT_CN 280
#define MENU_WINDOW_WIDTH_EN 190
#define MENU_WINDOW_HEIGHT_EN 245
#define IPUT_WINDOW_WIDTH 160
#define INPUT_WINDOW_PADDING_CN 66
#define INPUT_WINDOW_PADDING_EN 96
#define SETTINGS_WINDOW_WIDTH_CN 202
#define SETTINGS_WINDOW_WIDTH_EN 248
#if _WIN32
#define SETTINGS_WINDOW_HEIGHT_CN 345
#define SETTINGS_WINDOW_HEIGHT_EN 345
#else
#define SETTINGS_WINDOW_HEIGHT_CN 315
#define SETTINGS_WINDOW_HEIGHT_EN 315
#endif
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN 228
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN 275
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN 195
#define SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN 195
#define LANGUAGE_SELECT_WINDOW_PADDING_CN 120
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 120
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 120
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 120
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 171
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218
#define ENABLE_TURN_CHECKBOX_PADDING_CN 171
#define ENABLE_TURN_CHECKBOX_PADDING_EN 218
#define ENABLE_SRTP_CHECKBOX_PADDING_CN 171
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN 171
#define ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN 218
#define ENABLE_MINIZE_TO_TRAY_PADDING_CN 171
#define ENABLE_MINIZE_TO_TRAY_PADDING_EN 218
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN 90
#define SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN 137
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN 90
#define SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN 137
#define SETTINGS_SELECT_WINDOW_WIDTH 73
#define SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH 130
#define SETTINGS_OK_BUTTON_PADDING_CN 65
#define SETTINGS_OK_BUTTON_PADDING_EN 83
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN 78
#define SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN 91
#endif

View File

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

View File

@@ -1,36 +1,44 @@
#include <random>
#include "IconsFontAwesome6.h"
#include "layout_style.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
int Render::LocalWindow() {
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Always);
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_, main_window_height_default_ -
title_bar_height_ - status_bar_height_),
ImGuiChildFlags_Border,
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::SetWindowFontScale(1.0f);
ImGui::Text(
"%s", localization::local_desktop[localization_language_index_].c_str());
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::PushStyleColor(ImGuiCol_ChildBg,
ImVec4(239.0 / 255, 240.0 / 255, 242.0 / 255, 1.0f));
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(330, 180),
ImGui::BeginChild(
"LocalDesktopWindow_1",
ImVec2(local_child_window_width_, local_child_window_height_),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoTitleBar |
@@ -38,7 +46,7 @@ int Render::LocalWindow() {
ImGui::PopStyleVar();
ImGui::PopStyleColor();
{
ImGui::SetWindowFontScale(0.5f);
ImGui::SetWindowFontScale(0.8f);
ImGui::Text("%s",
localization::local_id[localization_language_index_].c_str());
@@ -48,34 +56,35 @@ int Render::LocalWindow() {
ImGui::SetWindowFontScale(1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
char client_id_display[12] = "";
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];
client_id_display_[j] = client_id_[i];
if (i == 2 || i == 5) {
client_id_display[++j] = ' ';
client_id_display_[++j] = ' ';
}
}
}
ImGui::InputText(
"##local_id", client_id_display, IM_ARRAYSIZE(client_id_display),
"##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))) {
if (ImGui::Button(ICON_FA_COPY, ImVec2(22, 38))) {
local_id_copied_ = true;
ImGui::SetClipboardText(client_id_);
copy_start_time_ = ImGui::GetTime();
}
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor(3);
auto time_duration = ImGui::GetTime() - copy_start_time_;
double time_duration = ImGui::GetTime() - copy_start_time_;
if (local_id_copied_ && time_duration < 1.0f) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(
@@ -88,8 +97,9 @@ int Render::LocalWindow() {
ImGui::SetNextWindowSize(
ImVec2(notification_window_width_, notification_window_height_));
ImGui::PushStyleColor(ImGuiCol_WindowBg,
ImVec4(1.0, 1.0, 1.0, 1.0 - time_duration));
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,
@@ -107,7 +117,7 @@ int Render::LocalWindow() {
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
ImGui::SetCursorPosY(window_height * 0.5f);
ImGui::PushStyleColor(ImGuiCol_Text,
ImVec4(0, 0, 0, 1.0 - time_duration));
ImVec4(0, 0, 0, 1.0f - (float)time_duration));
ImGui::Text("%s", text.c_str());
ImGui::PopStyleColor();
ImGui::SetWindowFontScale(1.0f);
@@ -115,41 +125,18 @@ int Render::LocalWindow() {
ImGui::End();
}
ImGui::SetWindowFontScale(1.0f);
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::SetWindowFontScale(0.5f);
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();
if (!password_inited_) {
char a[] = {
"123456789QWERTYUPASDFGHJKLZXCVBNMqwertyupasdfghijkzxcvbnm"};
std::mt19937 generator(
std::chrono::system_clock::now().time_since_epoch().count());
std::uniform_int_distribution<int> distribution(0, strlen(a) - 1);
random_password_.clear();
for (int i = 0, len = strlen(a); i < 6; i++) {
random_password_ += a[distribution(generator)];
}
password_inited_ = true;
if (0 != strcmp(random_password_.c_str(), password_saved_)) {
strncpy(password_saved_, random_password_.c_str(),
sizeof(password_saved_));
LOG_INFO("Generate new password and save into cache file");
SaveSettingsIntoCacheFile();
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::InputTextWithHint(
"##server_pwd",
@@ -166,7 +153,6 @@ int Render::LocalWindow() {
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;
@@ -186,21 +172,6 @@ int Render::LocalWindow() {
ImGui::SameLine();
if (ImGui::Button(
regenerate_password_ ? ICON_FA_SPINNER : ICON_FA_ARROWS_ROTATE,
ImVec2(22, 38))) {
regenerate_password_ = true;
password_inited_ = false;
regenerate_password_start_time_ = ImGui::GetTime();
LeaveConnection(peer_, client_id_);
is_create_connection_ = false;
}
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;
}
@@ -238,9 +209,9 @@ int Render::LocalWindow() {
auto window_height = ImGui::GetWindowSize().y;
std::string text =
localization::new_password[localization_language_index_];
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
ImGui::SetWindowFontScale(0.5f);
ImGui::SetCursorPosX((window_width - text_width / 2) * 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());
@@ -275,12 +246,22 @@ int Render::LocalWindow() {
focus_on_input_widget_ = true;
} else {
show_reset_password_window_ = false;
LOG_INFO("Generate new password and save into cache file");
strncpy(password_saved_, new_password_, sizeof(password_saved_));
memset(new_password_, 0, sizeof(new_password_));
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_);
is_create_connection_ = false;
DestroyPeer(&peer_);
focus_on_input_widget_ = true;
}
}
@@ -309,3 +290,4 @@ int Render::LocalWindow() {
return 0;
}
} // namespace crossdesk

View File

@@ -0,0 +1,289 @@
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
int Render::RecentConnectionsWindow() {
ImGui::SetNextWindowPos(
ImVec2(0, title_bar_height_ + local_window_height_ - 1.0f),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::BeginChild(
"RecentConnectionsWindow",
ImVec2(main_window_width_default_,
main_window_height_default_ - title_bar_height_ -
local_window_height_ - status_bar_height_ + 1.0f),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + main_window_text_y_padding_);
ImGui::Indent(main_child_window_x_padding_);
ImGui::TextColored(
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
localization::recent_connections[localization_language_index_].c_str());
ShowRecentConnections();
ImGui::EndChild();
return 0;
}
int Render::ShowRecentConnections() {
ImGui::SetCursorPosX(25.0f);
ImVec2 sub_window_pos = ImGui::GetCursorPos();
std::map<std::string, ImVec2> sub_containers_pos;
float recent_connection_sub_container_width =
recent_connection_image_width_ + 16.0f;
float recent_connection_sub_container_height =
recent_connection_image_height_ + 36.0f;
ImGui::PushStyleColor(ImGuiCol_ChildBg,
ImVec4(239.0f / 255, 240.0f / 255, 242.0f / 255, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
ImGui::BeginChild("RecentConnectionsContainer",
ImVec2(main_window_width_default_ - 50.0f, 145.0f),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_AlwaysHorizontalScrollbar |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
size_t recent_connections_count = recent_connections_.size();
int count = 0;
float button_width = 22;
float button_height = 22;
for (auto& it : recent_connections_) {
sub_containers_pos[it.first] = ImGui::GetCursorPos();
std::string recent_connection_sub_window_name =
"RecentConnectionsSubContainer" + it.first;
// recent connections sub container
ImGui::BeginChild(recent_connection_sub_window_name.c_str(),
ImVec2(recent_connection_sub_container_width,
recent_connection_sub_container_height),
ImGuiChildFlags_None,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoScrollbar);
std::string connection_info = it.first;
// remote id length is 9
// password length is 6
// connection_info -> remote_id + 'Y' + host_name + '@' + password
// -> remote_id + 'N' + host_name
if ('Y' == connection_info[9] && connection_info.size() >= 16) {
size_t pos_y = connection_info.find('Y');
size_t pos_at = connection_info.find('@');
if (pos_y == std::string::npos || pos_at == std::string::npos ||
pos_y >= pos_at) {
LOG_ERROR("Invalid filename");
continue;
}
it.second.remote_id = connection_info.substr(0, pos_y);
it.second.remote_host_name =
connection_info.substr(pos_y + 1, pos_at - pos_y - 1);
it.second.password = connection_info.substr(pos_at + 1);
it.second.remember_password = true;
} else if ('N' == connection_info[9] && connection_info.size() >= 10) {
size_t pos_n = connection_info.find('N');
size_t pos_at = connection_info.find('@');
if (pos_n == std::string::npos) {
LOG_ERROR("Invalid filename");
continue;
}
it.second.remote_id = connection_info.substr(0, pos_n);
it.second.remote_host_name = connection_info.substr(pos_n + 1);
it.second.password = "";
it.second.remember_password = false;
} else {
it.second.remote_host_name = "unknown";
}
ImVec2 image_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x + 5.0f,
ImGui::GetCursorScreenPos().y + 5.0f);
ImVec2 image_pos =
ImVec2(ImGui::GetCursorPosX() + 5.0f, ImGui::GetCursorPosY() + 5.0f);
ImGui::SetCursorPos(image_pos);
ImGui::Image((ImTextureID)(intptr_t)it.second.texture,
ImVec2((float)recent_connection_image_width_,
(float)recent_connection_image_height_));
// remote id display button
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0, 0, 0, 0.2f));
ImVec2 dummy_button_pos =
ImVec2(image_pos.x, image_pos.y + recent_connection_image_height_);
std::string dummy_button_name = "##DummyButton" + it.second.remote_id;
ImGui::SetCursorPos(dummy_button_pos);
ImGui::SetWindowFontScale(0.6f);
ImGui::Button(dummy_button_name.c_str(),
ImVec2(recent_connection_image_width_ - 2 * button_width,
button_height));
ImGui::SetWindowFontScale(1.0f);
ImGui::SetCursorPos(
ImVec2(dummy_button_pos.x + 2.0f, dummy_button_pos.y + 1.0f));
ImGui::SetWindowFontScale(0.65f);
ImGui::Text("%s", it.second.remote_id.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
ImGui::Text("%s", it.second.remote_host_name.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
}
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(1.0f, 1.0f, 1.0f, 0.7f));
ImGui::SetWindowFontScale(0.5f);
// trash button
{
ImVec2 trash_can_button_pos = ImVec2(
image_pos.x + recent_connection_image_width_ - 2 * button_width,
image_pos.y + recent_connection_image_height_);
ImGui::SetCursorPos(trash_can_button_pos);
std::string trash_can = ICON_FA_TRASH_CAN;
std::string recent_connection_delete_button_name =
trash_can + "##RecentConnectionDelete" +
std::to_string(trash_can_button_pos.x);
if (ImGui::Button(recent_connection_delete_button_name.c_str(),
ImVec2(button_width, button_height))) {
show_confirm_delete_connection_ = true;
delete_connection_name_ = it.first;
}
if (delete_connection_ && delete_connection_name_ == it.first) {
if (!thumbnail_->DeleteThumbnail(it.first)) {
reload_recent_connections_ = true;
delete_connection_ = false;
}
}
}
// connect button
{
ImVec2 connect_button_pos =
ImVec2(image_pos.x + recent_connection_image_width_ - button_width,
image_pos.y + recent_connection_image_height_);
ImGui::SetCursorPos(connect_button_pos);
std::string connect = ICON_FA_ARROW_RIGHT_LONG;
std::string connect_to_this_connection_button_name =
connect + "##ConnectionTo" + it.first;
if (ImGui::Button(connect_to_this_connection_button_name.c_str(),
ImVec2(button_width, button_height))) {
ConnectTo(it.second.remote_id, it.second.password.c_str(),
it.second.remember_password);
}
}
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor(3);
ImGui::EndChild();
if (count != recent_connections_count - 1) {
ImVec2 line_start =
ImVec2(image_screen_pos.x + recent_connection_image_width_ + 20.0f,
image_screen_pos.y);
ImVec2 line_end = ImVec2(
image_screen_pos.x + recent_connection_image_width_ + 20.0f,
image_screen_pos.y + recent_connection_image_height_ + button_height);
ImGui::GetWindowDrawList()->AddLine(line_start, line_end,
IM_COL32(0, 0, 0, 122), 1.0f);
}
count++;
ImGui::SameLine(0, count != recent_connections_count ? 26.0f : 0.0f);
}
ImGui::EndChild();
if (show_confirm_delete_connection_) {
ConfirmDeleteConnection();
}
return 0;
}
int Render::ConfirmDeleteConnection() {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
connection_status_window_width_) /
2,
(viewport->WorkSize.y - viewport->WorkPos.y -
connection_status_window_height_) /
2));
ImGui::SetNextWindowSize(ImVec2(connection_status_window_width_,
connection_status_window_height_));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::Begin("ConfirmDeleteConnectionWindow", nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoSavedSettings);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
std::string text =
localization::confirm_delete_connection[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width_ * 6 / 19);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
// ok
ImGui::SetWindowFontScale(0.5f);
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
delete_connection_ = true;
show_confirm_delete_connection_ = false;
}
ImGui::SameLine();
// cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
delete_connection_ = false;
show_confirm_delete_connection_ = false;
}
auto window_width = ImGui::GetWindowSize().x;
auto window_height = ImGui::GetWindowSize().y;
auto text_width = ImGui::CalcTextSize(text.c_str()).x;
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
ImGui::SetCursorPosY(window_height * 0.2f);
ImGui::Text("%s", text.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::End();
ImGui::PopStyleVar();
return 0;
}
} // namespace crossdesk

View File

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

1437
src/gui/render.cpp Normal file

File diff suppressed because it is too large Load Diff

474
src/gui/render.h Normal file
View File

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

548
src/gui/render_callback.cpp Normal file
View File

@@ -0,0 +1,548 @@
#include "device_controller.h"
#include "localization.h"
#include "platform.h"
#include "rd_log.h"
#include "render.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#ifdef DESK_PORT_DEBUG
#else
#define MOUSE_CONTROL 1
#endif
namespace crossdesk {
int Render::SendKeyCommand(int key_code, bool is_down) {
RemoteAction remote_action;
remote_action.type = ControlType::keyboard;
if (is_down) {
remote_action.k.flag = KeyFlag::key_down;
} else {
remote_action.k.flag = KeyFlag::key_up;
}
remote_action.k.key_value = key_code;
if (!controlled_remote_id_.empty()) {
if (client_properties_.find(controlled_remote_id_) !=
client_properties_.end()) {
auto props = client_properties_[controlled_remote_id_];
if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
}
}
}
return 0;
}
int Render::ProcessMouseEvent(const SDL_Event& event) {
controlled_remote_id_ = "";
int video_width, video_height = 0;
int render_width, render_height = 0;
float ratio_x, ratio_y = 0;
RemoteAction remote_action;
for (auto& it : client_properties_) {
auto props = it.second;
if (!props->control_mouse_) {
continue;
}
if (event.button.x >= props->stream_render_rect_.x &&
event.button.x <=
props->stream_render_rect_.x + props->stream_render_rect_.w &&
event.button.y >= props->stream_render_rect_.y &&
event.button.y <=
props->stream_render_rect_.y + props->stream_render_rect_.h) {
controlled_remote_id_ = it.first;
render_width = props->stream_render_rect_.w;
render_height = props->stream_render_rect_.h;
last_mouse_event.button.x = event.button.x;
last_mouse_event.button.y = event.button.y;
remote_action.m.x =
(float)(event.button.x - props->stream_render_rect_.x) / render_width;
remote_action.m.y =
(float)(event.button.y - props->stream_render_rect_.y) /
render_height;
if (SDL_EVENT_MOUSE_BUTTON_DOWN == event.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == event.button.button) {
remote_action.m.flag = MouseFlag::left_down;
} else if (SDL_BUTTON_RIGHT == event.button.button) {
remote_action.m.flag = MouseFlag::right_down;
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
remote_action.m.flag = MouseFlag::middle_down;
}
} else if (SDL_EVENT_MOUSE_BUTTON_UP == event.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == event.button.button) {
remote_action.m.flag = MouseFlag::left_up;
} else if (SDL_BUTTON_RIGHT == event.button.button) {
remote_action.m.flag = MouseFlag::right_up;
} else if (SDL_BUTTON_MIDDLE == event.button.button) {
remote_action.m.flag = MouseFlag::middle_up;
}
} else if (SDL_EVENT_MOUSE_MOTION == event.type) {
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::move;
}
if (props->control_bar_hovered_ || props->display_selectable_hovered_) {
remote_action.m.flag = MouseFlag::move;
}
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
} else if (SDL_EVENT_MOUSE_WHEEL == event.type &&
last_mouse_event.button.x >= props->stream_render_rect_.x &&
last_mouse_event.button.x <= props->stream_render_rect_.x +
props->stream_render_rect_.w &&
last_mouse_event.button.y >= props->stream_render_rect_.y &&
last_mouse_event.button.y <= props->stream_render_rect_.y +
props->stream_render_rect_.h) {
int scroll_x = event.wheel.x;
int scroll_y = event.wheel.y;
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
scroll_x = -scroll_x;
scroll_y = -scroll_y;
}
remote_action.type = ControlType::mouse;
if (scroll_x == 0) {
remote_action.m.flag = MouseFlag::wheel_vertical;
remote_action.m.s = scroll_y;
} else if (scroll_y == 0) {
remote_action.m.flag = MouseFlag::wheel_horizontal;
remote_action.m.s = scroll_x;
}
render_width = props->stream_render_rect_.w;
render_height = props->stream_render_rect_.h;
remote_action.m.x =
(float)(event.button.x - props->stream_render_rect_.x) / render_width;
remote_action.m.y =
(float)(event.button.y - props->stream_render_rect_.y) /
render_height;
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
}
}
return 0;
}
void Render::SdlCaptureAudioIn(void* userdata, Uint8* stream, int len) {
Render* render = (Render*)userdata;
if (!render) {
return;
}
if (1) {
for (auto it : render->client_properties_) {
auto props = it.second;
if (props->connection_status_ == ConnectionStatus::Connected) {
SendAudioFrame(props->peer_, (const char*)stream, len,
render->audio_label_.c_str());
}
}
} else {
memcpy(render->audio_buffer_, stream, len);
render->audio_len_ = len;
SDL_Delay(10);
render->audio_buffer_fresh_ = true;
}
}
void Render::SdlCaptureAudioOut([[maybe_unused]] void* userdata,
[[maybe_unused]] Uint8* stream,
[[maybe_unused]] int len) {
// Render *render = (Render *)userdata;
// for (auto it : render->client_properties_) {
// auto props = it.second;
// if (props->connection_status_ == SignalStatus::SignalConnected) {
// SendAudioFrame(props->peer_, (const char *)stream, len);
// }
// }
// if (!render->audio_buffer_fresh_) {
// return;
// }
// SDL_memset(stream, 0, len);
// if (render->audio_len_ == 0) {
// return;
// } else {
// }
// len = (len > render->audio_len_ ? render->audio_len_ : len);
// SDL_MixAudioFormat(stream, render->audio_buffer_, AUDIO_S16LSB, len,
// SDL_MIX_MAXVOLUME);
// render->audio_buffer_fresh_ = false;
}
void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
const char* user_id, size_t user_id_size,
void* user_data) {
Render* render = (Render*)user_data;
if (!render) {
return;
}
std::string remote_id(user_id, user_id_size);
if (render->client_properties_.find(remote_id) ==
render->client_properties_.end()) {
return;
}
SubStreamWindowProperties* props =
render->client_properties_.find(remote_id)->second.get();
if (props->connection_established_) {
if (!props->dst_buffer_) {
props->dst_buffer_capacity_ = video_frame->size;
props->dst_buffer_ = new unsigned char[video_frame->size];
}
if (props->dst_buffer_capacity_ < video_frame->size) {
delete props->dst_buffer_;
props->dst_buffer_capacity_ = video_frame->size;
props->dst_buffer_ = new unsigned char[video_frame->size];
}
memcpy(props->dst_buffer_, video_frame->data, video_frame->size);
bool need_to_update_render_rect = false;
if (props->video_width_ != props->video_width_last_ ||
props->video_height_ != props->video_height_last_) {
need_to_update_render_rect = true;
props->video_width_last_ = props->video_width_;
props->video_height_last_ = props->video_height_;
}
props->video_width_ = video_frame->width;
props->video_height_ = video_frame->height;
props->video_size_ = video_frame->size;
if (need_to_update_render_rect) {
render->UpdateRenderRect();
}
SDL_Event event;
event.type = render->STREAM_REFRESH_EVENT;
event.user.data1 = props;
SDL_PushEvent(&event);
props->streaming_ = true;
if (props->net_traffic_stats_button_pressed_) {
props->frame_count_++;
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - props->last_time_)
.count();
if (elapsed >= 1000) {
props->fps_ = props->frame_count_ * 1000 / elapsed;
props->frame_count_ = 0;
props->last_time_ = now;
}
}
}
}
void Render::OnReceiveAudioBufferCb(const char* data, size_t size,
const char* user_id, size_t user_id_size,
void* user_data) {
Render* render = (Render*)user_data;
if (!render) {
return;
}
render->audio_buffer_fresh_ = true;
if (render->output_stream_) {
int pushed = SDL_PutAudioStreamData(
render->output_stream_, (const Uint8*)data, static_cast<int>(size));
if (pushed < 0) {
LOG_ERROR("Failed to push audio data: {}", SDL_GetError());
}
}
}
void Render::OnReceiveDataBufferCb(const char* data, size_t size,
const char* user_id, size_t user_id_size,
void* user_data) {
Render* render = (Render*)user_data;
if (!render) {
return;
}
RemoteAction remote_action;
memcpy(&remote_action, data, size);
std::string remote_id(user_id, user_id_size);
if (render->client_properties_.find(remote_id) !=
render->client_properties_.end()) {
// local
auto props = render->client_properties_.find(remote_id)->second;
RemoteAction host_info;
if (DeserializeRemoteAction(data, size, host_info)) {
if (ControlType::host_infomation == host_info.type &&
props->remote_host_name_.empty()) {
props->remote_host_name_ =
std::string(host_info.i.host_name, host_info.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
for (int i = 0; i < host_info.i.display_num; i++) {
props->display_info_list_.push_back(DisplayInfo(
std::string(host_info.i.display_list[i]), host_info.i.left[i],
host_info.i.top[i], host_info.i.right[i], host_info.i.bottom[i]));
LOG_INFO("Remote display [{}:{}], bound [({}, {}) ({}, {})]", i + 1,
props->display_info_list_[i].name,
props->display_info_list_[i].left,
props->display_info_list_[i].top,
props->display_info_list_[i].right,
props->display_info_list_[i].bottom);
}
}
} else {
props->remote_host_name_ = std::string(remote_action.i.host_name,
remote_action.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
LOG_ERROR("No remote display detected");
}
FreeRemoteAction(host_info);
} else {
// remote
if (ControlType::mouse == remote_action.type && render->mouse_controller_) {
render->mouse_controller_->SendMouseCommand(remote_action,
render->selected_display_);
} else if (ControlType::audio_capture == remote_action.type) {
if (remote_action.a) {
render->StartSpeakerCapturer();
render->audio_capture_ = true;
} else {
render->StopSpeakerCapturer();
render->audio_capture_ = false;
}
} else if (ControlType::keyboard == remote_action.type &&
render->keyboard_capturer_) {
render->keyboard_capturer_->SendKeyboardCommand(
(int)remote_action.k.key_value,
remote_action.k.flag == KeyFlag::key_down);
} else if (ControlType::display_id == remote_action.type) {
if (render->screen_capturer_) {
render->selected_display_ = remote_action.d;
render->screen_capturer_->SwitchTo(remote_action.d);
}
}
}
}
void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
size_t user_id_size, void* user_data) {
Render* render = (Render*)user_data;
if (!render) {
return;
}
std::string client_id(user_id, user_id_size);
if (client_id == render->client_id_) {
render->signal_status_ = status;
if (SignalStatus::SignalConnecting == status) {
render->signal_connected_ = false;
} else if (SignalStatus::SignalConnected == status) {
render->signal_connected_ = true;
LOG_INFO("[{}] connected to signal server", client_id);
} else if (SignalStatus::SignalFailed == status) {
render->signal_connected_ = false;
} else if (SignalStatus::SignalClosed == status) {
render->signal_connected_ = false;
} else if (SignalStatus::SignalReconnecting == status) {
render->signal_connected_ = false;
} else if (SignalStatus::SignalServerClosed == status) {
render->signal_connected_ = false;
}
} else {
if (client_id.rfind("C-", 0) != 0) {
return;
}
std::string remote_id(client_id.begin() + 2, client_id.end());
if (render->client_properties_.find(remote_id) ==
render->client_properties_.end()) {
return;
}
auto props = render->client_properties_.find(remote_id)->second;
props->signal_status_ = status;
if (SignalStatus::SignalConnecting == status) {
props->signal_connected_ = false;
} else if (SignalStatus::SignalConnected == status) {
props->signal_connected_ = true;
LOG_INFO("[{}] connected to signal server", remote_id);
} else if (SignalStatus::SignalFailed == status) {
props->signal_connected_ = false;
} else if (SignalStatus::SignalClosed == status) {
props->signal_connected_ = false;
} else if (SignalStatus::SignalReconnecting == status) {
props->signal_connected_ = false;
} else if (SignalStatus::SignalServerClosed == status) {
props->signal_connected_ = false;
}
}
}
void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
const size_t user_id_size, void* user_data) {
Render* render = (Render*)user_data;
if (!render) return;
std::string remote_id(user_id, user_id_size);
auto it = render->client_properties_.find(remote_id);
auto props = (it != render->client_properties_.end()) ? it->second : nullptr;
if (props) {
render->is_client_mode_ = true;
render->show_connection_status_window_ = true;
props->connection_status_ = status;
switch (status) {
case ConnectionStatus::Connected:
if (!render->need_to_create_stream_window_ &&
!render->client_properties_.empty()) {
render->need_to_create_stream_window_ = true;
}
props->connection_established_ = true;
props->stream_render_rect_ = {
0, (int)render->title_bar_height_,
(int)render->stream_window_width_,
(int)(render->stream_window_height_ - render->title_bar_height_)};
break;
case ConnectionStatus::Disconnected:
case ConnectionStatus::Failed:
case ConnectionStatus::Closed:
render->password_validating_time_ = 0;
render->start_screen_capturer_ = false;
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->control_mouse_ = false;
props->connection_established_ = false;
props->mouse_control_button_pressed_ = false;
if (props->dst_buffer_) {
memset(props->dst_buffer_, 0, props->dst_buffer_capacity_);
SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_,
props->texture_width_);
}
render->CleanSubStreamWindowProperties(props);
break;
case ConnectionStatus::IncorrectPassword:
render->password_validating_ = false;
render->password_validating_time_++;
if (render->connect_button_pressed_) {
render->connect_button_pressed_ = false;
props->connection_established_ = false;
render->connect_button_label_ =
localization::connect[render->localization_language_index_];
}
break;
case ConnectionStatus::NoSuchTransmissionId:
if (render->connect_button_pressed_) {
props->connection_established_ = false;
render->connect_button_label_ =
localization::connect[render->localization_language_index_];
}
break;
default:
break;
}
} else {
render->is_client_mode_ = false;
render->show_connection_status_window_ = true;
switch (status) {
case ConnectionStatus::Connected:
render->need_to_send_host_info_ = true;
render->start_screen_capturer_ = true;
render->start_mouse_controller_ = true;
break;
case ConnectionStatus::Closed:
render->start_screen_capturer_ = false;
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->need_to_send_host_info_ = false;
if (props) props->connection_established_ = false;
if (render->audio_capture_) {
render->StopSpeakerCapturer();
render->audio_capture_ = false;
}
break;
default:
break;
}
}
}
void Render::NetStatusReport(const char* client_id, size_t client_id_size,
TraversalMode mode,
const XNetTrafficStats* net_traffic_stats,
const char* user_id, const size_t user_id_size,
void* user_data) {
Render* render = (Render*)user_data;
if (!render) {
return;
}
if (strchr(client_id, '@') != nullptr && strchr(user_id, '-') == nullptr) {
std::string id, password;
const char* at_pos = strchr(client_id, '@');
if (at_pos == nullptr) {
id = client_id;
password.clear();
} else {
id.assign(client_id, at_pos - client_id);
password = at_pos + 1;
}
memset(&render->client_id_, 0, sizeof(render->client_id_));
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
strncpy(render->password_saved_, password.c_str(),
sizeof(render->password_saved_) - 1);
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
memset(&render->client_id_with_password_, 0,
sizeof(render->client_id_with_password_));
strncpy(render->client_id_with_password_, client_id,
sizeof(render->client_id_with_password_) - 1);
render->client_id_with_password_[sizeof(render->client_id_with_password_) -
1] = '\0';
LOG_INFO("Use client id [{}] and save id into cache file", id);
render->SaveSettingsIntoCacheFile();
}
std::string remote_id(user_id, user_id_size);
if (render->client_properties_.find(remote_id) ==
render->client_properties_.end()) {
return;
}
auto props = render->client_properties_.find(remote_id)->second;
if (props->traversal_mode_ != mode) {
props->traversal_mode_ = mode;
LOG_INFO("Net mode: [{}]", int(props->traversal_mode_));
}
if (!net_traffic_stats) {
return;
}
// only display client side net status if connected to itself
if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) {
props->net_traffic_stats_ = *net_traffic_stats;
}
}
} // namespace crossdesk

View File

@@ -0,0 +1,329 @@
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
int CountDigits(int number) {
if (number == 0) return 1;
return (int)std::floor(std::log10(std::abs(number))) + 1;
}
int BitrateDisplay(int bitrate) {
int num_of_digits = CountDigits(bitrate);
if (num_of_digits <= 3) {
ImGui::Text("%d bps", bitrate);
} else if (num_of_digits > 3 && num_of_digits <= 6) {
ImGui::Text("%d kbps", bitrate / 1000);
} else {
ImGui::Text("%.1f mbps", bitrate / 1000000.0f);
}
return 0;
}
int LossRateDisplay(float loss_rate) {
if (loss_rate < 0.01f) {
ImGui::Text("0%%");
} else {
ImGui::Text("%.0f%%", loss_rate * 100);
}
return 0;
}
int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
if (props->control_bar_expand_) {
ImGui::SetCursorPosX(props->is_control_bar_in_left_
? (props->control_window_width_ + 5.0f)
: 38.0f);
// mouse control button
ImDrawList* draw_list = ImGui::GetWindowDrawList();
if (props->is_control_bar_in_left_) {
draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
ImGui::GetCursorScreenPos().y - 7.0f),
ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
ImGui::GetCursorScreenPos().y - 7.0f +
props->control_window_height_),
IM_COL32(178, 178, 178, 255), 1.0f);
}
std::string display = ICON_FA_DISPLAY;
if (ImGui::Button(display.c_str(), ImVec2(25, 25))) {
ImGui::OpenPopup("display");
}
ImVec2 btn_min = ImGui::GetItemRectMin();
ImVec2 btn_size_actual = ImGui::GetItemRectSize();
if (ImGui::BeginPopup("display")) {
ImGui::SetWindowFontScale(0.5f);
for (int i = 0; i < props->display_info_list_.size(); i++) {
if (ImGui::Selectable(props->display_info_list_[i].name.c_str())) {
props->selected_display_ = i;
RemoteAction remote_action;
remote_action.type = ControlType::display_id;
remote_action.d = i;
if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
}
}
props->display_selectable_hovered_ = ImGui::IsWindowHovered();
}
ImGui::SetWindowFontScale(1.0f);
ImGui::EndPopup();
}
ImGui::SetWindowFontScale(0.6f);
ImVec2 text_size = ImGui::CalcTextSize(
std::to_string(props->selected_display_ + 1).c_str());
ImVec2 text_pos =
ImVec2(btn_min.x + (btn_size_actual.x - text_size.x) * 0.5f,
btn_min.y + (btn_size_actual.y - text_size.y) * 0.5f - 2.0f);
ImGui::GetWindowDrawList()->AddText(
text_pos, IM_COL32(0, 0, 0, 255),
std::to_string(props->selected_display_ + 1).c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::SameLine();
float disable_mouse_x = ImGui::GetCursorScreenPos().x + 4.0f;
float disable_mouse_y = ImGui::GetCursorScreenPos().y + 4.0f;
std::string mouse = props->mouse_control_button_pressed_
? ICON_FA_COMPUTER_MOUSE
: ICON_FA_COMPUTER_MOUSE;
if (ImGui::Button(mouse.c_str(), ImVec2(25, 25))) {
if (props->connection_established_) {
start_keyboard_capturer_ = !start_keyboard_capturer_;
props->control_mouse_ = !props->control_mouse_;
props->mouse_control_button_pressed_ =
!props->mouse_control_button_pressed_;
props->mouse_control_button_label_ =
props->mouse_control_button_pressed_
? localization::release_mouse[localization_language_index_]
: localization::control_mouse[localization_language_index_];
}
}
if (!props->mouse_control_button_pressed_) {
draw_list->AddLine(
ImVec2(disable_mouse_x, disable_mouse_y),
ImVec2(disable_mouse_x + 16.0f, disable_mouse_y + 14.2f),
IM_COL32(0, 0, 0, 255), 2.0f);
draw_list->AddLine(
ImVec2(disable_mouse_x - 1.2f, disable_mouse_y + 1.2f),
ImVec2(disable_mouse_x + 15.3f, disable_mouse_y + 15.4f),
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255)
: IM_COL32(179, 213, 253, 255),
2.0f);
}
ImGui::SameLine();
// audio capture button
float disable_audio_x = ImGui::GetCursorScreenPos().x + 4;
float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f;
// std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH
// :
// ICON_FA_VOLUME_XMARK;
std::string audio = props->audio_capture_button_pressed_
? ICON_FA_VOLUME_HIGH
: ICON_FA_VOLUME_HIGH;
if (ImGui::Button(audio.c_str(), ImVec2(25, 25))) {
if (props->connection_established_) {
props->audio_capture_button_pressed_ =
!props->audio_capture_button_pressed_;
props->audio_capture_button_label_ =
props->audio_capture_button_pressed_
? localization::audio_capture[localization_language_index_]
: localization::mute[localization_language_index_];
RemoteAction remote_action;
remote_action.type = ControlType::audio_capture;
remote_action.a = props->audio_capture_button_pressed_;
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
}
}
if (!props->audio_capture_button_pressed_) {
draw_list->AddLine(
ImVec2(disable_audio_x, disable_audio_y),
ImVec2(disable_audio_x + 16.0f, disable_audio_y + 14.2f),
IM_COL32(0, 0, 0, 255), 2.0f);
draw_list->AddLine(
ImVec2(disable_audio_x - 1.2f, disable_audio_y + 1.2f),
ImVec2(disable_audio_x + 15.3f, disable_audio_y + 15.4f),
ImGui::IsItemHovered() ? IM_COL32(66, 150, 250, 255)
: IM_COL32(179, 213, 253, 255),
2.0f);
}
ImGui::SameLine();
// net traffic stats button
bool button_color_style_pushed = false;
if (props->net_traffic_stats_button_pressed_) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(66 / 255.0f, 150 / 255.0f,
250 / 255.0f, 1.0f));
button_color_style_pushed = true;
}
std::string net_traffic_stats = ICON_FA_SIGNAL;
if (ImGui::Button(net_traffic_stats.c_str(), ImVec2(25, 25))) {
props->net_traffic_stats_button_pressed_ =
!props->net_traffic_stats_button_pressed_;
props->control_window_height_is_changing_ = true;
props->net_traffic_stats_button_pressed_time_ = ImGui::GetTime();
props->net_traffic_stats_button_label_ =
props->net_traffic_stats_button_pressed_
? localization::hide_net_traffic_stats
[localization_language_index_]
: localization::show_net_traffic_stats
[localization_language_index_];
}
if (button_color_style_pushed) {
ImGui::PopStyleColor();
button_color_style_pushed = false;
}
ImGui::SameLine();
// fullscreen button
std::string fullscreen =
fullscreen_button_pressed_ ? ICON_FA_COMPRESS : ICON_FA_EXPAND;
if (ImGui::Button(fullscreen.c_str(), ImVec2(25, 25))) {
fullscreen_button_pressed_ = !fullscreen_button_pressed_;
props->fullscreen_button_label_ =
fullscreen_button_pressed_
? localization::exit_fullscreen[localization_language_index_]
: localization::fullscreen[localization_language_index_];
if (fullscreen_button_pressed_) {
SDL_SetWindowFullscreen(stream_window_, true);
} else {
SDL_SetWindowFullscreen(stream_window_, false);
}
props->reset_control_bar_pos_ = true;
}
ImGui::SameLine();
// close button
std::string close_button = ICON_FA_XMARK;
if (ImGui::Button(close_button.c_str(), ImVec2(25, 25))) {
CleanupPeer(props);
}
ImGui::SameLine();
if (!props->is_control_bar_in_left_) {
draw_list->AddLine(ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
ImGui::GetCursorScreenPos().y - 7.0f),
ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
ImGui::GetCursorScreenPos().y - 7.0f +
props->control_window_height_),
IM_COL32(178, 178, 178, 255), 1.0f);
}
}
ImGui::SetCursorPosX(props->is_control_bar_in_left_
? (props->control_window_width_ * 2 - 20.0f)
: 5.0f);
std::string control_bar =
props->control_bar_expand_
? (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_LEFT
: ICON_FA_ANGLE_RIGHT)
: (props->is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT
: ICON_FA_ANGLE_LEFT);
if (ImGui::Button(control_bar.c_str(), ImVec2(15, 25))) {
props->control_bar_expand_ = !props->control_bar_expand_;
props->control_bar_button_pressed_time_ = ImGui::GetTime();
props->control_window_width_is_changing_ = true;
if (!props->control_bar_expand_) {
props->control_window_height_ = 40;
props->net_traffic_stats_button_pressed_ = false;
}
}
if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) {
NetTrafficStats(props);
}
ImGui::PopStyleVar();
return 0;
}
int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::SetCursorPos(ImVec2(props->is_control_bar_in_left_
? (props->control_window_width_ + 5.0f)
: 5.0f,
40.0f));
if (ImGui::BeginTable("NetTrafficStats", 4, ImGuiTableFlags_BordersH,
ImVec2(props->control_window_max_width_ - 10.0f,
props->control_window_max_height_ - 60.0f))) {
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableNextColumn();
ImGui::Text(" ");
ImGui::TableNextColumn();
ImGui::Text("%s", localization::in[localization_language_index_].c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", localization::out[localization_language_index_].c_str());
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::loss_rate[localization_language_index_].c_str());
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::video[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.video_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.video_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(props->net_traffic_stats_.video_inbound_stats.loss_rate);
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::audio[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.audio_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.audio_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(props->net_traffic_stats_.audio_inbound_stats.loss_rate);
ImGui::TableNextColumn();
ImGui::Text("%s", localization::data[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.data_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.data_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(props->net_traffic_stats_.data_inbound_stats.loss_rate);
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::total[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.total_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)props->net_traffic_stats_.total_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate);
ImGui::TableNextColumn();
ImGui::Text("FPS");
ImGui::TableNextColumn();
ImGui::Text("%d", props->fps_);
ImGui::EndTable();
}
return 0;
}
} // namespace crossdesk

View File

@@ -1,6 +1,8 @@
#include "localization.h"
#include "render.h"
namespace crossdesk {
int Render::StatusBar() {
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e;
@@ -13,17 +15,16 @@ int Render::StatusBar() {
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(
ImVec2(15, main_window_height_default_ - status_bar_height_ + 9.0f), 5,
ImColor(signal_connected_ ? 0.0f : 1.0f, signal_connected_ ? 1.0f : 0.0f,
0.0f),
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(
ImVec2(15, main_window_height_default_ - status_bar_height_ + 10.0f), 6,
ImColor(1.0f, 1.0f, 1.0f), 100);
draw_list->AddCircle(dot_pos, 6.0f, ImColor(1.0f, 1.0f, 1.0f), 100);
ImGui::SetWindowFontScale(0.5f);
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),
@@ -37,3 +38,4 @@ int Render::StatusBar() {
ImGui::EndChild();
return 0;
}
} // namespace crossdesk

View File

@@ -1,30 +1,35 @@
#include "IconsFontAwesome6.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
#define BUTTON_PADDING 36.0f
namespace crossdesk {
int Render::TitleBar(bool main_window) {
ImGui::PushStyleColor(ImGuiCol_MenuBarBg, ImVec4(1, 1, 1, 0.0f));
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(
"TitleBar",
main_window ? "MainTitleBar" : "StreamTitleBar",
ImVec2(main_window ? main_window_width_ : stream_window_width_,
title_bar_height_),
ImGuiChildFlags_None,
ImGuiChildFlags_Border,
ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
if (ImGui::BeginMenuBar()) {
ImGui::SetCursorPosX(
(main_window ? main_window_width_ : stream_window_width_) -
(streaming_ ? BUTTON_PADDING * 4 - 3 : BUTTON_PADDING * 3 - 3));
(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 (!streaming_) {
if (main_window) {
float bar_pos_x = ImGui::GetCursorPosX() + 6;
float bar_pos_y = ImGui::GetCursorPosY() + 15;
std::string menu_button = " "; // ICON_FA_BARS;
@@ -54,6 +59,7 @@ int Render::TitleBar(bool main_window) {
{
SettingWindow();
SelfHostedServerWindow();
AboutWindow();
}
}
@@ -61,9 +67,9 @@ int Render::TitleBar(bool main_window) {
ImGui::PopStyleColor(2);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::SetCursorPosX(
(main_window ? main_window_width_ : stream_window_width_) -
(streaming_ ? BUTTON_PADDING * 3 : BUTTON_PADDING * 2));
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));
@@ -80,10 +86,8 @@ int Render::TitleBar(bool main_window) {
IM_COL32(0, 0, 0, 255));
ImGui::PopStyleColor(2);
if (streaming_) {
ImGui::SetCursorPosX(
(main_window ? main_window_width_ : stream_window_width_) -
BUTTON_PADDING * 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));
@@ -97,7 +101,7 @@ int Render::TitleBar(bool main_window) {
"##restore"; // ICON_FA_WINDOW_RESTORE;
if (ImGui::Button(window_restore_button.c_str(),
ImVec2(BUTTON_PADDING, 30))) {
SDL_RestoreWindow(main_window ? main_window_ : stream_window_);
SDL_RestoreWindow(stream_window_);
window_maximized_ = false;
}
draw_list->AddRect(ImVec2(pos_x_top, pos_y_top),
@@ -116,7 +120,7 @@ int Render::TitleBar(bool main_window) {
"##maximize"; // ICON_FA_SQUARE_FULL;
if (ImGui::Button(window_maximize_button.c_str(),
ImVec2(BUTTON_PADDING, 30))) {
SDL_MaximizeWindow(main_window ? main_window_ : stream_window_);
SDL_MaximizeWindow(stream_window_);
window_maximized_ = !window_maximized_;
}
draw_list->AddRect(ImVec2(maximize_pos_x, maximize_pos_y),
@@ -137,9 +141,17 @@ int Render::TitleBar(bool main_window) {
float xmark_size = 12.0f;
std::string close_button = "##xmark"; // ICON_FA_XMARK;
if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) {
#if _WIN32
if (enable_minimize_to_tray_) {
tray_->MinimizeToTray();
} else {
#endif
SDL_Event event;
event.type = SDL_QUIT;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
#if _WIN32
}
#endif
}
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
xmark_pos_y - xmark_size / 2 + 0.75f),
@@ -158,9 +170,8 @@ int Render::TitleBar(bool main_window) {
}
ImGui::EndMenuBar();
ImGui::SetWindowFontScale(1.0f);
ImGui::EndChild();
ImGui::PopStyleColor();
return 0;
}
} // namespace crossdesk

115
src/gui/tray/win_tray.cpp Normal file
View File

@@ -0,0 +1,115 @@
#include "win_tray.h"
#include <SDL3/SDL.h>
#include "localization.h"
namespace crossdesk {
// callback for the message-only window that handles tray icon messages
static LRESULT CALLBACK MsgWndProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
WinTray* tray =
reinterpret_cast<WinTray*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (!tray) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
if (msg == WM_TRAY_CALLBACK) {
MSG tmpMsg = {};
tmpMsg.message = msg;
tmpMsg.wParam = wParam;
tmpMsg.lParam = lParam;
tray->HandleTrayMessage(&tmpMsg);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
WinTray::WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
int language_index)
: app_hwnd_(app_hwnd),
icon_(icon),
tip_(tooltip),
hwnd_message_only_(nullptr),
language_index_(language_index) {
WNDCLASS wc = {};
wc.lpfnWndProc = MsgWndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"TrayMessageWindow";
RegisterClass(&wc);
// create a message-only window to receive tray messages
hwnd_message_only_ =
CreateWindowEx(0, wc.lpszClassName, L"TrayMsg", 0, 0, 0, 0, 0,
HWND_MESSAGE, nullptr, wc.hInstance, nullptr);
// store pointer to this WinTray instance in window data
SetWindowLongPtr(hwnd_message_only_, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(this));
// initialize NOTIFYICONDATA structure
ZeroMemory(&nid_, sizeof(nid_));
nid_.cbSize = sizeof(nid_);
nid_.hWnd = hwnd_message_only_;
nid_.uID = 1;
nid_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid_.uCallbackMessage = WM_TRAY_CALLBACK;
nid_.hIcon = icon_;
wcsncpy_s(nid_.szTip, tip_.c_str(), _TRUNCATE);
}
WinTray::~WinTray() {
RemoveTrayIcon();
if (hwnd_message_only_) DestroyWindow(hwnd_message_only_);
}
void WinTray::MinimizeToTray() {
Shell_NotifyIcon(NIM_ADD, &nid_);
// hide application window
ShowWindow(app_hwnd_, SW_HIDE);
}
void WinTray::RemoveTrayIcon() { Shell_NotifyIcon(NIM_DELETE, &nid_); }
bool WinTray::HandleTrayMessage(MSG* msg) {
if (!msg || msg->message != WM_TRAY_CALLBACK) return false;
switch (LOWORD(msg->lParam)) {
case WM_LBUTTONDBLCLK:
case WM_LBUTTONUP: {
ShowWindow(app_hwnd_, SW_SHOW);
SetForegroundWindow(app_hwnd_);
break;
}
case WM_RBUTTONUP: {
POINT pt;
GetCursorPos(&pt);
HMENU menu = CreatePopupMenu();
AppendMenuW(menu, MF_STRING, 1001,
localization::exit_program[language_index_]);
SetForegroundWindow(hwnd_message_only_);
int cmd =
TrackPopupMenu(menu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_LEFTALIGN,
pt.x, pt.y, 0, hwnd_message_only_, nullptr);
DestroyMenu(menu);
// handle menu command
if (cmd == 1001) {
// exit application
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
} else if (cmd == 1002) {
ShowWindow(app_hwnd_, SW_SHOW); // show main window
SetForegroundWindow(app_hwnd_);
}
break;
}
}
return true;
}
} // namespace crossdesk

38
src/gui/tray/win_tray.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-10-22
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _WIN_TRAY_H_
#define _WIN_TRAY_H_
#include <Windows.h>
#include <shellapi.h>
#include <string>
#define WM_TRAY_CALLBACK (WM_USER + 1)
namespace crossdesk {
class WinTray {
public:
WinTray(HWND app_hwnd, HICON icon, const std::wstring& tooltip,
int language_index);
~WinTray();
void MinimizeToTray();
void RemoveTrayIcon();
bool HandleTrayMessage(MSG* msg);
private:
HWND app_hwnd_;
HWND hwnd_message_only_;
HICON icon_;
std::wstring tip_;
int language_index_;
NOTIFYICONDATA nid_;
};
} // namespace crossdesk
#endif

View File

@@ -1,9 +1,10 @@
#include "IconsFontAwesome6.h"
#include "layout_style.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
int Render::AboutWindow() {
if (show_about_window_) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
@@ -28,15 +29,21 @@ int Render::AboutWindow() {
ImGui::SetWindowFontScale(0.5f);
std::string version;
#ifdef RD_VERSION
version = RD_VERSION;
#ifdef CROSSDESK_VERSION
version = CROSSDESK_VERSION;
#else
version = "Unknown";
#endif
std::string text =
localization::version[localization_language_index_] + ": " + version;
std::string text = localization::version[localization_language_index_] +
": CrossDesk v" + version;
ImGui::Text("%s", text.c_str());
ImGui::Text("");
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
std::string license_text = "Licensed under GNU LGPL v3.";
ImGui::Text("%s", copyright_text.c_str());
ImGui::Text("%s", license_text.c_str());
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
@@ -54,3 +61,4 @@ int Render::AboutWindow() {
}
return 0;
}
} // namespace crossdesk

View File

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

View File

@@ -0,0 +1,226 @@
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
double time_duration =
ImGui::GetTime() - props->control_bar_button_pressed_time_;
if (props->control_window_width_is_changing_) {
if (props->control_bar_expand_) {
props->control_window_width_ =
(float)(props->control_window_min_width_ +
(props->control_window_max_width_ -
props->control_window_min_width_) *
4 * time_duration);
} else {
props->control_window_width_ =
(float)(props->control_window_max_width_ -
(props->control_window_max_width_ -
props->control_window_min_width_) *
4 * time_duration);
}
}
time_duration =
ImGui::GetTime() - props->net_traffic_stats_button_pressed_time_;
if (props->control_window_height_is_changing_) {
if (props->control_bar_expand_ &&
props->net_traffic_stats_button_pressed_) {
props->control_window_height_ =
(float)(props->control_window_min_height_ +
(props->control_window_max_height_ -
props->control_window_min_height_) *
4 * time_duration);
} else if (props->control_bar_expand_ &&
!props->net_traffic_stats_button_pressed_) {
props->control_window_height_ =
(float)(props->control_window_max_height_ -
(props->control_window_max_height_ -
props->control_window_min_height_) *
4 * time_duration);
}
}
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 1));
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::SetNextWindowSize(
ImVec2(props->control_window_width_, props->control_window_height_),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_ + 1), ImGuiCond_Once);
float pos_x = 0;
float pos_y = 0;
float y_boundary = fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1);
if (props->reset_control_bar_pos_) {
float new_cursor_pos_x = 0;
float new_cursor_pos_y = 0;
// set control window pos
if (props->control_window_pos_.y + props->control_window_height_ >
stream_window_height_) {
pos_y = stream_window_height_ - props->control_window_height_;
} else if (props->control_window_pos_.y < y_boundary) {
pos_y = y_boundary;
} else {
pos_y = props->control_window_pos_.y;
}
if (props->is_control_bar_in_left_) {
pos_x = 0;
} else {
pos_x = stream_window_width_ - props->control_window_width_;
}
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
if (0 != props->mouse_diff_control_bar_pos_x_ &&
0 != props->mouse_diff_control_bar_pos_y_) {
// set cursor pos
new_cursor_pos_x = pos_x + props->mouse_diff_control_bar_pos_x_;
new_cursor_pos_y = pos_y + props->mouse_diff_control_bar_pos_y_;
SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x,
(int)new_cursor_pos_y);
}
props->reset_control_bar_pos_ = false;
} else if (!props->reset_control_bar_pos_ &&
ImGui::IsMouseReleased(ImGuiMouseButton_Left) ||
props->control_window_width_is_changing_) {
if (props->control_window_pos_.x <= stream_window_width_ / 2) {
if (props->control_window_pos_.y + props->control_window_height_ >
stream_window_height_) {
pos_y = stream_window_height_ - props->control_window_height_;
} else {
pos_y = props->control_window_pos_.y;
}
if (props->control_bar_expand_) {
if (props->control_window_width_ >= props->control_window_max_width_) {
props->control_window_width_ = props->control_window_max_width_;
props->control_window_width_is_changing_ = false;
} else {
props->control_window_width_is_changing_ = true;
}
} else {
if (props->control_window_width_ <= props->control_window_min_width_) {
props->control_window_width_ = props->control_window_min_width_;
props->control_window_width_is_changing_ = false;
} else {
props->control_window_width_is_changing_ = true;
}
}
props->is_control_bar_in_left_ = true;
} else if (props->control_window_pos_.x > stream_window_width_ / 2) {
pos_x = 0;
pos_y =
(props->control_window_pos_.y >= y_boundary &&
props->control_window_pos_.y <=
stream_window_height_ - props->control_window_height_)
? props->control_window_pos_.y
: (props->control_window_pos_.y < (fullscreen_button_pressed_
? 0
: (title_bar_height_ + 1))
? (fullscreen_button_pressed_ ? 0
: (title_bar_height_ + 1))
: (stream_window_height_ - props->control_window_height_));
if (props->control_bar_expand_) {
if (props->control_window_width_ >= props->control_window_max_width_) {
props->control_window_width_ = props->control_window_max_width_;
props->control_window_width_is_changing_ = false;
pos_x = stream_window_width_ - props->control_window_max_width_;
} else {
props->control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - props->control_window_width_;
}
} else {
if (props->control_window_width_ <= props->control_window_min_width_) {
props->control_window_width_ = props->control_window_min_width_;
props->control_window_width_is_changing_ = false;
pos_x = stream_window_width_ - props->control_window_min_width_;
} else {
props->control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - props->control_window_width_;
}
}
props->is_control_bar_in_left_ = false;
}
if (props->control_window_pos_.y + props->control_window_height_ >
stream_window_height_) {
pos_y = stream_window_height_ - props->control_window_height_;
} else if (props->control_window_pos_.y < y_boundary) {
pos_y = y_boundary;
}
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
}
if (props->control_bar_expand_ && props->control_window_height_is_changing_) {
if (props->net_traffic_stats_button_pressed_) {
if (props->control_window_height_ >= props->control_window_max_height_) {
props->control_window_height_ = props->control_window_max_height_;
props->control_window_height_is_changing_ = false;
} else {
props->control_window_height_is_changing_ = true;
}
} else {
if (props->control_window_height_ <= props->control_window_min_height_) {
props->control_window_height_ = props->control_window_min_height_;
props->control_window_height_is_changing_ = false;
} else {
props->control_window_height_is_changing_ = true;
}
}
}
std::string control_window_title = props->remote_id_ + "ControlWindow";
ImGui::Begin(control_window_title.c_str(), nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking);
ImGui::PopStyleVar();
props->control_window_pos_ = ImGui::GetWindowPos();
SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_);
props->mouse_diff_control_bar_pos_x_ =
props->mouse_pos_x_ - props->control_window_pos_.x;
props->mouse_diff_control_bar_pos_y_ =
props->mouse_pos_y_ - props->control_window_pos_.y;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e;
ImGui::SetNextWindowPos(
ImVec2(props->is_control_bar_in_left_
? props->control_window_pos_.x - props->control_window_width_
: props->control_window_pos_.x,
props->control_window_pos_.y),
ImGuiCond_Always);
ImGui::SetWindowFontScale(0.5f);
std::string control_child_window_title =
props->remote_id_ + "ControlChildWindow";
ImGui::BeginChild(
control_child_window_title.c_str(),
ImVec2(props->control_window_width_ * 2, props->control_window_height_),
ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration);
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor();
ControlBar(props);
props->control_bar_hovered_ = ImGui::IsWindowHovered();
ImGui::EndChild();
ImGui::End();
ImGui::PopStyleVar(4);
ImGui::PopStyleColor();
return 0;
}
} // namespace crossdesk

View File

@@ -1,9 +1,10 @@
#include "IconsFontAwesome6.h"
#include "layout_style.h"
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
int Render::SettingWindow() {
if (show_settings_window_) {
if (settings_window_pos_reset_) {
@@ -37,6 +38,9 @@ int Render::SettingWindow() {
// Settings
{
static int settings_items_padding = 30;
int settings_items_offset = 0;
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
@@ -54,7 +58,8 @@ int Render::SettingWindow() {
localization::language_zh[localization_language_index_].c_str(),
localization::language_en[localization_language_index_].c_str()};
ImGui::SetCursorPosY(32);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text(
"%s", localization::language[localization_language_index_].c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
@@ -62,7 +67,7 @@ int Render::SettingWindow() {
} else {
ImGui::SetCursorPosX(LANGUAGE_SELECT_WINDOW_PADDING_EN);
}
ImGui::SetCursorPosY(30);
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
ImGui::Combo("##language", &language_button_value_, language_items,
@@ -71,6 +76,10 @@ int Render::SettingWindow() {
ImGui::Separator();
if (stream_window_inited_) {
ImGui::BeginDisabled();
}
{
const char* video_quality_items[] = {
localization::video_quality_high[localization_language_index_]
@@ -80,7 +89,8 @@ int Render::SettingWindow() {
localization::video_quality_low[localization_language_index_]
.c_str()};
ImGui::SetCursorPosY(62);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text(
"%s",
localization::video_quality[localization_language_index_].c_str());
@@ -90,7 +100,7 @@ int Render::SettingWindow() {
} else {
ImGui::SetCursorPosX(VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN);
}
ImGui::SetCursorPosY(60);
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
ImGui::Combo("##video_quality", &video_quality_button_value_,
@@ -100,11 +110,36 @@ int Render::SettingWindow() {
ImGui::Separator();
{
const char *video_encode_format_items[] = {
localization::av1[localization_language_index_].c_str(),
localization::h264[localization_language_index_].c_str()};
const char* video_frame_rate_items[] = {"30 fps", "60 fps"};
ImGui::SetCursorPosY(92);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text("%s",
localization::video_frame_rate[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN);
} else {
ImGui::SetCursorPosX(VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
ImGui::Combo("##video_frame_rate", &video_frame_rate_button_value_,
video_frame_rate_items,
IM_ARRAYSIZE(video_frame_rate_items));
}
ImGui::Separator();
{
const char* video_encode_format_items[] = {
localization::h264[localization_language_index_].c_str(),
localization::av1[localization_language_index_].c_str()};
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text(
"%s",
localization::video_encode_format[localization_language_index_]
@@ -115,7 +150,7 @@ int Render::SettingWindow() {
} else {
ImGui::SetCursorPosX(VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN);
}
ImGui::SetCursorPosY(90);
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SETTINGS_SELECT_WINDOW_WIDTH);
ImGui::Combo(
@@ -126,7 +161,8 @@ int Render::SettingWindow() {
ImGui::Separator();
{
ImGui::SetCursorPosY(122);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text("%s", localization::enable_hardware_video_codec
[localization_language_index_]
.c_str());
@@ -136,7 +172,7 @@ int Render::SettingWindow() {
} else {
ImGui::SetCursorPosX(ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN);
}
ImGui::SetCursorPosY(120);
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_hardware_video_codec",
&enable_hardware_video_codec_);
}
@@ -144,7 +180,8 @@ int Render::SettingWindow() {
ImGui::Separator();
{
ImGui::SetCursorPosY(152);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text(
"%s",
localization::enable_turn[localization_language_index_].c_str());
@@ -154,28 +191,95 @@ int Render::SettingWindow() {
} else {
ImGui::SetCursorPosX(ENABLE_TURN_CHECKBOX_PADDING_EN);
}
ImGui::SetCursorPosY(150);
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_turn", &enable_turn_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text(
"%s",
localization::enable_srtp[localization_language_index_].c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_CN);
} else {
ImGui::SetCursorPosX(ENABLE_SRTP_CHECKBOX_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_srtp", &enable_srtp_);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 1);
if (ImGui::Button(localization::self_hosted_server_config
[localization_language_index_]
.c_str())) {
show_self_hosted_server_config_window_ = true;
}
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_CN);
} else {
ImGui::SetCursorPosX(ENABLE_SELF_HOSTED_SERVER_CHECKBOX_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_self_hosted_server",
&enable_self_hosted_server_);
}
#if _WIN32
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 4);
ImGui::Text("%s",
localization::minimize_to_tray[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_CN);
} else {
ImGui::SetCursorPosX(ENABLE_MINIZE_TO_TRAY_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::Checkbox("##enable_minimize_to_tray_",
&enable_minimize_to_tray_);
}
#endif
if (stream_window_inited_) {
ImGui::EndDisabled();
}
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_CN);
} else {
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN);
}
ImGui::SetCursorPosY(190.0f);
settings_items_offset += settings_items_padding + 10;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::PopStyleVar();
// OK
if (ImGui::Button(
localization::ok[localization_language_index_].c_str())) {
show_settings_window_ = false;
show_self_hosted_server_config_window_ = false;
// Language
if (language_button_value_ == 0) {
config_center_.SetLanguage(ConfigCenter::LANGUAGE::CHINESE);
config_center_->SetLanguage(ConfigCenter::LANGUAGE::CHINESE);
} else {
config_center_.SetLanguage(ConfigCenter::LANGUAGE::ENGLISH);
config_center_->SetLanguage(ConfigCenter::LANGUAGE::ENGLISH);
}
language_button_value_last_ = language_button_value_;
localization_language_ = (ConfigCenter::LANGUAGE)language_button_value_;
@@ -185,52 +289,64 @@ int Render::SettingWindow() {
// Video quality
if (video_quality_button_value_ == 0) {
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
} else if (video_quality_button_value_ == 1) {
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
} else {
config_center_.SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
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(
config_center_->SetVideoEncodeFormat(
ConfigCenter::VIDEO_ENCODE_FORMAT::H264);
} else if (video_encode_format_button_value_ == 1) {
config_center_->SetVideoEncodeFormat(
ConfigCenter::VIDEO_ENCODE_FORMAT::AV1);
}
video_encode_format_button_value_last_ =
video_encode_format_button_value_;
// Hardware video codec
if (enable_hardware_video_codec_) {
config_center_.SetHardwareVideoCodec(true);
config_center_->SetHardwareVideoCodec(true);
} else {
config_center_.SetHardwareVideoCodec(false);
config_center_->SetHardwareVideoCodec(false);
}
enable_hardware_video_codec_last_ = enable_hardware_video_codec_;
// TURN mode
if (enable_turn_) {
config_center_.SetTurn(true);
config_center_->SetTurn(true);
} else {
config_center_.SetTurn(false);
config_center_->SetTurn(false);
}
enable_turn_last_ = enable_turn_;
SaveSettingsIntoCacheFile();
// SRTP
if (enable_srtp_) {
config_center_->SetSrtp(true);
} else {
config_center_->SetSrtp(false);
}
enable_srtp_last_ = enable_srtp_;
if (enable_self_hosted_server_) {
config_center_->SetSelfHosted(true);
} else {
config_center_->SetSelfHosted(false);
}
settings_window_pos_reset_ = true;
// Recreate peer instance
LoadSettingsFromCacheFile();
// Recreate peer instance
{
if (!stream_window_inited_) {
LOG_INFO("Recreate peer instance");
DestroyPeer(&peer_);
is_create_connection_ = false;
CleanupPeers();
CreateConnectionPeer();
}
}
@@ -240,6 +356,8 @@ int Render::SettingWindow() {
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str())) {
show_settings_window_ = false;
show_self_hosted_server_config_window_ = false;
if (language_button_value_ != language_button_value_last_) {
language_button_value_ = language_button_value_last_;
}
@@ -275,3 +393,4 @@ int Render::SettingWindow() {
return 0;
}
} // namespace crossdesk

View File

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

View File

@@ -0,0 +1,324 @@
#include <filesystem>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
std::vector<std::string> GetRootEntries() {
std::vector<std::string> roots;
#ifdef _WIN32
DWORD mask = GetLogicalDrives();
for (char letter = 'A'; letter <= 'Z'; ++letter) {
if (mask & 1) {
roots.push_back(std::string(1, letter) + ":\\");
}
mask >>= 1;
}
#else
roots.push_back("/");
#endif
return roots;
}
int Render::ShowSimpleFileBrowser() {
std::string display_text;
if (selected_current_file_path_.empty()) {
selected_current_file_path_ = std::filesystem::current_path().string();
}
if (!tls_cert_path_self_.empty()) {
display_text =
std::filesystem::path(tls_cert_path_self_).filename().string();
} else if (selected_current_file_path_ != "Root") {
display_text =
std::filesystem::path(selected_current_file_path_).filename().string();
if (display_text.empty()) {
display_text = selected_current_file_path_;
}
}
if (display_text.empty()) {
display_text =
localization::select_a_file[localization_language_index_].c_str();
}
if (show_file_browser_) {
ImGui::PushItemFlag(ImGuiItemFlags_AutoClosePopups, false);
float fixed_width = 130.0f;
ImGui::SetNextItemWidth(fixed_width);
ImGui::SetNextWindowSizeConstraints(ImVec2(fixed_width, 0),
ImVec2(fixed_width, 100.0f));
if (ImGui::BeginCombo("##select_a_file", display_text.c_str(), 0)) {
bool file_selected = false;
auto roots = GetRootEntries();
if (selected_current_file_path_ == "Root" ||
!std::filesystem::exists(selected_current_file_path_) ||
!std::filesystem::is_directory(selected_current_file_path_)) {
for (const auto& root : roots) {
if (ImGui::Selectable(root.c_str())) {
selected_current_file_path_ = root;
tls_cert_path_self_.clear();
}
}
} else {
std::filesystem::path p(selected_current_file_path_);
if (ImGui::Selectable("..")) {
if (std::find(roots.begin(), roots.end(),
selected_current_file_path_) != roots.end()) {
selected_current_file_path_ = "Root";
} else if (p.has_parent_path()) {
selected_current_file_path_ = p.parent_path().string();
} else {
selected_current_file_path_ = "Root";
}
tls_cert_path_self_.clear();
}
try {
for (const auto& entry : std::filesystem::directory_iterator(
selected_current_file_path_)) {
std::string name = entry.path().filename().string();
if (entry.is_directory()) {
if (ImGui::Selectable(name.c_str())) {
selected_current_file_path_ = entry.path().string();
tls_cert_path_self_.clear();
}
} else {
if (ImGui::Selectable(name.c_str())) {
tls_cert_path_self_ = entry.path().string();
file_selected = true;
show_file_browser_ = false;
}
}
}
} catch (const std::exception& e) {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", e.what());
}
}
ImGui::EndCombo();
}
ImGui::PopItemFlag();
} else {
show_file_browser_ = true;
}
return 0;
}
int Render::SelfHostedServerWindow() {
if (show_self_hosted_server_config_window_) {
if (self_hosted_server_config_window_pos_reset_) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetNextWindowPos(
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN) /
2,
(viewport->WorkSize.y - viewport->WorkPos.y -
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN) /
2));
ImGui::SetNextWindowSize(
ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_CN,
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_CN));
} else {
ImGui::SetNextWindowPos(
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN) /
2,
(viewport->WorkSize.y - viewport->WorkPos.y -
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN) /
2));
ImGui::SetNextWindowSize(
ImVec2(SELF_HOSTED_SERVER_CONFIG_WINDOW_WIDTH_EN,
SELF_HOSTED_SERVER_CONFIG_WINDOW_HEIGHT_EN));
}
self_hosted_server_config_window_pos_reset_ = false;
}
// Settings
{
static int settings_items_padding = 30;
int settings_items_offset = 0;
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::Begin(localization::self_hosted_server_settings
[localization_language_index_]
.c_str(),
nullptr,
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoSavedSettings);
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text("%s", localization::self_hosted_server_address
[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_CN);
} else {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_HOST_INPUT_BOX_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
ImGui::InputText("##signal_server_ip_self_", signal_server_ip_self_,
IM_ARRAYSIZE(signal_server_ip_self_),
ImGuiInputTextFlags_AlwaysOverwrite);
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text(
"%s",
localization::self_hosted_server_port[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
} else {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
ImGui::InputText("##signal_server_port_self_", signal_server_port_self_,
IM_ARRAYSIZE(signal_server_port_self_));
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text("%s", localization::self_hosted_server_coturn_server_port
[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
} else {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
ImGui::InputText("##coturn_server_port_self_", coturn_server_port_self_,
IM_ARRAYSIZE(coturn_server_port_self_));
}
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text("%s", localization::self_hosted_server_certificate_path
[localization_language_index_]
.c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_CN);
} else {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_PORT_INPUT_BOX_PADDING_EN);
}
ImGui::SetCursorPosY(settings_items_offset);
ImGui::SetNextItemWidth(SELF_HOSTED_SERVER_INPUT_WINDOW_WIDTH);
ShowSimpleFileBrowser();
}
if (stream_window_inited_) {
ImGui::EndDisabled();
}
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_CN);
} else {
ImGui::SetCursorPosX(SELF_HOSTED_SERVER_CONFIG_OK_BUTTON_PADDING_EN);
}
settings_items_offset += settings_items_padding + 10;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::PopStyleVar();
// OK
if (ImGui::Button(
localization::ok[localization_language_index_].c_str())) {
show_self_hosted_server_config_window_ = false;
config_center_->SetServerHost(signal_server_ip_self_);
config_center_->SetServerPort(atoi(signal_server_port_self_));
config_center_->SetCoturnServerPort(atoi(coturn_server_port_self_));
config_center_->SetCertFilePath(tls_cert_path_self_);
strncpy(signal_server_ip_, signal_server_ip_self_,
sizeof(signal_server_ip_) - 1);
signal_server_ip_[sizeof(signal_server_ip_) - 1] = '\0';
strncpy(signal_server_port_, signal_server_port_self_,
sizeof(signal_server_port_) - 1);
signal_server_port_[sizeof(signal_server_port_) - 1] = '\0';
strncpy(coturn_server_port_, coturn_server_port_self_,
sizeof(coturn_server_port_) - 1);
coturn_server_port_[sizeof(coturn_server_port_) - 1] = '\0';
strncpy(cert_file_path_, tls_cert_path_self_.c_str(),
sizeof(cert_file_path_) - 1);
cert_file_path_[sizeof(cert_file_path_) - 1] = '\0';
self_hosted_server_config_window_pos_reset_ = true;
}
ImGui::SameLine();
// Cancel
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str())) {
show_self_hosted_server_config_window_ = false;
self_hosted_server_config_window_pos_reset_ = true;
strncpy(signal_server_ip_self_, signal_server_ip_,
sizeof(signal_server_ip_self_) - 1);
signal_server_ip_self_[sizeof(signal_server_ip_self_) - 1] = '\0';
strncpy(signal_server_port_self_, signal_server_port_,
sizeof(signal_server_port_self_) - 1);
signal_server_port_self_[sizeof(signal_server_port_self_) - 1] = '\0';
config_center_->SetServerHost(signal_server_ip_self_);
config_center_->SetServerPort(atoi(signal_server_port_self_));
tls_cert_path_self_.clear();
}
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::SetWindowFontScale(1.0f);
}
}
return 0;
}
} // namespace crossdesk

View File

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

View File

@@ -1,85 +0,0 @@
/*
* @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 = {u8"本桌面", "Local Desktop"};
static std::vector<std::string> local_id = {u8"本机ID", "Local ID"};
static std::vector<std::string> local_id_copied_to_clipboard = {
u8"已复制到剪贴板", "Copied to clipboard"};
static std::vector<std::string> password = {u8"密码", "Password"};
static std::vector<std::string> max_password_len = {u8"最大6个字符",
"Max 6 chars"};
static std::vector<std::string> remote_desktop = {u8"控制远程桌面",
"Control Remote Desktop"};
static std::vector<std::string> remote_id = {u8"对端ID", "Remote ID"};
static std::vector<std::string> connect = {u8"连接", "Connect"};
static std::vector<std::string> disconnect = {u8"断开连接", "Disconnect"};
static std::vector<std::string> fullscreen = {u8" 全屏", " Fullscreen"};
static std::vector<std::string> exit_fullscreen = {u8" 退出全屏",
" Exit fullscreen"};
static std::vector<std::string> control_mouse = {u8" 控制", " Control"};
static std::vector<std::string> release_mouse = {u8" 释放", " Release"};
static std::vector<std::string> audio_capture = {u8" 声音", " Audio"};
static std::vector<std::string> mute = {u8" 静音", " Mute"};
static std::vector<std::string> settings = {u8"设置", "Settings"};
static std::vector<std::string> language = {u8"语言:", "Language:"};
static std::vector<std::string> language_zh = {u8"中文", "Chinese"};
static std::vector<std::string> language_en = {u8"英文", "English"};
static std::vector<std::string> video_quality = {u8"视频质量:",
"Video Quality:"};
static std::vector<std::string> video_quality_high = {u8"", "High"};
static std::vector<std::string> video_quality_medium = {u8"", "Medium"};
static std::vector<std::string> video_quality_low = {u8"", "Low"};
static std::vector<std::string> video_encode_format = {u8"视频编码格式:",
"Video Encode Format:"};
static std::vector<std::string> av1 = {u8"AV1", "AV1"};
static std::vector<std::string> h264 = {u8"H.264", "H.264"};
static std::vector<std::string> enable_hardware_video_codec = {
u8"启用硬件编解码器:", "Enable Hardware Video Codec:"};
static std::vector<std::string> enable_turn = {u8"启用中继服务:",
"Enable TURN Service:"};
static std::vector<std::string> ok = {u8"确认", "OK"};
static std::vector<std::string> cancel = {u8"取消", "Cancel"};
static std::vector<std::string> new_password = {
u8"请输入六位密码:", "Please input a six-char password:"};
static std::vector<std::string> input_password = {u8"请输入密码:",
"Please input password:"};
static std::vector<std::string> validate_password = {u8"验证密码中...",
"Validate password ..."};
static std::vector<std::string> reinput_password = {
u8"请重新输入密码", "Please input password again"};
static std::vector<std::string> signal_connected = {u8"已连接服务器",
"Connected"};
static std::vector<std::string> signal_disconnected = {u8"未连接服务器",
"Disconnected"};
static std::vector<std::string> p2p_connected = {u8"对等连接已建立",
"P2P Connected"};
static std::vector<std::string> p2p_disconnected = {u8"对等连接已断开",
"P2P Disconnected"};
static std::vector<std::string> p2p_connecting = {u8"正在建立对等连接...",
"P2P Connecting ..."};
static std::vector<std::string> p2p_failed = {u8"对等连接失败", "P2P Failed"};
static std::vector<std::string> p2p_closed = {u8"对等连接已关闭", "P2P closed"};
static std::vector<std::string> no_such_id = {u8"无此ID", "No such ID"};
static std::vector<std::string> about = {u8"关于", "About"};
static std::vector<std::string> version = {u8"版本", "Version"};
} // namespace localization
#endif

65
src/log/rd_log.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "rd_log.h"
#include <atomic>
#include <filesystem>
namespace crossdesk {
namespace {
std::string g_log_dir = "logs";
std::once_flag g_logger_once_flag;
std::shared_ptr<spdlog::logger> g_logger;
std::atomic<bool> g_logger_created{false};
} // namespace
void InitLogger(const std::string& log_dir) {
if (g_logger_created.load()) {
LOG_WARN(
"InitLogger called after logger initialized. Ignoring log_dir: {}, "
"using previous log_dir: {}",
log_dir, g_log_dir);
return;
}
g_log_dir = log_dir;
}
std::shared_ptr<spdlog::logger> get_logger() {
std::call_once(g_logger_once_flag, []() {
g_logger_created.store(true);
std::error_code ec;
std::filesystem::create_directories(g_log_dir, ec);
auto now = std::chrono::system_clock::now() + std::chrono::hours(8);
auto now_time = std::chrono::system_clock::to_time_t(now);
std::tm tm_info;
#ifdef _WIN32
gmtime_s(&tm_info, &now_time);
#else
gmtime_r(&now_time, &tm_info);
#endif
std::stringstream ss;
ss << LOGGER_NAME;
ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log");
std::string filename = g_log_dir + "/" + ss.str();
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
filename, 5 * 1024 * 1024, 3));
g_logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(),
sinks.end());
g_logger->flush_on(spdlog::level::info);
spdlog::register_logger(g_logger);
});
return g_logger;
}
} // namespace crossdesk

View File

@@ -1,7 +1,7 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-07-17
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
* @Date: 2025-07-21
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _RD_LOG_H_
@@ -10,8 +10,11 @@
#include <chrono>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>
#include "spdlog/common.h"
#include "spdlog/logger.h"
@@ -20,103 +23,19 @@
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"
using namespace std::chrono;
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
constexpr auto LOGGER_NAME = "rd";
namespace crossdesk {
#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__); \
}
constexpr auto LOGGER_NAME = "crossdesk";
#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__); \
}
void InitLogger(const std::string& log_dir);
#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__); \
}
std::shared_ptr<spdlog::logger> get_logger();
#define LOG_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__)
#define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__)
#define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__)
#define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__)
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,94 @@
#include "path_manager.h"
#include <cstdlib>
namespace crossdesk {
PathManager::PathManager(const std::string& app_name) : app_name_(app_name) {}
std::filesystem::path PathManager::GetConfigPath() {
#ifdef _WIN32
return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_;
#elif __APPLE__
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_;
#else
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") / app_name_;
#endif
}
std::filesystem::path PathManager::GetCachePath() {
#ifdef _WIN32
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "cache";
#elif __APPLE__
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
#else
return GetEnvOrDefault("XDG_CACHE_HOME", GetHome() + "/.cache") / app_name_;
#endif
}
std::filesystem::path PathManager::GetLogPath() {
#ifdef _WIN32
return GetKnownFolder(FOLDERID_LocalAppData) / app_name_ / "logs";
#elif __APPLE__
return GetHome() + "/Library/Logs/" + app_name_;
#else
return GetCachePath() / app_name_ / "logs";
#endif
}
std::filesystem::path PathManager::GetCertPath() {
#ifdef _WIN32
// %APPDATA%\AppName\Certs
return GetKnownFolder(FOLDERID_RoamingAppData) / app_name_ / "certs";
#elif __APPLE__
// $HOME/Library/Application Support/AppName/certs
return GetHome() + "/Library/Application Support/" + app_name_ + "/certs";
#else
// $XDG_CONFIG_HOME/AppName/certs
return GetEnvOrDefault("XDG_CONFIG_HOME", GetHome() + "/.config") /
app_name_ / "certs";
#endif
}
bool PathManager::CreateDirectories(const std::filesystem::path& p) {
std::error_code ec;
bool created = std::filesystem::create_directories(p, ec);
if (ec) {
return false;
}
return created || std::filesystem::exists(p);
}
#ifdef _WIN32
std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) {
PWSTR path = NULL;
if (SUCCEEDED(SHGetKnownFolderPath(id, 0, NULL, &path))) {
std::wstring wpath(path);
CoTaskMemFree(path);
return std::filesystem::path(wpath);
}
return {};
}
#endif
std::string PathManager::GetHome() {
if (const char* home = getenv("HOME")) {
return std::string(home);
}
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path)))
return std::string(path);
#endif
return {};
}
std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var,
const std::string& def) {
if (const char* val = getenv(env_var)) {
return std::filesystem::path(val);
}
return std::filesystem::path(def);
}
} // namespace crossdesk

View File

@@ -0,0 +1,46 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-07-16
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _PATH_MANAGER_H_
#define _PATH_MANAGER_H_
#include <filesystem>
#include <string>
#ifdef _WIN32
#include <shlobj.h>
#include <windows.h>
#endif
namespace crossdesk {
class PathManager {
public:
explicit PathManager(const std::string& app_name);
std::filesystem::path GetConfigPath();
std::filesystem::path GetCachePath();
std::filesystem::path GetLogPath();
std::filesystem::path GetCertPath();
bool CreateDirectories(const std::filesystem::path& p);
private:
#ifdef _WIN32
std::filesystem::path GetKnownFolder(REFKNOWNFOLDERID id);
#endif
std::string GetHome();
std::filesystem::path GetEnvOrDefault(const char* env_var,
const std::string& def);
private:
std::string app_name_;
};
} // namespace crossdesk
#endif

View File

@@ -1,152 +1,177 @@
#include "screen_capturer_x11.h"
#include <iostream>
#include <chrono>
#include <thread>
#include "log.h"
#include "libyuv.h"
#include "rd_log.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
namespace crossdesk {
ScreenCapturerX11::ScreenCapturerX11() {}
ScreenCapturerX11::~ScreenCapturerX11() {
if (inited_ && capture_thread_.joinable()) {
capture_thread_.join();
inited_ = false;
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
display_ = XOpenDisplay(nullptr);
if (!display_) {
LOG_ERROR("Cannot connect to X server");
return -1;
}
root_ = DefaultRootWindow(display_);
screen_res_ = XRRGetScreenResources(display_, root_);
if (!screen_res_) {
LOG_ERROR("Failed to get screen resources");
XCloseDisplay(display_);
return 1;
}
for (int i = 0; i < screen_res_->noutput; ++i) {
RROutput output = screen_res_->outputs[i];
XRROutputInfo* output_info =
XRRGetOutputInfo(display_, screen_res_, output);
if (output_info->connection == RR_Connected && output_info->crtc != 0) {
XRRCrtcInfo* crtc_info =
XRRGetCrtcInfo(display_, screen_res_, output_info->crtc);
display_info_list_.push_back(
DisplayInfo((void*)display_, output_info->name, true, crtc_info->x,
crtc_info->y, crtc_info->width, crtc_info->height));
XRRFreeCrtcInfo(crtc_info);
}
if (output_info) {
XRRFreeOutputInfo(output_info);
}
}
int ScreenCapturerX11::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
if (cb) {
_on_data = cb;
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);
// show remote cursor
av_dict_set(&options_, "capture_cursor", "0", 0);
// Make the grabbed area follow the mouse
// av_dict_set(&options_, "follow_mouse", "centered", 0);
// Video frame size. The default is to capture the full screen
// av_dict_set(&options_, "video_size", "1280x720", 0);
std::string capture_method = "x11grab";
ifmt_ = (AVInputFormat *)av_find_input_format(capture_method.c_str());
if (!ifmt_) {
LOG_ERROR("Couldn't find_input_format [{}]", capture_method.c_str());
}
// Grab at position 10,20
if (avformat_open_input(&pFormatCtx_, ":0.0", ifmt_, &options_) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
printf("Couldn't find stream information.\n");
return -1;
}
videoindex_ = -1;
for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++)
if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex_ = i_;
break;
}
if (videoindex_ == -1) {
printf("Didn't find a video stream.\n");
return -1;
}
pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar;
pCodecCtx_ = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx_, pCodecParam_);
pCodec_ = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx_->codec_id));
if (pCodec_ == NULL) {
printf("Codec not found.\n");
return -1;
}
if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width;
const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height;
pFrame_ = av_frame_alloc();
pFrameNv12_ = av_frame_alloc();
pFrame_->width = screen_w;
pFrame_->height = screen_h;
pFrameNv12_->width = 1280;
pFrameNv12_->height = 720;
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
img_convert_ctx_ = sws_getContext(
pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width,
pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
inited_ = true;
y_plane_.resize(width_ * height_);
uv_plane_.resize((width_ / 2) * (height_ / 2) * 2);
return 0;
}
int ScreenCapturerX11::Destroy() {
running_ = false;
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_ = std::thread([this]() {
if (running_) return 0;
running_ = true;
paused_ = false;
thread_ = std::thread([this]() {
while (running_) {
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 (!paused_) OnFrame();
}
});
return 0;
}
int ScreenCapturerX11::Stop() {
if (!running_) return 0;
running_ = false;
if (thread_.joinable()) thread_.join();
return 0;
}
int ScreenCapturerX11::Pause() { return 0; }
int ScreenCapturerX11::Pause(int monitor_index) {
paused_ = true;
return 0;
}
int ScreenCapturerX11::Resume() { return 0; }
int ScreenCapturerX11::Resume(int monitor_index) {
paused_ = false;
return 0;
}
void ScreenCapturerX11::OnFrame() {}
int ScreenCapturerX11::SwitchTo(int monitor_index) {
monitor_index_ = monitor_index;
return 0;
}
void ScreenCapturerX11::CleanUp() {}
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
return display_info_list_;
}
void ScreenCapturerX11::OnFrame() {
if (!display_) {
LOG_ERROR("Display is not initialized");
return;
}
if (monitor_index_ < 0 || monitor_index_ >= display_info_list_.size()) {
LOG_ERROR("Invalid monitor index: {}", monitor_index_.load());
return;
}
left_ = display_info_list_[monitor_index_].left;
top_ = display_info_list_[monitor_index_].top;
width_ = display_info_list_[monitor_index_].width;
height_ = display_info_list_[monitor_index_].height;
XImage* image = XGetImage(display_, root_, left_, top_, width_, height_,
AllPlanes, ZPixmap);
if (!image) return;
bool needs_copy = image->bytes_per_line != width_ * 4;
std::vector<uint8_t> argb_buf;
uint8_t* src_argb = nullptr;
if (needs_copy) {
argb_buf.resize(width_ * height_ * 4);
for (int y = 0; y < height_; ++y) {
memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line,
width_ * 4);
}
src_argb = argb_buf.data();
} else {
src_argb = reinterpret_cast<uint8_t*>(image->data);
}
libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_,
uv_plane_.data(), width_, width_, height_);
std::vector<uint8_t> nv12;
nv12.reserve(y_plane_.size() + uv_plane_.size());
nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end());
nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end());
if (callback_) {
callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_,
display_info_list_[monitor_index_].name.c_str());
}
XDestroyImage(image);
}
} // namespace crossdesk

View File

@@ -1,23 +1,26 @@
/*
* @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
namespace crossdesk {
class ScreenCapturerX11 : public ScreenCapturer {
public:
@@ -25,60 +28,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;
virtual int Stop();
int Pause();
int Resume();
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_ = 60;
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;
bool inited_ = false;
// ffmpeg
AVFormatContext *pFormatCtx_ = nullptr;
AVCodecContext *pCodecCtx_ = nullptr;
AVCodec *pCodec_ = nullptr;
AVCodecParameters *pCodecParam_ = nullptr;
AVDictionary *options_ = nullptr;
AVInputFormat *ifmt_ = nullptr;
AVFrame *pFrame_ = nullptr;
AVFrame *pFrameNv12_ = nullptr;
AVPacket *packet_ = nullptr;
struct SwsContext *img_convert_ctx_ = nullptr;
// thread
std::thread capture_thread_;
std::atomic_bool running_;
// 缓冲区
std::vector<uint8_t> y_plane_;
std::vector<uint8_t> uv_plane_;
};
} // namespace crossdesk
#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,212 +0,0 @@
#include "screen_capturer_avf.h"
#include <ApplicationServices/ApplicationServices.h>
#include <iostream>
#include "rd_log.h"
#define USE_SCALE_FACTOR 0
ScreenCapturerAvf::ScreenCapturerAvf() {}
ScreenCapturerAvf::~ScreenCapturerAvf() {
if (inited_ && capture_thread_.joinable()) {
capture_thread_.join();
inited_ = false;
}
if (nv12_frame_) {
delete[] nv12_frame_;
nv12_frame_ = nullptr;
}
if (pFormatCtx_) {
avformat_close_input(&pFormatCtx_);
pFormatCtx_ = nullptr;
}
if (pCodecCtx_) {
avcodec_free_context(&pCodecCtx_);
pCodecCtx_ = nullptr;
}
if (options_) {
av_dict_free(&options_);
options_ = nullptr;
}
if (pFrame_) {
av_frame_free(&pFrame_);
pFrame_ = nullptr;
}
if (packet_) {
av_packet_free(&packet_);
packet_ = nullptr;
}
#if USE_SCALE_FACTOR
if (img_convert_ctx_) {
sws_freeContext(img_convert_ctx_);
img_convert_ctx_ = nullptr;
}
#endif
}
int ScreenCapturerAvf::Init(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", "0", 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", "1440x900", 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();
pFrame_->width = screen_w;
pFrame_->height = screen_h;
#if USE_SCALE_FACTOR
pFrame_resized_ = av_frame_alloc();
pFrame_resized_->width = CGDisplayPixelsWide(CGMainDisplayID());
pFrame_resized_->height = CGDisplayPixelsHigh(CGMainDisplayID());
img_convert_ctx_ =
sws_getContext(pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt,
pFrame_resized_->width, pFrame_resized_->height,
AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL);
#endif
if (!nv12_frame_) {
nv12_frame_ = new unsigned char[screen_w * screen_h * 3 / 2];
}
packet_ = (AVPacket *)av_malloc(sizeof(AVPacket));
inited_ = true;
return 0;
}
int ScreenCapturerAvf::Destroy() {
running_ = false;
return 0;
}
int ScreenCapturerAvf::Start() {
if (running_) {
return 0;
}
running_ = true;
capture_thread_ = std::thread([this]() {
while (running_) {
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_) {
#if USE_SCALE_FACTOR
av_image_fill_arrays(pFrame_resized_->data,
pFrame_resized_->linesize, nv12_frame_,
AV_PIX_FMT_NV12, pFrame_resized_->width,
pFrame_resized_->height, 1);
sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0,
pFrame_->height, pFrame_resized_->data,
pFrame_resized_->linesize);
_on_data((unsigned char *)nv12_frame_,
pFrame_resized_->width * pFrame_resized_->height * 3 / 2,
pFrame_resized_->width, pFrame_resized_->height);
#else
memcpy(nv12_frame_, pFrame_->data[0],
pFrame_->linesize[0] * pFrame_->height);
memcpy(nv12_frame_ + pFrame_->linesize[0] * pFrame_->height,
pFrame_->data[1],
pFrame_->linesize[1] * pFrame_->height / 2);
_on_data((unsigned char *)nv12_frame_,
pFrame_->width * pFrame_->height * 3 / 2, pFrame_->width,
pFrame_->height);
#endif
}
}
}
}
});
return 0;
}
int ScreenCapturerAvf::Stop() {
running_ = false;
return 0;
}
int ScreenCapturerAvf::Pause() { return 0; }
int ScreenCapturerAvf::Resume() { return 0; }
void ScreenCapturerAvf::OnFrame() {}
void ScreenCapturerAvf::CleanUp() {}

View File

@@ -1,87 +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 int fps, cb_desktop_data cb);
virtual int Destroy();
virtual int Start();
virtual int Stop();
int Pause();
int Resume();
void OnFrame();
protected:
void CleanUp();
private:
std::atomic_bool _paused;
std::atomic_bool _inited;
std::thread _thread;
std::string _device_name;
int _fps;
cb_desktop_data _on_data;
private:
int i_ = 0;
int videoindex_ = 0;
int got_picture_ = 0;
bool inited_ = false;
// ffmpeg
AVFormatContext *pFormatCtx_ = nullptr;
AVCodecContext *pCodecCtx_ = nullptr;
AVCodec *pCodec_ = nullptr;
AVCodecParameters *pCodecParam_ = nullptr;
AVDictionary *options_ = nullptr;
AVInputFormat *ifmt_ = nullptr;
AVFrame *pFrame_ = nullptr;
AVFrame *pFrame_resized_ = nullptr;
AVPacket *packet_ = nullptr;
struct SwsContext *img_convert_ctx_ = nullptr;
unsigned char *nv12_frame_ = nullptr;
// thread
std::thread capture_thread_;
std::atomic_bool running_;
};
#endif

View File

@@ -1,104 +0,0 @@
#include <IOSurface/IOSurface.h>
#include <utility>
#include "rd_log.h"
#include "screen_capturer_cgd.h"
ScreenCapturerCg::ScreenCapturerCg() {}
ScreenCapturerCg::~ScreenCapturerCg() {}
int ScreenCapturerCg::Init(const int fps, cb_desktop_data cb) {
if (cb) {
_on_data = cb;
}
size_t pixel_width = 1280;
size_t pixel_height = 720;
CGDirectDisplayID display_id = 0;
CGDisplayStreamFrameAvailableHandler handler =
^(CGDisplayStreamFrameStatus status, uint64_t display_time,
IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef updateRef) {
if (status == kCGDisplayStreamFrameStatusStopped) return;
// Only pay attention to frame updates.
if (status != kCGDisplayStreamFrameStatusFrameComplete) return;
// size_t count = 0;
// const CGRect* rects = CGDisplayStreamUpdateGetRects(
// updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
// 获取帧数据
void* frameData = IOSurfaceGetBaseAddressOfPlane(frame_surface, 0);
size_t width = IOSurfaceGetWidthOfPlane(frame_surface, 0);
size_t height = IOSurfaceGetHeightOfPlane(frame_surface, 0);
};
CFDictionaryRef properties_dictionary = CFDictionaryCreate(
kCFAllocatorDefault, (const void*[]){kCGDisplayStreamShowCursor},
(const void*[]){kCFBooleanFalse}, 1, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CGDisplayStreamRef display_stream =
CGDisplayStreamCreate(display_id, pixel_width, pixel_height, 'BGRA',
properties_dictionary, handler);
if (display_stream) {
CGError error = CGDisplayStreamStart(display_stream);
if (error != kCGErrorSuccess) return -1;
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(display_stream);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
display_streams_.push_back(display_stream);
}
CFRelease(properties_dictionary);
return 0;
}
int ScreenCapturerCg::Destroy() {
running_ = false;
return 0;
}
int ScreenCapturerCg::Start() {
if (_running) {
return 0;
}
running_ = true;
capture_thread_ = std::thread([this]() {
while (running_) {
CFRunLoopRun();
}
});
return 0;
}
int ScreenCapturerCg::Stop() {
running_ = false;
return 0;
}
int ScreenCapturerCg::Pause() { return 0; }
int ScreenCapturerCg::Resume() { return 0; }
void ScreenCapturerCg::OnFrame() {}
void ScreenCapturerCg::CleanUp() {}
//
void ScreenCapturerCg::UnregisterRefreshAndMoveHandlers() {
for (CGDisplayStreamRef stream : display_streams_) {
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(stream);
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
CGDisplayStreamStop(stream);
CFRelease(stream);
}
display_streams_.clear();
}

View File

@@ -1,56 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-10-16
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_CGD_H_
#define _SCREEN_CAPTURER_CGD_H_
#include <CoreGraphics/CoreGraphics.h>
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include "screen_capturer.h"
class ScreenCapturerCg : public ScreenCapturer {
public:
ScreenCapturerCg();
~ScreenCapturerCg();
public:
virtual int Init(const int fps, cb_desktop_data cb);
virtual int Destroy();
virtual int Start();
virtual int Stop();
int Pause();
int Resume();
void OnFrame();
protected:
void CleanUp();
private:
int _fps;
cb_desktop_data _on_data;
// thread
std::thread capture_thread_;
std::atomic_bool running_;
private:
};
#endif

View File

@@ -1,51 +0,0 @@
#include "screen_capturer_sck.h"
#include "rd_log.h"
ScreenCapturerSck::ScreenCapturerSck() {}
ScreenCapturerSck::~ScreenCapturerSck() {
// if (inited_ && capture_thread_.joinable()) {
// capture_thread_.join();
// inited_ = false;
// }
}
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() { return 0; }
int ScreenCapturerSck::Start() {
// if (running_) {
// return 0;
// }
// running_ = true;
// capture_thread_ = std::thread([this]() {
// while (running_) {
// }
// });
return 0;
}
int ScreenCapturerSck::Stop() { return 0; }
int ScreenCapturerSck::Pause() { return 0; }
int ScreenCapturerSck::Resume() { return 0; }
void ScreenCapturerSck::OnFrame() {}
void ScreenCapturerSck::CleanUp() {}

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