191 Commits
new-ui ... sdl3

Author SHA1 Message Date
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
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
121 changed files with 13384 additions and 7997 deletions

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

@@ -0,0 +1,376 @@
name: Build and Release CrossDesk
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: |
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}"
else
LEGAL_VERSION="${VERSION_NUM}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
ls -la $XMAKE_GLOBALDIR
xmake b -vy --root crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package
run: |
chmod +x ./scripts/linux/pkg_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: |
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="0.0.0-${VERSION_NUM}"
else
LEGAL_VERSION="${VERSION_NUM}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
xmake b -vy --root crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${LEGAL_VERSION}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}.deb
# macOS
build-macos:
name: Build on macOS
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: 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##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}"
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: ~/.xmake/packages
key: ${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
- name: Install xmake
run: brew install xmake
- name: Checkout code
uses: actions/checkout@v4
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Build CrossDesk
run: xmake b -vy crossdesk
- name: Decode and save certificate
shell: bash
run: |
mkdir -p certs
echo "${{ secrets.CROSSDESK_CERT_BASE64 }}" | base64 --decode > certs/crossdesk.cn_root.crt
- name: Package CrossDesk app
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${VERSION_NUM}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
- name: Move files to release dir
run: |
mkdir -p release
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
# Windows
build-windows-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 '/', '-'
echo "VERSION_NUM=$version" >> $env:GITHUB_ENV
- name: Cache xmake dependencies
uses: actions/cache@v4
with:
path: D:\xmake_global\.xmake\packages
key: ${{ runner.os }}-xmake-deps-intel-${{ hashFiles('**/xmake.lua') }}
restore-keys: |
${{ runner.os }}-xmake-deps-intel-
- name: Install xmake
run: |
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
xmake create cuda
Set-Location cuda
xmake g --theme=plain
$cudaPath = ""
$packagesPath = "D:\xmake_global\.xmake\packages"
if (Test-Path $packagesPath) {
Write-Host "Packages directory exists: $packagesPath"
try {
$info = xmake require --info "cuda 12.6.3" 2>$null
if ($null -ne $info -and $info -ne "") {
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
} catch {}
} else {
Write-Host "Packages directory not found: $packagesPath"
Write-Host "Installing CUDA package..."
xmake require -vy "cuda 12.6.3"
$info = xmake require --info "cuda 12.6.3"
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
Write-Host "Resolved CUDA_PATH = $cudaPath"
Pop-Location
- name: 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 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##*/}"
VERSION_NUM="${VERSION#v}"
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 }}

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

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

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "thirdparty/projectx"]
path = thirdparty/projectx
url = https://github.com/dijunkun/projectx.git
[submodule "thirdparty/minirtc"]
path = thirdparty/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.

107
README.md
View File

@@ -1,67 +1,112 @@
# Continuous Desk
# CrossDesk
#### More than remote desktop
#### 跨界连接,高效如一
----
[中文](README_CN.md) / [English](README.md)
[English](README_EN.md) / [中文](README.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
xmake b crossdesk
```
Run:
```
# Windows/MacOS
xmake r remote_desk
#### 无 CUDA 环境下的开发支持
# root privileges are required on Linux
./remote_desk
对于未安装 **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 crossdesk
```
## LICENSE
运行
```
xmake r crossdesk
```
Continuous Desk is licenced under MIT, and some third-party libraries are distributed under their licenses.
#### 注意
运行时如果客户端状态栏显示 **未连接服务器**,请先在 [CrossDesk 官方网站](https://www.crossdesk.cn/) 安装客户端,以便在环境中安装所需的证书文件。
<img width="129" height="60" 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) 。

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 许可证,其中使用到的第三方库根据自身许可证进行分发。

116
README_EN.md Normal file
View File

@@ -0,0 +1,116 @@
# CrossDesk
#### Bridging work, uniting efficiency
----
[中文](README.md) / [English](README_EN.md)
![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/bf39f8fa-de77-41a1-8db3-73d6cab9da6a)
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/f6556966-a84f-4301-a79b-2726b389ed71)
Before connecting, you can customize configuration options in the settings, such as language and video encoding format.
![settings](https://github.com/user-attachments/assets/12f7e9c3-a472-40c1-8fb9-ae7d1ae3865c)
## 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 crossdesk
```
#### Development Without CUDA Environment
For 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 crossdesk
```
Run:
```
xmake r 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="108" height="57" alt="image" src="https://github.com/user-attachments/assets/26e8b9f3-b326-410e-9466-dd073eaf675a" />
## 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) .

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

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

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

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

View File

@@ -1,5 +1,7 @@
#include "config_center.h"
#include "rd_log.h"
ConfigCenter::ConfigCenter() {}
ConfigCenter::~ConfigCenter() {}
@@ -14,6 +16,11 @@ int ConfigCenter::SetVideoQuality(VIDEO_QUALITY video_quality) {
return 0;
}
int ConfigCenter::SetVideoFrameRate(VIDEO_FRAME_RATE video_frame_rate) {
video_frame_rate_ = video_frame_rate;
return 0;
}
int ConfigCenter::SetVideoEncodeFormat(
VIDEO_ENCODE_FORMAT video_encode_format) {
video_encode_format_ = video_encode_format;
@@ -30,12 +37,22 @@ int ConfigCenter::SetTurn(bool enable_turn) {
return 0;
}
int ConfigCenter::SetSrtp(bool enable_srtp) {
enable_srtp_ = enable_srtp;
return 0;
}
ConfigCenter::LANGUAGE ConfigCenter::GetLanguage() { return language_; }
ConfigCenter::VIDEO_QUALITY ConfigCenter::GetVideoQuality() {
return video_quality_;
}
int ConfigCenter::GetVideoFrameRate() {
int fps = video_frame_rate_ == VIDEO_FRAME_RATE::FPS_30 ? 30 : 60;
return fps;
}
ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() {
return video_encode_format_;
}
@@ -43,3 +60,5 @@ ConfigCenter::VIDEO_ENCODE_FORMAT ConfigCenter::GetVideoEncodeFormat() {
bool ConfigCenter::IsHardwareVideoCodec() { return hardware_video_codec_; }
bool ConfigCenter::IsEnableTurn() { return enable_turn_; }
bool ConfigCenter::IsEnableSrtp() { return enable_srtp_; }

View File

@@ -11,6 +11,7 @@ class ConfigCenter {
public:
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1 };
enum class VIDEO_QUALITY { LOW = 0, MEDIUM = 1, HIGH = 2 };
enum class VIDEO_FRAME_RATE { FPS_30 = 0, FPS_60 = 1 };
enum class VIDEO_ENCODE_FORMAT { AV1 = 0, H264 = 1 };
public:
@@ -20,24 +21,30 @@ class ConfigCenter {
public:
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);
public:
LANGUAGE GetLanguage();
VIDEO_QUALITY GetVideoQuality();
int GetVideoFrameRate();
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat();
bool IsHardwareVideoCodec();
bool IsEnableTurn();
bool IsEnableSrtp();
private:
// Default value should be same with parameters in localization.h
LANGUAGE language_ = LANGUAGE::CHINESE;
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::AV1;
bool hardware_video_codec_ = false;
bool enable_turn_ = false;
bool enable_srtp_ = false;
};
#endif

View File

@@ -9,17 +9,31 @@
#include <stdio.h>
#include "display_info.h"
typedef enum {
mouse = 0,
keyboard,
audio_capture,
host_infomation
host_infomation,
display_id,
} ControlType;
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
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;
@@ -31,6 +45,12 @@ typedef struct {
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 {
@@ -40,6 +60,7 @@ typedef struct {
Key k;
HostInfo i;
bool a;
int d;
};
} RemoteAction;
@@ -53,7 +74,7 @@ class DeviceController {
public:
// virtual int Init(int screen_width, int screen_height);
// virtual int Destroy();
// virtual int SendCommand(RemoteAction remote_action);
// virtual int SendMouseCommand(RemoteAction remote_action);
// virtual int Hook();
// virtual int Unhook();

View File

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

View File

@@ -7,6 +7,10 @@
#ifndef _KEYBOARD_CAPTURER_H_
#define _KEYBOARD_CAPTURER_H_
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include "device_controller.h"
class KeyboardCapturer : public DeviceController {
@@ -20,6 +24,9 @@ class KeyboardCapturer : public DeviceController {
virtual int SendKeyboardCommand(int key_code, bool is_down);
private:
Display *display_;
Window root_;
bool running_;
};
#endif

View File

@@ -123,12 +123,44 @@ int KeyboardCapturer::Unhook() {
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;

View File

@@ -31,6 +31,7 @@ class KeyboardCapturer : public DeviceController {
bool control_flag_ = false;
bool option_flag_ = false;
bool command_flag_ = false;
int fn_key_code_ = 0x3F;
};
#endif

View File

@@ -1,248 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-25
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _KEYBOARD_CONVERTER_H_
#define _KEYBOARD_CONVERTER_H_
#include <map>
// Windows vkCode to macOS CGKeyCode (104 keys)
std::map<int, int> vkCodeToCGKeyCode = {
// A-Z
{0x41, 0x00}, // A
{0x42, 0x0B}, // B
{0x43, 0x08}, // C
{0x44, 0x02}, // D
{0x45, 0x0E}, // E
{0x46, 0x03}, // F
{0x47, 0x05}, // G
{0x48, 0x04}, // H
{0x49, 0x22}, // I
{0x4A, 0x26}, // J
{0x4B, 0x28}, // K
{0x4C, 0x25}, // L
{0x4D, 0x2E}, // M
{0x4E, 0x2D}, // N
{0x4F, 0x1F}, // O
{0x50, 0x23}, // P
{0x51, 0x0C}, // Q
{0x52, 0x0F}, // R
{0x53, 0x01}, // S
{0x54, 0x11}, // T
{0x55, 0x20}, // U
{0x56, 0x09}, // V
{0x57, 0x0D}, // W
{0x58, 0x07}, // X
{0x59, 0x10}, // Y
{0x5A, 0x06}, // Z
// 0-9
{0x30, 0x1D}, // 0
{0x31, 0x12}, // 1
{0x32, 0x13}, // 2
{0x33, 0x14}, // 3
{0x34, 0x15}, // 4
{0x35, 0x17}, // 5
{0x36, 0x16}, // 6
{0x37, 0x1A}, // 7
{0x38, 0x1C}, // 8
{0x39, 0x19}, // 9
// F1-F12
{0x70, 0x7A}, // F1
{0x71, 0x78}, // F2
{0x72, 0x63}, // F3
{0x73, 0x76}, // F4
{0x74, 0x60}, // F5
{0x75, 0x61}, // F6
{0x76, 0x62}, // F7
{0x77, 0x64}, // F8
{0x78, 0x65}, // F9
{0x79, 0x6D}, // F10
{0x7A, 0x67}, // F11
{0x7B, 0x6F}, // F12
// control keys
{0x1B, 0x35}, // Escape
{0x0D, 0x24}, // Enter
{0x20, 0x31}, // Space
{0x08, 0x33}, // Backspace
{0x09, 0x30}, // Tab
{0x2C, 0x74}, // Print Screen
{0x2D, 0x72}, // Insert
{0x2E, 0x75}, // Delete
{0x24, 0x73}, // Home
{0x23, 0x77}, // End
{0x21, 0x79}, // Page Up
{0x22, 0x7A}, // Page Down
// arrow keys
{0x25, 0x7B}, // Left Arrow
{0x27, 0x7C}, // Right Arrow
{0x26, 0x7E}, // Up Arrow
{0x28, 0x7D}, // Down Arrow
// numpad
{0x60, 0x52}, // Numpad 0
{0x61, 0x53}, // Numpad 1
{0x62, 0x54}, // Numpad 2
{0x63, 0x55}, // Numpad 3
{0x64, 0x56}, // Numpad 4
{0x65, 0x57}, // Numpad 5
{0x66, 0x58}, // Numpad 6
{0x67, 0x59}, // Numpad 7
{0x68, 0x5B}, // Numpad 8
{0x69, 0x5C}, // Numpad 9
{0x6E, 0x41}, // Numpad .
{0x6F, 0x4B}, // Numpad /
{0x6A, 0x43}, // Numpad *
{0x6D, 0x4E}, // Numpad -
{0x6B, 0x45}, // Numpad +
// symbol keys
{0xBA, 0x29}, // ; (Semicolon)
{0xDE, 0x27}, // ' (Quote)
{0xC0, 0x32}, // ` (Backtick)
{0xBC, 0x2B}, // , (Comma)
{0xBE, 0x2F}, // . (Period)
{0xBF, 0x2C}, // / (Slash)
{0xDC, 0x2A}, // \ (Backslash)
{0xDB, 0x21}, // [ (Left Bracket)
{0xDD, 0x1E}, // ] (Right Bracket)
{0xBD, 0x1B}, // - (Minus)
{0xBB, 0x18}, // = (Equals)
// modifier keys
{0x14, 0x39}, // Caps Lock
{0xA0, 0x38}, // Shift (Left)
{0xA1, 0x3C}, // Shift (Right)
{0xA2, 0x3B}, // Ctrl (Left)
{0xA3, 0x3E}, // Ctrl (Right)
{0xA4, 0x3A}, // Alt (Left)
{0xA5, 0x3D}, // Alt (Right)
{0x5B, 0x37}, // Left Command (Windows key)
{0x5C, 0x36}, // Right Command
};
// macOS CGKeyCode to Windows vkCode
std::map<int, int> CGKeyCodeToVkCode = {
// A-Z
{0x00, 0x41}, // A
{0x0B, 0x42}, // B
{0x08, 0x43}, // C
{0x02, 0x44}, // D
{0x0E, 0x45}, // E
{0x03, 0x46}, // F
{0x05, 0x47}, // G
{0x04, 0x48}, // H
{0x22, 0x49}, // I
{0x26, 0x4A}, // J
{0x28, 0x4B}, // K
{0x25, 0x4C}, // L
{0x2E, 0x4D}, // M
{0x2D, 0x4E}, // N
{0x1F, 0x4F}, // O
{0x23, 0x50}, // P
{0x0C, 0x51}, // Q
{0x0F, 0x52}, // R
{0x01, 0x53}, // S
{0x11, 0x54}, // T
{0x20, 0x55}, // U
{0x09, 0x56}, // V
{0x0D, 0x57}, // W
{0x07, 0x58}, // X
{0x10, 0x59}, // Y
{0x06, 0x5A}, // Z
// 0-9
{0x1D, 0x30}, // 0
{0x12, 0x31}, // 1
{0x13, 0x32}, // 2
{0x14, 0x33}, // 3
{0x15, 0x34}, // 4
{0x17, 0x35}, // 5
{0x16, 0x36}, // 6
{0x1A, 0x37}, // 7
{0x1C, 0x38}, // 8
{0x19, 0x39}, // 9
// F1-F12
{0x7A, 0x70}, // F1
{0x78, 0x71}, // F2
{0x63, 0x72}, // F3
{0x76, 0x73}, // F4
{0x60, 0x74}, // F5
{0x61, 0x75}, // F6
{0x62, 0x76}, // F7
{0x64, 0x77}, // F8
{0x65, 0x78}, // F9
{0x6D, 0x79}, // F10
{0x67, 0x7A}, // F11
{0x6F, 0x7B}, // F12
// control keys
{0x35, 0x1B}, // Escape
{0x24, 0x0D}, // Enter
{0x31, 0x20}, // Space
{0x33, 0x08}, // Backspace
{0x30, 0x09}, // Tab
{0x74, 0x2C}, // Print Screen
{0x72, 0x2D}, // Insert
{0x75, 0x2E}, // Delete
{0x73, 0x24}, // Home
{0x77, 0x23}, // End
{0x79, 0x21}, // Page Up
{0x7A, 0x22}, // Page Down
// arrow keys
{0x7B, 0x25}, // Left Arrow
{0x7C, 0x27}, // Right Arrow
{0x7E, 0x26}, // Up Arrow
{0x7D, 0x28}, // Down Arrow
// numpad
{0x52, 0x60}, // Numpad 0
{0x53, 0x61}, // Numpad 1
{0x54, 0x62}, // Numpad 2
{0x55, 0x63}, // Numpad 3
{0x56, 0x64}, // Numpad 4
{0x57, 0x65}, // Numpad 5
{0x58, 0x66}, // Numpad 6
{0x59, 0x67}, // Numpad 7
{0x5B, 0x68}, // Numpad 8
{0x5C, 0x69}, // Numpad 9
{0x41, 0x6E}, // Numpad .
{0x4B, 0x6F}, // Numpad /
{0x43, 0x6A}, // Numpad *
{0x4E, 0x6D}, // Numpad -
{0x45, 0x6B}, // Numpad +
// symbol keys
{0x29, 0xBA}, // ; (Semicolon)
{0x27, 0xDE}, // ' (Quote)
{0x32, 0xC0}, // ` (Backtick)
{0x2B, 0xBC}, // , (Comma)
{0x2F, 0xBE}, // . (Period)
{0x2C, 0xBF}, // / (Slash)
{0x2A, 0xDC}, // \ (Backslash)
{0x21, 0xDB}, // [ (Left Bracket)
{0x1E, 0xDD}, // ] (Right Bracket)
{0x1B, 0xBD}, // - (Minus)
{0x18, 0xBB}, // = (Equals)
// modifier keys
{0x39, 0x14}, // Caps Lock
{0x38, 0xA0}, // Shift (Left)
{0x3C, 0xA1}, // Shift (Right)
{0x3B, 0xA2}, // Control (Left)
{0x3E, 0xA3}, // Control (Right)
{0x3A, 0xA4}, // Alt (Left)
{0x3D, 0xA5}, // Alt (Right)
{0x37, 0x5B}, // Left Command (Windows key)
{0x36, 0x5C}, // Right Command
};
#endif

View File

@@ -30,7 +30,7 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
if (!keyboard_hook_) {
LOG_ERROR("Failed to install keyboard hook!")
LOG_ERROR("Failed to install keyboard hook");
return -1;
}
return 0;

View File

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

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,124 @@
#include "mouse_controller.h"
#include "log.h"
#include <X11/extensions/XTest.h>
#include "rd_log.h"
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);
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;
}
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));
return 0;
}
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));
void MouseController::SetMousePosition(int x, int y) {
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
XFlush(display_);
}
void MouseController::SimulateKeyDown(int kval) {
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
XFlush(display_);
}
void MouseController::SimulateKeyUp(int kval) {
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
XFlush(display_);
}
void MouseController::SimulateMouseWheel(int direction_button, int count) {
for (int i = 0; i < count; ++i) {
XTestFakeButtonEvent(display_, direction_button, True, CurrentTime);
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
}
XFlush(display_);
}

View File

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

View File

@@ -8,47 +8,94 @@ 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;
}

View File

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

View File

@@ -6,34 +6,59 @@ 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 = (LONG)remote_action.m.x;
ip.mi.dy = (LONG)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);

View File

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

View File

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

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\"")
@@ -10,9 +10,7 @@
#include "render.h"
int main([[maybe_unused]] int argc, [[maybe_unused]] char *argv[]) {
LOG_INFO("Remote desk");
Render render;
render.Run();
return 0;

View File

@@ -10,94 +10,139 @@
#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_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 = {
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"};
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 = {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> recent_connections = {u8"近期连接",
"Recent Connections"};
static std::vector<std::string> disconnect = {u8"断开连接", "Disconnect"};
static std::vector<std::string> fullscreen = {u8"全屏", " Fullscreen"};
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 = {
u8"显示流量统计", "Show Net Traffic Stats"};
reinterpret_cast<const char*>(u8"显示流量统计"), "Show Net Traffic Stats"};
static std::vector<std::string> hide_net_traffic_stats = {
u8"隐藏流量统计", "Hide Net Traffic Stats"};
static std::vector<std::string> video = {u8"视频", "Video"};
static std::vector<std::string> audio = {u8"", "Audio"};
static std::vector<std::string> data = {u8"数据", "Data"};
static std::vector<std::string> total = {u8"总计", "Total"};
static std::vector<std::string> in = {u8"输入", "In"};
static std::vector<std::string> out = {u8"输出", "Out"};
static std::vector<std::string> loss_rate = {u8"丢包率", "Loss Rate"};
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"};
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 = {
u8"启用硬件编解码器:", "Enable Hardware Video Codec:"};
static std::vector<std::string> enable_turn = {u8"启用中继服务:",
"Enable TURN Service:"};
reinterpret_cast<const char*>(u8"启用硬件编解码器:"),
"Enable Hardware Video Codec:"};
static std::vector<std::string> enable_turn = {
reinterpret_cast<const char*>(u8"启用中继服务:"), "Enable TURN Service:"};
static std::vector<std::string> ok = {u8"确认", "OK"};
static std::vector<std::string> cancel = {u8"取消", "Cancel"};
static std::vector<std::string> enable_srtp = {
reinterpret_cast<const char*>(u8"启用SRTP:"), "Enable SRTP:"};
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 = {
u8"请输入六位密码:", "Please input a six-char password:"};
reinterpret_cast<const char*>(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> 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 = {
u8"请重新输入密码", "Please input password again"};
reinterpret_cast<const char*>(u8"请重新输入密码"),
"Please input password again"};
static std::vector<std::string> remember_password = {u8"记住密码",
"Remember password"};
static std::vector<std::string> remember_password = {
reinterpret_cast<const char*>(u8"记住密码"), "Remember password"};
static std::vector<std::string> signal_connected = {u8"已连接服务器",
"Connected"};
static std::vector<std::string> signal_disconnected = {u8"未连接服务器",
"Disconnected"};
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 = {u8"对等连接已建立",
"P2P Connected"};
static std::vector<std::string> p2p_disconnected = {u8"对等连接已断开",
"P2P Disconnected"};
static std::vector<std::string> p2p_connecting = {u8"正在建立对等连接...",
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 = {u8"对等连接失败", "P2P Failed"};
static std::vector<std::string> p2p_closed = {u8"对等连接已关闭", "P2P closed"};
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 = {u8"无此ID", "No such ID"};
static std::vector<std::string> no_such_id = {
reinterpret_cast<const char*>(u8"无此ID"), "No such ID"};
static std::vector<std::string> about = {u8"关于", "About"};
static std::vector<std::string> version = {u8"版本", "Version"};
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 = {
u8"确认删除此连接", "Confirm to delete this connection"};
reinterpret_cast<const char*>(u8"确认删除此连接"),
"Confirm to delete this connection"};
} // namespace localization
#endif

View File

@@ -1,15 +1,40 @@
#include "rd_log.h"
std::shared_ptr<spdlog::logger> get_logger() {
if (auto logger = spdlog::get(LOGGER_NAME)) {
return logger;
#include <atomic>
#include <filesystem>
namespace {
std::string g_log_dir = "logs";
std::once_flag g_logger_once_flag;
std::shared_ptr<spdlog::logger> g_logger;
std::atomic<bool> g_logger_created{false};
} // namespace
void InitLogger(const std::string& log_dir) {
if (g_logger_created.load()) {
LOG_WARN(
"InitLogger called after logger initialized. Ignoring log_dir: {}, "
"using previous log_dir: {}",
log_dir, g_log_dir);
return;
}
g_log_dir = log_dir;
}
std::shared_ptr<spdlog::logger> get_logger() {
std::call_once(g_logger_once_flag, []() {
g_logger_created.store(true);
std::error_code ec;
std::filesystem::create_directories(g_log_dir, ec);
auto now = std::chrono::system_clock::now() + std::chrono::hours(8);
auto now_time = std::chrono::system_clock::to_time_t(now);
std::tm tm_info;
#ifdef _WIN32
gmtime_s(&tm_info, &now_time);
#else
@@ -17,21 +42,21 @@ std::shared_ptr<spdlog::logger> get_logger() {
#endif
std::stringstream ss;
std::string filename;
ss << LOGGER_NAME;
ss << std::put_time(&tm_info, "-%Y%m%d-%H%M%S.log");
ss >> filename;
std::string path = "logs/" + filename;
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>(
path, 1048576 * 5, 3));
filename, 5 * 1024 * 1024, 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);
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 combined_logger;
return g_logger;
}

View File

@@ -1,7 +1,7 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-26
* 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,20 +23,17 @@
#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";
constexpr auto LOGGER_NAME = "crossdesk";
void InitLogger(const std::string& log_dir);
std::shared_ptr<spdlog::logger> get_logger();
#define LOG_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__);
#define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__);
#define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__);
#define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__);
#define LOG_INFO(...) SPDLOG_LOGGER_INFO(get_logger(), __VA_ARGS__)
#define LOG_WARN(...) SPDLOG_LOGGER_WARN(get_logger(), __VA_ARGS__)
#define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(get_logger(), __VA_ARGS__)
#define LOG_FATAL(...) SPDLOG_LOGGER_CRITICAL(get_logger(), __VA_ARGS__)
#endif

View File

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

View File

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

View File

@@ -1,152 +1,174 @@
#include "screen_capturer_x11.h"
#include <iostream>
#include <chrono>
#include <thread>
#include "log.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
unsigned char nv12_buffer_[NV12_BUFFER_SIZE];
#include "libyuv.h"
#include "rd_log.h"
ScreenCapturerX11::ScreenCapturerX11() {}
ScreenCapturerX11::~ScreenCapturerX11() {
if (inited_ && capture_thread_.joinable()) {
capture_thread_.join();
inited_ = false;
}
}
ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); }
int ScreenCapturerX11::Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb) {
if (cb) {
_on_data = cb;
int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
display_ = XOpenDisplay(nullptr);
if (!display_) {
LOG_ERROR("Cannot connect to X server");
return -1;
}
root_ = DefaultRootWindow(display_);
screen_res_ = XRRGetScreenResources(display_, root_);
if (!screen_res_) {
LOG_ERROR("Failed to get screen resources");
XCloseDisplay(display_);
return 1;
}
for (int i = 0; i < screen_res_->noutput; ++i) {
RROutput output = screen_res_->outputs[i];
XRROutputInfo* output_info =
XRRGetOutputInfo(display_, screen_res_, output);
if (output_info->connection == RR_Connected && output_info->crtc != 0) {
XRRCrtcInfo* crtc_info =
XRRGetCrtcInfo(display_, screen_res_, output_info->crtc);
display_info_list_.push_back(
DisplayInfo((void*)display_, output_info->name, true, crtc_info->x,
crtc_info->y, crtc_info->width, crtc_info->height));
XRRFreeCrtcInfo(crtc_info);
}
if (output_info) {
XRRFreeOutputInfo(output_info);
}
}
XWindowAttributes attr;
XGetWindowAttributes(display_, root_, &attr);
width_ = attr.width;
height_ = attr.height;
if (width_ % 2 != 0 || height_ % 2 != 0) {
LOG_ERROR("Width and height must be even numbers");
return -2;
}
fps_ = fps;
callback_ = cb;
av_log_set_level(AV_LOG_QUIET);
pFormatCtx_ = avformat_alloc_context();
avdevice_register_all();
// grabbing frame rate
av_dict_set(&options_, "framerate", "30", 0);
// 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);
}

View File

@@ -1,23 +1,24 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-05-07
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SCREEN_CAPTURER_X11_H_
#define _SCREEN_CAPTURER_X11_H_
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xrandr.h>
#include <atomic>
#include <cstring>
#include <functional>
#include <string>
#include <iostream>
#include <thread>
#include <vector>
#include "screen_capturer.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
class ScreenCapturerX11 : public ScreenCapturer {
public:
@@ -25,60 +26,39 @@ class ScreenCapturerX11 : public ScreenCapturer {
~ScreenCapturerX11();
public:
virtual int Init(const RECORD_DESKTOP_RECT &rect, const int fps,
cb_desktop_data cb);
int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
virtual int Destroy();
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
virtual int Start();
int SwitchTo(int monitor_index) override;
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_;
};
#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() {}

View File

@@ -1,256 +0,0 @@
/*
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "screen_capturer_sck.h"
#include "rd_log.h"
#include <CoreGraphics/CoreGraphics.h>
#include <IOSurface/IOSurface.h>
#include <ScreenCaptureKit/ScreenCaptureKit.h>
#include <atomic>
#include <mutex>
class ScreenCapturerSckImpl;
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
// introduced in macOS 14.
API_AVAILABLE(macos(14.0))
@interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput>
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer;
- (void)onShareableContentCreated:(SCShareableContent *)content;
// Called just before the capturer is destroyed. This avoids a dangling pointer,
// and prevents any new calls into a deleted capturer. If any method-call on the
// capturer is currently running on a different thread, this blocks until it
// completes.
- (void)releaseCapturer;
@end
class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
public:
explicit ScreenCapturerSckImpl();
ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete;
ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete;
~ScreenCapturerSckImpl();
public:
int Init(const int fps, cb_desktop_data cb);
void OnReceiveContent(SCShareableContent *content);
void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment);
virtual int Destroy() { return 0; }
virtual int Start() { return 0; }
virtual int Stop() { return 0; }
private:
SckHelper *__strong helper_;
SCStream *__strong stream_;
cb_desktop_data _on_data;
unsigned char *nv12_frame_ = nullptr;
bool permanent_error_ = false;
CGDirectDisplayID current_display_ = -1;
std::mutex mtx_;
};
@implementation SckHelper {
// This lock is to prevent the capturer being destroyed while an instance
// method is still running on another thread.
std::mutex helper_mtx_;
ScreenCapturerSckImpl *_capturer;
}
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer {
self = [super init];
if (self) {
_capturer = capturer;
}
return self;
}
- (void)onShareableContentCreated:(SCShareableContent *)content {
std::lock_guard<std::mutex> lock(helper_mtx_);
if (_capturer) {
_capturer->OnReceiveContent(content);
} else {
LOG_ERROR("Invalid capturer");
}
}
- (void)stream:(SCStream *)stream
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
ofType:(SCStreamOutputType)type {
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (!pixelBuffer) {
return;
}
IOSurfaceRef ioSurface = CVPixelBufferGetIOSurface(pixelBuffer);
if (!ioSurface) {
return;
}
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(
sampleBuffer, /*createIfNecessary=*/false);
if (!attachmentsArray || CFArrayGetCount(attachmentsArray) <= 0) {
LOG_ERROR("Discarding frame with no attachments");
return;
}
CFDictionaryRef attachment =
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0));
std::lock_guard<std::mutex> lock(helper_mtx_);
if (_capturer) {
_capturer->OnNewIOSurface(ioSurface, attachment);
}
}
- (void)releaseCapturer {
std::lock_guard<std::mutex> lock(helper_mtx_);
_capturer = nullptr;
}
@end
ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
helper_ = [[SckHelper alloc] initWithCapturer:this];
}
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
[stream_ stopCaptureWithCompletionHandler:nil];
[helper_ releaseCapturer];
}
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
_on_data = cb;
SckHelper *local_helper = helper_;
auto handler = ^(SCShareableContent *content, NSError *error) {
[local_helper onShareableContentCreated:content];
};
[SCShareableContent getShareableContentWithCompletionHandler:handler];
return 0;
}
void ScreenCapturerSckImpl::OnReceiveContent(SCShareableContent *content) {
if (!content) {
LOG_ERROR("getShareableContent failed");
permanent_error_ = true;
return;
}
if (!content.displays.count) {
LOG_ERROR("getShareableContent returned no displays");
permanent_error_ = true;
return;
}
SCDisplay *captured_display;
{
std::lock_guard<std::mutex> lock(mtx_);
for (SCDisplay *display in content.displays) {
if (current_display_ == display.displayID) {
captured_display = display;
break;
}
}
if (!captured_display) {
if (-1 == current_display_) {
LOG_ERROR("Full screen capture is not supported, falling back to first "
"display");
} else {
LOG_ERROR("Display [{}] not found, falling back to first display",
current_display_);
}
captured_display = content.displays.firstObject;
}
}
SCContentFilter *filter =
[[SCContentFilter alloc] initWithDisplay:captured_display
excludingWindows:@[]];
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
config.showsCursor = false;
config.width = filter.contentRect.size.width * filter.pointPixelScale;
config.height = filter.contentRect.size.height * filter.pointPixelScale;
config.captureResolution = SCCaptureResolutionNominal;
std::lock_guard<std::mutex> lock(mtx_);
if (stream_) {
LOG_INFO("Updating stream configuration");
[stream_ updateContentFilter:filter completionHandler:nil];
[stream_ updateConfiguration:config completionHandler:nil];
} else {
stream_ = [[SCStream alloc] initWithFilter:filter
configuration:config
delegate:helper_];
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
// best performance.
NSError *add_stream_output_error;
bool add_stream_output_result =
[stream_ addStreamOutput:helper_
type:SCStreamOutputTypeScreen
sampleHandlerQueue:nil
error:&add_stream_output_error];
if (!add_stream_output_result) {
stream_ = nil;
LOG_ERROR("addStreamOutput failed");
permanent_error_ = true;
return;
}
auto handler = ^(NSError *error) {
if (error) {
// It should be safe to access `this` here, because the C++ destructor
// calls stopCaptureWithCompletionHandler on the stream, which cancels
// this handler.
permanent_error_ = true;
LOG_ERROR("startCaptureWithCompletionHandler failed");
} else {
LOG_INFO("Capture started");
}
};
[stream_ startCaptureWithCompletionHandler:handler];
}
}
void ScreenCapturerSckImpl::OnNewIOSurface(IOSurfaceRef io_surface,
CFDictionaryRef attachment) {
size_t width = IOSurfaceGetWidth(io_surface);
size_t height = IOSurfaceGetHeight(io_surface);
uint32_t aseed;
IOSurfaceLock(io_surface, kIOSurfaceLockReadOnly, &aseed);
nv12_frame_ =
static_cast<unsigned char *>(IOSurfaceGetBaseAddress(io_surface));
_on_data(nv12_frame_, width * height * 3 / 2, width, height);
IOSurfaceUnlock(io_surface, kIOSurfaceLockReadOnly, &aseed);
}
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
return std::make_unique<ScreenCapturerSckImpl>();
}

View File

@@ -0,0 +1,73 @@
#include "screen_capturer_sck.h"
#include "rd_log.h"
ScreenCapturerSck::ScreenCapturerSck() {}
ScreenCapturerSck::~ScreenCapturerSck() {}
int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) {
if (cb) {
on_data_ = cb;
} else {
LOG_ERROR("cb is null");
return -1;
}
screen_capturer_sck_impl_ = CreateScreenCapturerSck();
screen_capturer_sck_impl_->Init(fps, on_data_);
return 0;
}
int ScreenCapturerSck::Destroy() {
if (screen_capturer_sck_impl_) {
screen_capturer_sck_impl_->Destroy();
}
return 0;
}
int ScreenCapturerSck::Start() {
screen_capturer_sck_impl_->Start();
return 0;
}
int ScreenCapturerSck::Stop() {
if (screen_capturer_sck_impl_) {
screen_capturer_sck_impl_->Stop();
}
return 0;
}
int ScreenCapturerSck::Pause(int monitor_index) {
if (screen_capturer_sck_impl_) {
return screen_capturer_sck_impl_->Pause(monitor_index);
}
return 0;
}
int ScreenCapturerSck::Resume(int monitor_index) {
if (screen_capturer_sck_impl_) {
return screen_capturer_sck_impl_->Resume(monitor_index);
}
return 0;
}
int ScreenCapturerSck::SwitchTo(int monitor_index) {
if (screen_capturer_sck_impl_) {
return screen_capturer_sck_impl_->SwitchTo(monitor_index);
}
return -1;
}
std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
if (screen_capturer_sck_impl_) {
return screen_capturer_sck_impl_->GetDisplayInfoList();
}
return std::vector<DisplayInfo>();
}
void ScreenCapturerSck::OnFrame() {}
void ScreenCapturerSck::CleanUp() {}

View File

@@ -22,17 +22,17 @@ class ScreenCapturerSck : public ScreenCapturer {
~ScreenCapturerSck();
public:
virtual int Init(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();

View File

@@ -0,0 +1,488 @@
/*
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "screen_capturer_sck.h"
#include <ApplicationServices/ApplicationServices.h>
#include <CoreGraphics/CoreGraphics.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/graphics/IOGraphicsLib.h>
#include <IOSurface/IOSurface.h>
#include <ScreenCaptureKit/ScreenCaptureKit.h>
#include <atomic>
#include <mutex>
#include <vector>
#include "display_info.h"
#include "rd_log.h"
static const int kFullDesktopScreenId = -1;
class ScreenCapturerSckImpl;
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
// introduced in macOS 14.
API_AVAILABLE(macos(14.0))
@interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput>
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer;
- (void)onShareableContentCreated:(SCShareableContent *)content;
// Called just before the capturer is destroyed. This avoids a dangling pointer,
// and prevents any new calls into a deleted capturer. If any method-call on the
// capturer is currently running on a different thread, this blocks until it
// completes.
- (void)releaseCapturer;
@end
class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
public:
explicit ScreenCapturerSckImpl();
ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete;
ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete;
~ScreenCapturerSckImpl();
public:
int Init(const int fps, cb_desktop_data cb) override;
int Start() override;
int SwitchTo(int monitor_index) override;
int Destroy() override;
int Stop() override;
int Pause(int monitor_index) override { return 0; }
int Resume(int monitor_index) override { return 0; }
std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; }
private:
std::vector<DisplayInfo> display_info_list_;
std::map<int, CGDirectDisplayID> display_id_map_;
std::map<CGDirectDisplayID, int> display_id_map_reverse_;
std::map<CGDirectDisplayID, std::string> display_id_name_map_;
unsigned char *nv12_frame_ = nullptr;
int width_ = 0;
int height_ = 0;
int fps_ = 60;
public:
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
// nil if an error occurred. May run on an arbitrary thread.
void OnShareableContentCreated(SCShareableContent *content);
// Called by SckHelper to notify of a newly captured frame. May run on an arbitrary thread.
// void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment);
void OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, CFDictionaryRef attachment);
private:
// Called when starting the capturer or the configuration has changed (either from a
// SwitchTo() call, or the screen-resolution has changed). This tells SCK to fetch new
// shareable content, and the completion-handler will either start a new stream, or reconfigure
// the existing stream. Runs on the caller's thread.
void StartOrReconfigureCapturer();
// Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
SckHelper *__strong helper_;
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
// thread.
cb_desktop_data _on_data = nullptr;
// Signals that a permanent error occurred. This may be set on any thread, and is read by
// CaptureFrame() which runs on the caller's thread.
std::atomic<bool> permanent_error_ = false;
// Guards some variables that may be accessed on different threads.
std::mutex lock_;
// Provides captured desktop frames.
SCStream *__strong stream_;
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
// support full-desktop capture, and will fall back to the first display.
CGDirectDisplayID current_display_ = 0;
};
std::string GetDisplayName(CGDirectDisplayID display_id) {
io_iterator_t iter;
io_service_t serv = 0, matched_serv = 0;
CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");
if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter) != KERN_SUCCESS) {
return "";
}
while ((serv = IOIteratorNext(iter)) != 0) {
CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
if (info) {
CFNumberRef vendorID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
CFNumberRef productID = (CFNumberRef)CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
uint32_t vID = 0, pID = 0;
if (vendorID && productID && CFNumberGetValue(vendorID, kCFNumberIntType, &vID) &&
CFNumberGetValue(productID, kCFNumberIntType, &pID) &&
vID == CGDisplayVendorNumber(display_id) && pID == CGDisplayModelNumber(display_id)) {
matched_serv = serv;
CFRelease(info);
break;
}
CFRelease(info);
}
IOObjectRelease(serv);
}
IOObjectRelease(iter);
if (!matched_serv) return "";
CFDictionaryRef display_info =
IODisplayCreateInfoDictionary(matched_serv, kIODisplayOnlyPreferredName);
IOObjectRelease(matched_serv);
if (!display_info) return "";
CFDictionaryRef product_name_dict =
(CFDictionaryRef)CFDictionaryGetValue(display_info, CFSTR(kDisplayProductName));
std::string result;
if (product_name_dict) {
CFIndex count = CFDictionaryGetCount(product_name_dict);
if (count > 0) {
std::vector<const void *> keys(count);
std::vector<const void *> values(count);
CFDictionaryGetKeysAndValues(product_name_dict, keys.data(), values.data());
CFStringRef name_ref = (CFStringRef)values[0];
if (name_ref) {
CFIndex maxSize =
CFStringGetMaximumSizeForEncoding(CFStringGetLength(name_ref), kCFStringEncodingUTF8) +
1;
std::vector<char> buffer(maxSize);
if (CFStringGetCString(name_ref, buffer.data(), buffer.size(), kCFStringEncodingUTF8)) {
result = buffer.data();
}
}
}
}
CFRelease(display_info);
return result;
}
ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
helper_ = [[SckHelper alloc] initWithCapturer:this];
}
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
display_info_list_.clear();
display_id_map_.clear();
display_id_map_reverse_.clear();
display_id_name_map_.clear();
if (nv12_frame_) {
delete[] nv12_frame_;
nv12_frame_ = nullptr;
}
[stream_ stopCaptureWithCompletionHandler:nil];
[helper_ releaseCapturer];
}
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
_on_data = cb;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block SCShareableContent *content = nil;
[SCShareableContent
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
if (error) {
NSLog(@"Failed to get shareable content: %@", error);
} else {
content = result;
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (!content || content.displays.count == 0) {
LOG_ERROR("Failed to get display info");
return 0;
}
CGDirectDisplayID displays[10];
uint32_t count;
CGGetActiveDisplayList(10, displays, &count);
int unnamed_count = 1;
for (SCDisplay *display in content.displays) {
CGDirectDisplayID display_id = display.displayID;
CGRect bounds = CGDisplayBounds(display_id);
bool is_primary = CGDisplayIsMain(display_id);
std::string name;
name = GetDisplayName(display_id);
if (name.empty()) {
name = "Display " + std::to_string(unnamed_count++);
}
DisplayInfo info((void *)(uintptr_t)display_id, name, is_primary,
static_cast<int>(bounds.origin.x), static_cast<int>(bounds.origin.y),
static_cast<int>(bounds.origin.x + bounds.size.width),
static_cast<int>(bounds.origin.y + bounds.size.height));
display_info_list_.push_back(info);
display_id_map_[display_info_list_.size() - 1] = display_id;
display_id_map_reverse_[display_id] = display_info_list_.size() - 1;
display_id_name_map_[display_id] = name;
}
return 0;
}
int ScreenCapturerSckImpl::Start() {
StartOrReconfigureCapturer();
return 0;
}
int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
if (stream_) {
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) {
std::lock_guard<std::mutex> lock(lock_);
stream_ = nil;
current_display_ = display_id_map_[monitor_index];
StartOrReconfigureCapturer();
}];
} else {
current_display_ = display_id_map_[monitor_index];
StartOrReconfigureCapturer();
}
return 0;
}
int ScreenCapturerSckImpl::Destroy() {
std::lock_guard<std::mutex> lock(lock_);
if (stream_) {
LOG_INFO("Destroying stream");
[stream_ stopCaptureWithCompletionHandler:nil];
stream_ = nil;
}
current_display_ = 0;
permanent_error_ = false;
_on_data = nullptr;
[helper_ releaseCapturer];
helper_ = nil;
return 0;
}
int ScreenCapturerSckImpl::Stop() {
std::lock_guard<std::mutex> lock(lock_);
if (stream_) {
LOG_INFO("Stopping stream");
[stream_ stopCaptureWithCompletionHandler:nil];
stream_ = nil;
}
current_display_ = 0;
return 0;
}
void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *content) {
if (!content) {
LOG_ERROR("getShareableContent failed");
permanent_error_ = true;
return;
}
if (!content.displays.count) {
LOG_ERROR("getShareableContent returned no displays");
permanent_error_ = true;
return;
}
SCDisplay *captured_display;
{
std::lock_guard<std::mutex> lock(lock_);
for (SCDisplay *display in content.displays) {
if (current_display_ == display.displayID) {
LOG_WARN("current display: {}, name: {}", current_display_,
display_id_name_map_[current_display_]);
captured_display = display;
break;
}
}
if (!captured_display) {
captured_display = content.displays.firstObject;
current_display_ = captured_display.displayID;
}
}
SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display
excludingWindows:@[]];
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
config.showsCursor = false;
config.width = filter.contentRect.size.width * filter.pointPixelScale;
config.height = filter.contentRect.size.height * filter.pointPixelScale;
config.captureResolution = SCCaptureResolutionAutomatic;
config.minimumFrameInterval = CMTimeMake(1, fps_);
std::lock_guard<std::mutex> lock(lock_);
if (stream_) {
LOG_INFO("Updating stream configuration");
[stream_ updateContentFilter:filter completionHandler:nil];
[stream_ updateConfiguration:config completionHandler:nil];
} else {
stream_ = [[SCStream alloc] initWithFilter:filter configuration:config delegate:helper_];
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
// best performance.
NSError *add_stream_output_error;
dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL);
bool add_stream_output_result = [stream_ addStreamOutput:helper_
type:SCStreamOutputTypeScreen
sampleHandlerQueue:queue
error:&add_stream_output_error];
if (!add_stream_output_result) {
stream_ = nil;
LOG_ERROR("addStreamOutput failed");
permanent_error_ = true;
return;
}
auto handler = ^(NSError *error) {
if (error) {
// It should be safe to access `this` here, because the C++ destructor
// calls stopCaptureWithCompletionHandler on the stream, which cancels
// this handler.
permanent_error_ = true;
LOG_ERROR("startCaptureWithCompletionHandler failed");
} else {
LOG_INFO("Capture started");
}
};
[stream_ startCaptureWithCompletionHandler:handler];
}
}
void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
CFDictionaryRef attachment) {
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
if (status != kCVReturnSuccess) {
LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status);
return;
}
size_t required_size = width * height * 3 / 2;
if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) {
delete[] nv12_frame_;
nv12_frame_ = new unsigned char[required_size];
width_ = width;
height_ = height;
}
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
unsigned char *dst_y = nv12_frame_;
for (size_t row = 0; row < height; ++row) {
memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width);
}
unsigned char *dst_uv = nv12_frame_ + width * height;
for (size_t row = 0; row < height / 2; ++row) {
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
}
_on_data(nv12_frame_, width * height * 3 / 2, width, height,
display_id_name_map_[current_display_].c_str());
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
}
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
// The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_`
// inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is
// deleted before the block is executed.
SckHelper *local_helper = helper_;
auto handler = ^(SCShareableContent *content, NSError *error) {
[local_helper onShareableContentCreated:content];
};
[SCShareableContent getShareableContentWithCompletionHandler:handler];
}
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
return std::make_unique<ScreenCapturerSckImpl>();
}
@implementation SckHelper {
// This lock is to prevent the capturer being destroyed while an instance
// method is still running on another thread.
std::mutex _capturer_lock;
ScreenCapturerSckImpl *_capturer;
}
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer {
self = [super init];
if (self) {
_capturer = capturer;
}
return self;
}
- (void)onShareableContentCreated:(SCShareableContent *)content {
std::lock_guard<std::mutex> lock(_capturer_lock);
if (_capturer) {
_capturer->OnShareableContentCreated(content);
} else {
LOG_ERROR("Invalid capturer");
}
}
- (void)stream:(SCStream *)stream
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
ofType:(SCStreamOutputType)type {
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (!pixelBuffer) {
return;
}
CFRetain(pixelBuffer);
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) {
LOG_ERROR("Discarding frame with no attachments");
CFRelease(pixelBuffer);
return;
}
CFDictionaryRef attachment =
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0));
std::lock_guard<std::mutex> lock(_capturer_lock);
if (_capturer) {
_capturer->OnNewCVPixelBuffer(pixelBuffer, attachment);
}
CFRelease(pixelBuffer);
}
- (void)releaseCapturer {
std::lock_guard<std::mutex> lock(_capturer_lock);
_capturer = nullptr;
}
@end

View File

@@ -9,9 +9,12 @@
#include <functional>
#include "display_info.h"
class ScreenCapturer {
public:
typedef std::function<void(unsigned char *, int, int, int)> cb_desktop_data;
typedef std::function<void(unsigned char*, int, int, int, const char*)>
cb_desktop_data;
public:
virtual ~ScreenCapturer() {}
@@ -19,10 +22,13 @@ class ScreenCapturer {
public:
virtual int Init(const int fps, cb_desktop_data cb) = 0;
virtual int Destroy() = 0;
virtual int Start() = 0;
virtual int Stop() = 0;
virtual int Pause(int monitor_index) = 0;
virtual int Resume(int monitor_index) = 0;
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
virtual int SwitchTo(int monitor_index) = 0;
};
#endif

View File

@@ -8,19 +8,44 @@
#include <iostream>
#include "libyuv.h"
#include "rd_log.h"
static std::vector<DisplayInfo> gs_display_list;
std::string WideToUtf8(const wchar_t *wideStr) {
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
nullptr, nullptr);
std::string result(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr,
nullptr);
result.pop_back();
return result;
}
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
[[maybe_unused]] LPRECT lprc, LPARAM data) {
MONITORINFOEX info_ex;
info_ex.cbSize = sizeof(MONITORINFOEX);
MONITORINFOEX monitor_info_;
monitor_info_.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(hmonitor, &info_ex);
if (info_ex.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
if (info_ex.dwFlags & MONITORINFOF_PRIMARY) {
if (GetMonitorInfo(hmonitor, &monitor_info_)) {
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
gs_display_list.insert(
gs_display_list.begin(),
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR *)data = hmonitor;
} else {
gs_display_list.push_back(DisplayInfo(
(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
}
}
if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
return true;
}
@@ -28,12 +53,13 @@ BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
HMONITOR GetPrimaryMonitor() {
HMONITOR hmonitor = nullptr;
gs_display_list.clear();
::EnumDisplayMonitors(NULL, NULL, EnumMonitorProc, (LPARAM)&hmonitor);
return hmonitor;
}
ScreenCapturerWgc::ScreenCapturerWgc() {}
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
ScreenCapturerWgc::~ScreenCapturerWgc() {
Stop();
@@ -64,116 +90,162 @@ bool ScreenCapturerWgc::IsWgcSupported() {
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
int error = 0;
if (_inited == true) return error;
if (inited_ == true) return error;
// nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2];
// nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2];
_fps = fps;
fps_ = fps;
_on_data = cb;
on_data_ = cb;
do {
if (!IsWgcSupported()) {
std::cout << "AE_UNSUPPORT" << std::endl;
LOG_ERROR("WGC not supported");
error = 2;
break;
}
session_ = new WgcSessionImpl();
if (!session_) {
error = -1;
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
break;
}
session_->RegisterObserver(this);
error = session_->Initialize(GetPrimaryMonitor());
_inited = true;
} while (0);
if (error != 0) {
}
return error;
}
monitor_ = GetPrimaryMonitor();
display_info_list_ = gs_display_list;
if (display_info_list_.empty()) {
LOG_ERROR("No display found");
return -1;
}
for (int i = 0; i < display_info_list_.size(); i++) {
const auto &display = display_info_list_[i];
LOG_INFO(
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
"({}, {})",
i, display.name, (display.is_primary ? "yes" : "no"), display.left,
display.top, display.right, display.bottom);
sessions_.push_back(
{std::make_unique<WgcSessionImpl>(i), false, false, false});
sessions_.back().session_->RegisterObserver(this);
error = sessions_.back().session_->Initialize((HMONITOR)display.handle);
if (error != 0) {
return error;
}
sessions_[i].inited_ = true;
inited_ = true;
}
LOG_INFO("Default on monitor {}:{}", monitor_index_,
display_info_list_[monitor_index_].name);
return 0;
}
int ScreenCapturerWgc::Destroy() { return 0; }
int ScreenCapturerWgc::Start() {
if (_running == true) {
std::cout << "record desktop duplication is already running" << std::endl;
if (running_ == true) {
LOG_ERROR("Screen capturer already running");
return 0;
}
if (_inited == false) {
std::cout << "AE_NEED_INIT" << std::endl;
if (inited_ == false) {
LOG_ERROR("Screen capturer not inited");
return 4;
}
_running = true;
session_->Start();
for (int i = 0; i < sessions_.size(); i++) {
if (sessions_[i].inited_ == false) {
LOG_ERROR("Session {} not inited", i);
continue;
}
if (sessions_[i].running_) {
LOG_ERROR("Session {} is already running", i);
} else {
sessions_[i].session_->Start();
if (i != 0) {
sessions_[i].session_->Pause();
sessions_[i].paused_ = true;
}
sessions_[i].running_ = true;
}
running_ = true;
}
return 0;
}
int ScreenCapturerWgc::Pause() {
_paused = true;
if (session_) session_->Pause();
int ScreenCapturerWgc::Pause(int monitor_index) {
if (monitor_index >= sessions_.size() || monitor_index < 0) {
LOG_ERROR("Invalid session index: {}", monitor_index);
return -1;
}
if (!sessions_[monitor_index].paused_) {
sessions_[monitor_index].session_->Pause();
sessions_[monitor_index].paused_ = true;
LOG_INFO("Pausing session {}", monitor_index);
}
return 0;
}
int ScreenCapturerWgc::Resume() {
_paused = false;
if (session_) session_->Resume();
int ScreenCapturerWgc::Resume(int monitor_index) {
if (monitor_index >= sessions_.size() || monitor_index < 0) {
LOG_ERROR("Invalid session index: {}", monitor_index);
return -1;
}
if (sessions_[monitor_index].paused_) {
sessions_[monitor_index].session_->Resume();
sessions_[monitor_index].paused_ = false;
LOG_INFO("Resuming session {}", monitor_index);
}
return 0;
}
int ScreenCapturerWgc::Stop() {
_running = false;
if (session_) session_->Stop();
for (int i = 0; i < sessions_.size(); i++) {
if (sessions_[i].running_) {
sessions_[i].session_->Stop();
sessions_[i].running_ = false;
}
}
running_ = false;
return 0;
}
void ConvertABGRtoBGRA(const uint8_t *abgr_data, uint8_t *bgra_data, int width,
int height, int abgr_stride, int bgra_stride) {
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
int abgr_index = (i * abgr_stride + j) * 4;
int bgra_index = (i * bgra_stride + j) * 4;
int ScreenCapturerWgc::SwitchTo(int monitor_index) {
if (monitor_index_ == monitor_index) {
LOG_INFO("Already on monitor {}:{}", monitor_index_ + 1,
display_info_list_[monitor_index_].name);
return 0;
}
bgra_data[bgra_index + 0] = abgr_data[abgr_index + 2]; // 蓝色
bgra_data[bgra_index + 1] = abgr_data[abgr_index + 1]; // 绿色
bgra_data[bgra_index + 2] = abgr_data[abgr_index + 0]; // 红色
bgra_data[bgra_index + 3] = abgr_data[abgr_index + 3]; // Alpha
if (monitor_index >= display_info_list_.size()) {
LOG_ERROR("Invalid monitor index: {}", monitor_index);
return -1;
}
if (!sessions_[monitor_index].inited_) {
LOG_ERROR("Monitor {} not inited", monitor_index);
return -1;
}
Pause(monitor_index_);
monitor_index_ = monitor_index;
LOG_INFO("Switching to monitor {}:{}", monitor_index_,
display_info_list_[monitor_index_].name);
Resume(monitor_index);
return 0;
}
void ConvertBGRAtoABGR(const uint8_t *bgra_data, uint8_t *abgr_data, int width,
int height, int bgra_stride, int abgr_stride) {
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
int bgra_index = (i * bgra_stride + j) * 4;
int abgr_index = (i * abgr_stride + j) * 4;
abgr_data[abgr_index + 0] = bgra_data[bgra_index + 3]; // Alpha
abgr_data[abgr_index + 1] = bgra_data[bgra_index + 0]; // Blue
abgr_data[abgr_index + 2] = bgra_data[bgra_index + 1]; // Green
abgr_data[abgr_index + 3] = bgra_data[bgra_index + 2]; // Red
}
}
}
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
if (_on_data) {
// int width = 1280;
// int height = 720;
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame,
int id) {
if (on_data_) {
if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
}
@@ -183,15 +255,18 @@ void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
(uint8_t *)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height);
_on_data(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height);
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height, display_info_list_[id].name.c_str());
}
}
void ScreenCapturerWgc::CleanUp() {
_inited = false;
if (session_) session_->Release();
session_ = nullptr;
if (inited_) {
for (auto &session : sessions_) {
if (session.session_) {
session.session_->Stop();
}
}
sessions_.clear();
}
}

View File

@@ -5,6 +5,7 @@
#include <functional>
#include <string>
#include <thread>
#include <vector>
#include "screen_capturer.h"
#include "wgc_session.h"
@@ -19,37 +20,49 @@ class ScreenCapturerWgc : public ScreenCapturer,
public:
bool IsWgcSupported();
virtual int Init(const int fps, cb_desktop_data cb);
virtual int Destroy();
int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
virtual int Start();
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
int Pause();
int Resume();
virtual int Stop();
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
void OnFrame(const WgcSession::wgc_session_frame &frame);
int SwitchTo(int monitor_index);
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
protected:
void CleanUp();
private:
WgcSession *session_ = nullptr;
HMONITOR monitor_;
MONITORINFOEX monitor_info_;
std::vector<DisplayInfo> display_info_list_;
int monitor_index_ = 0;
std::atomic_bool _running;
std::atomic_bool _paused;
std::atomic_bool _inited;
private:
class WgcSessionInfo {
public:
std::unique_ptr<WgcSession> session_;
bool inited_ = false;
bool running_ = false;
bool paused_ = false;
};
std::thread _thread;
std::vector<WgcSessionInfo> sessions_;
std::string _device_name;
std::atomic_bool running_;
std::atomic_bool inited_;
int _fps;
int fps_ = 60;
cb_desktop_data _on_data = nullptr;
cb_desktop_data on_data_ = nullptr;
unsigned char *nv12_frame_ = nullptr;
unsigned char *nv12_frame_scaled_ = nullptr;
unsigned char* nv12_frame_ = nullptr;
unsigned char* nv12_frame_scaled_ = nullptr;
};
#endif

View File

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

View File

@@ -23,7 +23,7 @@ HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
}
WgcSessionImpl::WgcSessionImpl() {}
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
WgcSessionImpl::~WgcSessionImpl() {
Stop();
@@ -124,6 +124,8 @@ int WgcSessionImpl::Stop() {
int WgcSessionImpl::Pause() {
std::lock_guard locker(lock_);
is_paused_ = true;
CHECK_INIT;
return 0;
}
@@ -131,6 +133,8 @@ int WgcSessionImpl::Pause() {
int WgcSessionImpl::Resume() {
std::lock_guard locker(lock_);
is_paused_ = false;
CHECK_INIT;
return 0;
}
@@ -233,6 +237,10 @@ void WgcSessionImpl::OnFrame(
// copy to mapped texture
{
if (is_paused_) {
return;
}
auto frame_captured =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
@@ -256,11 +264,13 @@ void WgcSessionImpl::OnFrame(
// copy data from map_result.pData
if (map_result.pData && observer_) {
observer_->OnFrame(wgc_session_frame{
static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height), map_result.RowPitch,
observer_->OnFrame(
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height),
map_result.RowPitch,
const_cast<const unsigned char *>(
(unsigned char *)map_result.pData)});
(unsigned char *)map_result.pData)},
id_);
}
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);

View File

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

View File

@@ -3,10 +3,10 @@
#include "rd_log.h"
#include "render.h"
int Render::ConnectionStatusWindow() {
if (show_connection_status_window_) {
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,
@@ -33,55 +33,51 @@ int Render::ConnectionStatusWindow() {
ImGui::SetWindowFontScale(0.5f);
std::string text;
if (ConnectionStatus::Connecting == connection_status_) {
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 == connection_status_) {
} 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()) ||
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 == connection_status_) {
} 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()) ||
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 == connection_status_) {
} 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()) ||
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 == connection_status_) {
} 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()) ||
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 == connection_status_) {
} else if (ConnectionStatus::IncorrectPassword == props->connection_status_) {
if (!password_validating_) {
if (password_validating_time_ == 1) {
text = localization::input_password[localization_language_index_];
@@ -102,8 +98,8 @@ int Render::ConnectionStatusWindow() {
focus_on_input_widget_ = false;
}
ImGui::InputText("##password", remote_password_,
IM_ARRAYSIZE(remote_password_),
ImGui::InputText("##password", props->remote_password_,
IM_ARRAYSIZE(props->remote_password_),
ImGuiInputTextFlags_CharsNoBlank);
ImGui::SetWindowFontScale(0.4f);
@@ -113,9 +109,8 @@ int Render::ConnectionStatusWindow() {
.c_str());
ImGui::SetCursorPosX((window_width - text_size.x) * 0.5f - 13.0f);
ImGui::Checkbox(
localization::remember_password[localization_language_index_]
.c_str(),
&remember_password_);
localization::remember_password[localization_language_index_].c_str(),
&(props->remember_password_));
ImGui::SetWindowFontScale(0.5f);
ImGui::PopStyleVar();
@@ -127,7 +122,8 @@ int Render::ConnectionStatusWindow() {
ImGui::IsKeyPressed(ImGuiKey_Enter)) {
show_connection_status_window_ = true;
password_validating_ = true;
rejoin_ = true;
props->rejoin_ = true;
need_to_rejoin_ = true;
focus_on_input_widget_ = true;
}
@@ -136,7 +132,7 @@ int Render::ConnectionStatusWindow() {
if (ImGui::Button(
localization::cancel[localization_language_index_].c_str()) ||
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
memset(remote_password_, 0, sizeof(remote_password_));
memset(props->remote_password_, 0, sizeof(props->remote_password_));
show_connection_status_window_ = false;
focus_on_input_widget_ = true;
}
@@ -145,16 +141,18 @@ int Render::ConnectionStatusWindow() {
ImGui::SetCursorPosX(connection_status_window_width_ * 3 / 7);
ImGui::SetCursorPosY(connection_status_window_height_ * 2 / 3);
}
} else if (ConnectionStatus::NoSuchTransmissionId == connection_status_) {
} 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()) ||
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;
}
}
@@ -168,6 +166,6 @@ int Render::ConnectionStatusWindow() {
ImGui::End();
ImGui::PopStyleVar();
}
return 0;
return ret_flag;
}

View File

@@ -29,42 +29,83 @@ int LossRateDisplay(float loss_rate) {
return 0;
}
int Render::ControlBar() {
int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImVec2 mouse_button_pos = ImVec2(0, 0);
if (control_bar_expand_) {
ImGui::SetCursorPosX(
is_control_bar_in_left_ ? (control_window_width_ + 5.0f) : 38.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 (is_control_bar_in_left_) {
draw_list->AddLine(
ImVec2(ImGui::GetCursorScreenPos().x - 5.0f,
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 + control_window_height_),
ImGui::GetCursorScreenPos().y - 7.0f +
props->control_window_height_),
IM_COL32(178, 178, 178, 255), 1.0f);
}
mouse_button_pos = ImGui::GetCursorScreenPos();
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 = mouse_control_button_pressed_ ? ICON_FA_COMPUTER_MOUSE
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 (connection_established_) {
control_mouse_ = !control_mouse_;
if (props->connection_established_) {
start_keyboard_capturer_ = !start_keyboard_capturer_;
mouse_control_button_pressed_ = !mouse_control_button_pressed_;
mouse_control_button_label_ =
mouse_control_button_pressed_
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 (!mouse_control_button_pressed_) {
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),
@@ -82,26 +123,28 @@ int Render::ControlBar() {
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 = 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 (connection_established_) {
audio_capture_ = !audio_capture_;
audio_capture_button_pressed_ = !audio_capture_button_pressed_;
audio_capture_button_label_ =
audio_capture_button_pressed_
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 = audio_capture_button_pressed_;
SendDataFrame(peer_, (const char*)&remote_action,
sizeof(remote_action));
remote_action.a = props->audio_capture_button_pressed_;
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
}
}
if (!audio_capture_button_pressed_) {
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),
@@ -117,18 +160,19 @@ int Render::ControlBar() {
ImGui::SameLine();
// net traffic stats button
bool button_color_style_pushed = false;
if (net_traffic_stats_button_pressed_) {
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))) {
net_traffic_stats_button_pressed_ = !net_traffic_stats_button_pressed_;
control_window_height_is_changing_ = true;
net_traffic_stats_button_pressed_time_ = ImGui::GetTime();
net_traffic_stats_button_label_ =
net_traffic_stats_button_pressed_
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
@@ -145,63 +189,61 @@ int Render::ControlBar() {
fullscreen_button_pressed_ ? ICON_FA_COMPRESS : ICON_FA_EXPAND;
if (ImGui::Button(fullscreen.c_str(), ImVec2(25, 25))) {
fullscreen_button_pressed_ = !fullscreen_button_pressed_;
fullscreen_button_label_ =
props->fullscreen_button_label_ =
fullscreen_button_pressed_
? localization::exit_fullscreen[localization_language_index_]
: localization::fullscreen[localization_language_index_];
// save stream window last size
SDL_GetWindowSize(stream_window_, &stream_window_width_last_,
&stream_window_height_last_);
if (fullscreen_button_pressed_) {
SDL_SetWindowFullscreen(stream_window_, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetWindowFullscreen(stream_window_, true);
} else {
SDL_SetWindowFullscreen(stream_window_, SDL_FALSE);
SDL_SetWindowFullscreen(stream_window_, false);
}
reset_control_bar_pos_ = true;
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))) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
CleanupPeer(props);
}
ImGui::SameLine();
if (!is_control_bar_in_left_) {
draw_list->AddLine(
ImVec2(ImGui::GetCursorScreenPos().x - 3.0f,
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 + control_window_height_),
ImGui::GetCursorScreenPos().y - 7.0f +
props->control_window_height_),
IM_COL32(178, 178, 178, 255), 1.0f);
}
}
ImGui::SetCursorPosX(
is_control_bar_in_left_ ? (control_window_width_ * 2 - 20.0f) : 5.0f);
ImGui::SetCursorPosX(props->is_control_bar_in_left_
? (props->control_window_width_ * 2 - 20.0f)
: 5.0f);
std::string control_bar =
control_bar_expand_
? (is_control_bar_in_left_ ? ICON_FA_ANGLE_LEFT : ICON_FA_ANGLE_RIGHT)
: (is_control_bar_in_left_ ? ICON_FA_ANGLE_RIGHT
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))) {
control_bar_expand_ = !control_bar_expand_;
control_bar_button_pressed_time_ = ImGui::GetTime();
control_window_width_is_changing_ = true;
props->control_bar_expand_ = !props->control_bar_expand_;
props->control_bar_button_pressed_time_ = ImGui::GetTime();
props->control_window_width_is_changing_ = true;
if (!control_bar_expand_) {
control_window_height_ = 40;
net_traffic_stats_button_pressed_ = false;
if (!props->control_bar_expand_) {
props->control_window_height_ = 40;
props->net_traffic_stats_button_pressed_ = false;
}
}
if (net_traffic_stats_button_pressed_ && control_bar_expand_) {
NetTrafficStats();
if (props->net_traffic_stats_button_pressed_ && props->control_bar_expand_) {
NetTrafficStats(props);
}
ImGui::PopStyleVar();
@@ -209,13 +251,15 @@ int Render::ControlBar() {
return 0;
}
int Render::NetTrafficStats() {
ImGui::SetCursorPos(ImVec2(
is_control_bar_in_left_ ? (control_window_width_ + 5.0f) : 5.0f, 40.0f));
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(control_window_max_width_ - 10.0f,
control_window_max_height_ - 40.0f))) {
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);
@@ -231,48 +275,49 @@ int Render::NetTrafficStats() {
ImGui::Text("%s",
localization::loss_rate[localization_language_index_].c_str());
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::video[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.video_inbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.video_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.video_outbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.video_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(net_traffic_stats_.video_inbound_stats.loss_rate);
LossRateDisplay(props->net_traffic_stats_.video_inbound_stats.loss_rate);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::audio[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.audio_inbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.audio_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.audio_outbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.audio_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(net_traffic_stats_.audio_inbound_stats.loss_rate);
LossRateDisplay(props->net_traffic_stats_.audio_inbound_stats.loss_rate);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s", localization::data[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.data_inbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.data_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.data_outbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.data_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(net_traffic_stats_.data_inbound_stats.loss_rate);
LossRateDisplay(props->net_traffic_stats_.data_inbound_stats.loss_rate);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s",
localization::total[localization_language_index_].c_str());
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.total_inbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.total_inbound_stats.bitrate);
ImGui::TableNextColumn();
BitrateDisplay((int)net_traffic_stats_.total_outbound_stats.bitrate);
BitrateDisplay((int)props->net_traffic_stats_.total_outbound_stats.bitrate);
ImGui::TableNextColumn();
LossRateDisplay(net_traffic_stats_.total_inbound_stats.loss_rate);
LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate);
ImGui::TableNextColumn();
ImGui::Text("FPS");
ImGui::TableNextColumn();
ImGui::Text("%d", props->fps_);
ImGui::EndTable();
}

View File

@@ -1,33 +1,41 @@
#include "rd_log.h"
#include "render.h"
int Render::ControlWindow() {
double time_duration = ImGui::GetTime() - control_bar_button_pressed_time_;
if (control_window_width_is_changing_) {
if (control_bar_expand_) {
control_window_width_ =
(float)(control_window_min_width_ +
(control_window_max_width_ - control_window_min_width_) * 4 *
time_duration);
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 {
control_window_width_ =
(float)(control_window_max_width_ -
(control_window_max_width_ - control_window_min_width_) * 4 *
time_duration);
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() - net_traffic_stats_button_pressed_time_;
if (control_window_height_is_changing_) {
if (control_bar_expand_ && net_traffic_stats_button_pressed_) {
control_window_height_ =
(float)(control_window_min_height_ +
(control_window_max_height_ - control_window_min_height_) *
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 (control_bar_expand_ && !net_traffic_stats_button_pressed_) {
control_window_height_ =
(float)(control_window_max_height_ -
(control_window_max_height_ - control_window_min_height_) *
} 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);
}
}
@@ -35,191 +43,176 @@ int Render::ControlWindow() {
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);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::SetNextWindowSize(
ImVec2(control_window_width_, control_window_height_), ImGuiCond_Always);
if (0 == control_winodw_pos_.x && 0 == control_winodw_pos_.y) {
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_ + 1), ImGuiCond_Once);
}
if (reset_control_bar_pos_) {
float new_control_window_pos_x, new_control_window_pos_y, new_cursor_pos_x,
new_cursor_pos_y;
// set control window pos
new_control_window_pos_x = control_winodw_pos_.x;
if (control_winodw_pos_.y < stream_render_rect_last_.y) {
new_control_window_pos_y =
stream_render_rect_.y -
(stream_render_rect_last_.y - control_winodw_pos_.y);
if (fullscreen_button_pressed_ && new_control_window_pos_y < 0) {
new_control_window_pos_y = 0;
} else if (!fullscreen_button_pressed_ &&
new_control_window_pos_y < (title_bar_height_ + 1)) {
new_control_window_pos_y = title_bar_height_ + 1;
}
} else if (control_winodw_pos_.y + control_window_height_ >
stream_render_rect_last_.y + stream_render_rect_last_.h) {
new_control_window_pos_y =
stream_render_rect_.y + stream_render_rect_.h +
(control_winodw_pos_.y - stream_render_rect_last_.y -
stream_render_rect_last_.h);
if (new_control_window_pos_y >
stream_window_height_ - control_window_height_) {
new_control_window_pos_y =
stream_window_height_ - control_window_height_;
}
} else if (control_winodw_pos_.y + control_window_height_ ==
stream_render_rect_last_.y + stream_render_rect_last_.h) {
new_control_window_pos_y = stream_render_rect_.y + stream_render_rect_.h -
control_window_height_;
} else {
new_control_window_pos_y =
(control_winodw_pos_.y - stream_render_rect_last_.y) /
(float)(stream_render_rect_last_.h) * stream_render_rect_.h +
stream_render_rect_.y;
}
ImGui::SetNextWindowPos(
ImVec2(new_control_window_pos_x, new_control_window_pos_y),
ImVec2(props->control_window_width_, props->control_window_height_),
ImGuiCond_Always);
if (0 != mouse_diff_control_bar_pos_x_ &&
0 != mouse_diff_control_bar_pos_y_) {
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 =
new_control_window_pos_x + mouse_diff_control_bar_pos_x_;
new_cursor_pos_y =
new_control_window_pos_y + mouse_diff_control_bar_pos_y_;
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);
}
reset_control_bar_pos_ = false;
} else if (!reset_control_bar_pos_ &&
ImGui::IsMouseReleased(ImGuiPopupFlags_MouseButtonLeft) ||
control_window_width_is_changing_) {
if (control_winodw_pos_.x <= stream_window_width_ / 2) {
float pos_x = 0;
float pos_y =
(control_winodw_pos_.y >=
(fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1)) &&
control_winodw_pos_.y <=
stream_window_height_ - control_window_height_)
? control_winodw_pos_.y
: (control_winodw_pos_.y < (fullscreen_button_pressed_
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_ - control_window_height_));
: (stream_window_height_ - props->control_window_height_));
if (control_bar_expand_) {
if (control_window_width_ >= control_window_max_width_) {
control_window_width_ = control_window_max_width_;
control_window_width_is_changing_ = false;
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 {
control_window_width_is_changing_ = true;
props->control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - props->control_window_width_;
}
} else {
if (control_window_width_ <= control_window_min_width_) {
control_window_width_ = control_window_min_width_;
control_window_width_is_changing_ = false;
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 {
control_window_width_is_changing_ = true;
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);
is_control_bar_in_left_ = true;
} else if (control_winodw_pos_.x > stream_window_width_ / 2) {
float pos_x = 0;
float pos_y =
(control_winodw_pos_.y >=
(fullscreen_button_pressed_ ? 0 : (title_bar_height_ + 1)) &&
control_winodw_pos_.y <=
stream_window_height_ - control_window_height_)
? control_winodw_pos_.y
: (control_winodw_pos_.y < (fullscreen_button_pressed_
? 0
: (title_bar_height_ + 1))
? (fullscreen_button_pressed_ ? 0
: (title_bar_height_ + 1))
: (stream_window_height_ - control_window_height_));
if (control_bar_expand_) {
if (control_window_width_ >= control_window_max_width_) {
control_window_width_ = control_window_max_width_;
control_window_width_is_changing_ = false;
pos_x = stream_window_width_ - control_window_max_width_;
} else {
control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - control_window_width_;
}
} else {
if (control_window_width_ <= control_window_min_width_) {
control_window_width_ = control_window_min_width_;
control_window_width_is_changing_ = false;
pos_x = stream_window_width_ - control_window_min_width_;
} else {
control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - control_window_width_;
}
}
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
is_control_bar_in_left_ = false;
}
}
if (control_bar_expand_ && control_window_height_is_changing_) {
if (net_traffic_stats_button_pressed_) {
if (control_window_height_ >= control_window_max_height_) {
control_window_height_ = control_window_max_height_;
control_window_height_is_changing_ = false;
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 {
control_window_height_is_changing_ = true;
props->control_window_height_is_changing_ = true;
}
} else {
if (control_window_height_ <= control_window_min_height_) {
control_window_height_ = control_window_min_height_;
control_window_height_is_changing_ = false;
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 {
control_window_height_is_changing_ = true;
props->control_window_height_is_changing_ = true;
}
}
}
ImGui::Begin("ControlWindow", nullptr,
std::string control_window_title = props->remote_id_ + "ControlWindow";
ImGui::Begin(control_window_title.c_str(), nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar);
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking);
ImGui::PopStyleVar();
control_winodw_pos_ = ImGui::GetWindowPos();
SDL_GetMouseState(&mouse_pos_x_, &mouse_pos_y_);
mouse_diff_control_bar_pos_x_ = mouse_pos_x_ - control_winodw_pos_.x;
mouse_diff_control_bar_pos_y_ = mouse_pos_y_ - control_winodw_pos_.y;
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(is_control_bar_in_left_
? control_winodw_pos_.x - control_window_width_
: control_winodw_pos_.x,
control_winodw_pos_.y),
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);
ImGui::BeginChild("ControlBar",
ImVec2(control_window_width_ * 2, control_window_height_),
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();
control_bar_hovered_ = ImGui::IsWindowHovered();
ControlBar(props);
props->control_bar_hovered_ = ImGui::IsWindowHovered();
ImGui::EndChild();
ImGui::End();

View File

@@ -18,20 +18,24 @@
#define IPUT_WINDOW_WIDTH 160
#define INPUT_WINDOW_PADDING_CN 66
#define INPUT_WINDOW_PADDING_EN 96
#define SETTINGS_WINDOW_WIDTH_CN 181
#define SETTINGS_WINDOW_WIDTH_EN 228
#define SETTINGS_WINDOW_HEIGHT_CN 220
#define SETTINGS_WINDOW_HEIGHT_EN 220
#define SETTINGS_WINDOW_WIDTH_CN 182
#define SETTINGS_WINDOW_WIDTH_EN 248
#define SETTINGS_WINDOW_HEIGHT_CN 275
#define SETTINGS_WINDOW_HEIGHT_EN 275
#define LANGUAGE_SELECT_WINDOW_PADDING_CN 100
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 147
#define LANGUAGE_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_CN 100
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 147
#define VIDEO_QUALITY_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_CN 100
#define VIDEO_FRAME_RATE_SELECT_WINDOW_PADDING_EN 167
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_CN 100
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 147
#define VIDEO_ENCODE_FORMAT_SELECT_WINDOW_PADDING_EN 167
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_CN 151
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 198
#define ENABLE_HARDWARE_VIDEO_CODEC_CHECKBOX_PADDING_EN 218
#define ENABLE_TURN_CHECKBOX_PADDING_CN 151
#define ENABLE_TURN_CHECKBOX_PADDING_EN 198
#define ENABLE_TURN_CHECKBOX_PADDING_EN 218
#define ENABLE_SRTP_CHECKBOX_PADDING_CN 151
#define ENABLE_SRTP_CHECKBOX_PADDING_EN 218
#define SETTINGS_SELECT_WINDOW_WIDTH 73
#define SETTINGS_OK_BUTTON_PADDING_CN 55
#define SETTINGS_OK_BUTTON_PADDING_EN 78

View File

@@ -74,7 +74,7 @@ int Render::LocalWindow() {
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();
@@ -84,7 +84,7 @@ int Render::LocalWindow() {
double time_duration = ImGui::GetTime() - copy_start_time_;
if (local_id_copied_ && time_duration < 1.0f) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
notification_window_width_) /
@@ -135,28 +135,6 @@ int Render::LocalWindow() {
ImGui::SetNextItemWidth(IPUT_WINDOW_WIDTH);
ImGui::Spacing();
if (!password_inited_) {
char a[] = {
"123456789QWERTYUPASDFGHJKLZXCVBNMqwertyupasdfghijkzxcvbnm"};
std::mt19937 generator((unsigned int)std::chrono::system_clock::now()
.time_since_epoch()
.count());
std::uniform_int_distribution<int> distribution(0,
(int)(strlen(a) - 1));
random_password_.clear();
for (int i = 0; i < 6; i++) {
random_password_ += a[distribution(generator)];
}
password_inited_ = true;
if (0 != strcmp(random_password_.c_str(), password_saved_)) {
memcpy(password_saved_, random_password_.c_str(),
sizeof(password_saved_));
LOG_INFO("Generate new password and save into cache file");
SaveSettingsIntoCacheFile();
}
}
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
ImGui::InputTextWithHint(
"##server_pwd",
@@ -181,7 +159,7 @@ int Render::LocalWindow() {
}
if (!show_password_) {
ImDrawList *draw_list = ImGui::GetWindowDrawList();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddLine(ImVec2(l_x + 3.0f, l_y + 12.5f),
ImVec2(l_x + 20.3f, l_y + 26.5f),
IM_COL32(239, 240, 242, 255), 2.0f);
@@ -192,21 +170,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;
}
@@ -214,7 +177,7 @@ int Render::LocalWindow() {
ImGui::PopStyleColor(3);
if (show_reset_password_window_) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
@@ -281,12 +244,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");
memcpy(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;
}
}

View File

@@ -28,7 +28,22 @@ int Render::MainWindow() {
RecentConnectionsWindow();
StatusBar();
ConnectionStatusWindow();
if (show_connection_status_window_) {
for (auto it = client_properties_.begin();
it != client_properties_.end();) {
auto& props = it->second;
if (focused_remote_id_ == props->remote_id_) {
if (ConnectionStatusWindow(props)) {
it = client_properties_.erase(it);
} else {
++it;
}
} else {
++it;
}
}
}
return 0;
}

View File

@@ -56,17 +56,15 @@ int Render::ShowRecentConnections() {
ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
size_t recent_connections_count = recent_connection_textures_.size();
size_t recent_connections_count = recent_connections_.size();
int count = 0;
float button_width = 22;
float button_height = 22;
for (auto it = recent_connection_textures_.begin();
it != recent_connection_textures_.end(); ++it) {
sub_containers_pos[it->first] = ImGui::GetCursorPos();
for (auto& it : recent_connections_) {
sub_containers_pos[it.first] = ImGui::GetCursorPos();
std::string recent_connection_sub_window_name =
"RecentConnectionsSubContainer" + it->first;
"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),
@@ -76,24 +74,42 @@ int Render::ShowRecentConnections() {
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoScrollbar);
std::string connection_info = it->first;
std::string remote_id;
std::string password;
std::string host_name;
std::string connection_info = it.first;
// remote id length is 9
// password length is 6
// connection_info -> remote_id + 'Y' + password + host_name
// connection_info -> remote_id + 'Y' + host_name + '@' + password
// -> remote_id + 'N' + host_name
if ('Y' == connection_info[9] && connection_info.size() >= 16) {
remote_id = connection_info.substr(0, 9);
password = connection_info.substr(10, 6);
host_name = connection_info.substr(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) {
remote_id = connection_info.substr(0, 9);
host_name = connection_info.substr(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 {
host_name = "unknown";
it.second.remote_host_name = "unknown";
}
ImVec2 image_screen_pos = ImVec2(ImGui::GetCursorScreenPos().x + 5.0f,
@@ -101,7 +117,7 @@ int Render::ShowRecentConnections() {
ImVec2 image_pos =
ImVec2(ImGui::GetCursorPosX() + 5.0f, ImGui::GetCursorPosY() + 5.0f);
ImGui::SetCursorPos(image_pos);
ImGui::Image((ImTextureID)(intptr_t)it->second,
ImGui::Image((ImTextureID)(intptr_t)it.second.texture,
ImVec2((float)recent_connection_image_width_,
(float)recent_connection_image_height_));
@@ -113,7 +129,7 @@ int Render::ShowRecentConnections() {
ImVec2 dummy_button_pos =
ImVec2(image_pos.x, image_pos.y + recent_connection_image_height_);
std::string dummy_button_name = "##DummyButton" + remote_id;
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(),
@@ -123,14 +139,14 @@ int Render::ShowRecentConnections() {
ImGui::SetCursorPos(
ImVec2(dummy_button_pos.x + 2.0f, dummy_button_pos.y + 1.0f));
ImGui::SetWindowFontScale(0.65f);
ImGui::Text("%s", remote_id.c_str());
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", host_name.c_str());
ImGui::Text("%s", it.second.remote_host_name.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
@@ -155,10 +171,11 @@ int Render::ShowRecentConnections() {
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_) {
if (!thumbnail_->DeleteThumbnail(it->first)) {
if (delete_connection_ && delete_connection_name_ == it.first) {
if (!thumbnail_->DeleteThumbnail(it.first)) {
reload_recent_connections_ = true;
delete_connection_ = false;
}
@@ -173,15 +190,11 @@ int Render::ShowRecentConnections() {
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;
connect + "##ConnectionTo" + it.first;
if (ImGui::Button(connect_to_this_connection_button_name.c_str(),
ImVec2(button_width, button_height))) {
remote_id_ = remote_id;
if (!password.empty() && password.size() == 6) {
remember_password_ = true;
}
memcpy(remote_password_, password.c_str(), 6);
ConnectTo();
ConnectTo(it.second.remote_id, it.second.password.c_str(),
it.second.remember_password);
}
}
ImGui::SetWindowFontScale(1.0f);
@@ -196,7 +209,7 @@ int Render::ShowRecentConnections() {
ImVec2 line_end = ImVec2(
image_screen_pos.x + recent_connection_image_width_ + 20.0f,
image_screen_pos.y + recent_connection_image_height_ + button_height);
ImGui::GetForegroundDrawList()->AddLine(line_start, line_end,
ImGui::GetWindowDrawList()->AddLine(line_start, line_end,
IM_COL32(0, 0, 0, 122), 1.0f);
}

View File

@@ -68,18 +68,45 @@ int Render::RemoteWindow() {
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;
remote_id_ = remote_id_display_;
remote_id_.erase(remove_if(remote_id_.begin(), remote_id_.end(),
static_cast<int (*)(int)>(&isspace)),
remote_id_.end());
ConnectTo();
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 (rejoin_) {
ConnectTo();
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();
@@ -102,32 +129,51 @@ static int InputTextCallback(ImGuiInputTextCallbackData *data) {
return 0;
}
int Render::ConnectTo() {
connection_status_ = ConnectionStatus::Connecting;
int ret = -1;
if (signal_connected_) {
if (!connection_established_) {
if (0 == strcmp(remote_id_.c_str(), client_id_) && !peer_reserved_) {
peer_reserved_ = CreatePeer(&params_);
if (peer_reserved_) {
LOG_INFO("Create peer[reserved] instance successful");
std::string client_id = "C-";
client_id += client_id_;
Init(peer_reserved_, client_id.c_str());
LOG_INFO("Peer[reserved] init finish");
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[reserved] instance failed");
}
LOG_INFO("Create peer [{}] instance failed", props->local_id_);
}
ret = JoinConnection(peer_reserved_ ? peer_reserved_ : peer_,
remote_id_.c_str(), remote_password_);
if (0 == ret) {
is_client_mode_ = true;
rejoin_ = false;
} else {
rejoin_ = true;
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,25 +7,115 @@
#ifndef _MAIN_WINDOW_H_
#define _MAIN_WINDOW_H_
#include <SDL.h>
#include <SDL3/SDL.h>
#include <atomic>
#include <chrono>
#include <fstream>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include "../../thirdparty/projectx/src/interface/x.h"
#include "IconsFontAwesome6.h"
#include "config_center.h"
#include "device_controller_factory.h"
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_sdlrenderer2.h"
#include "imgui_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"
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();
@@ -33,6 +123,26 @@ class 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);
@@ -42,17 +152,17 @@ class Render {
int RemoteWindow();
int RecentConnectionsWindow();
int SettingWindow();
int ControlWindow();
int ControlBar();
int ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props);
int ControlBar(std::shared_ptr<SubStreamWindowProperties>& props);
int AboutWindow();
int StatusBar();
int ConnectionStatusWindow();
int LoadRecentConnections();
bool ConnectionStatusWindow(
std::shared_ptr<SubStreamWindowProperties>& props);
int ShowRecentConnections();
private:
int CreateRtcConnection();
int ConnectTo();
int ConnectTo(const std::string& remote_id, const char* password,
bool remember_password);
int CreateMainWindow();
int DestroyMainWindow();
int CreateStreamWindow();
@@ -65,48 +175,57 @@ class Render {
int DrawMainWindow();
int DrawStreamWindow();
int ConfirmDeleteConnection();
int NetTrafficStats();
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 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 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 OnReceiveDataBufferCb(const char* data, size_t size,
const char* user_id, size_t user_id_size,
void* user_data);
static void OnSignalStatusCb(SignalStatus status, void *user_data);
static void 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 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,
static void NetStatusReport(const char* client_id, size_t client_id_size,
TraversalMode mode,
const XNetTrafficStats *net_traffic_stats,
void *user_data);
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 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 ProcessMouseKeyEvent(SDL_Event &event);
int ProcessMouseEvent(SDL_Event &event);
int SendKeyCommand(int key_code, bool is_down);
int ProcessMouseEvent(const SDL_Event& event);
int SendKeyEvent(int key_code, bool is_down);
int ProcessKeyEvent(int key_code, bool is_down);
static void SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len);
static void SdlCaptureAudioOut(void *userdata, Uint8 *stream, int len);
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();
@@ -125,75 +244,79 @@ class Render {
int AudioDeviceDestroy();
private:
typedef struct {
char client_id[10];
char password[7];
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];
} CDCache;
};
private:
CDCache cd_cache_;
std::mutex cd_cache_mutex_;
ConfigCenter config_center_;
ConfigCenter::LANGUAGE localization_language_ =
ConfigCenter::LANGUAGE::CHINESE;
std::unique_ptr<PathManager> path_manager_;
std::string cert_path_;
std::string exec_log_path_;
std::string dll_log_path_;
std::string cache_path_;
int localization_language_index_ = -1;
int localization_language_index_last_ = -1;
bool modules_inited_ = false;
private:
std::string window_title = "Remote Desk Client";
std::string mac_addr_str_ = "";
std::string connect_button_label_ = "Connect";
std::string fullscreen_button_label_ = "Fullscreen";
std::string 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 settings_button_label_ = "Setting";
char input_password_tmp_[7] = "";
char input_password_[7] = "";
std::string random_password_ = "";
char remote_password_[7] = "";
char new_password_[7] = "";
char remote_id_display_[12] = "";
std::string remote_id_ = "";
char client_password_[20] = "";
private:
/* ------ all windows property start ------ */
float title_bar_width_ = 640;
float title_bar_height_ = 30;
int screen_width_ = 1280;
int screen_height_ = 720;
/* ------ 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
// 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;
int stream_window_width_default_ = 1280;
int stream_window_height_default_ = 720;
float stream_window_width_ = 1280;
float stream_window_height_ = 720;
int stream_window_width_last_ = 1280;
int stream_window_height_last_ = 720;
float stream_window_width_before_maximized_ = 1280;
float stream_window_height_before_maximized_ = 720;
float control_window_min_width_ = 20;
float control_window_max_width_ = 200;
float control_window_min_height_ = 40;
float control_window_max_height_ = 150;
float control_window_width_ = 200;
float control_window_height_ = 40;
float local_window_width_ = 320;
float local_window_height_ = 235;
float remote_window_width_ = 320;
@@ -212,176 +335,116 @@ class Render {
float notification_window_height_ = 80;
float about_window_width_ = 200;
float about_window_height_ = 150;
int screen_width_ = 1280;
int screen_height_ = 720;
int selected_display_ = 0;
std::string connect_button_label_ = "Connect";
char input_password_tmp_[7] = "";
char input_password_[7] = "";
std::string random_password_ = "";
char new_password_[7] = "";
char remote_id_display_[12] = "";
unsigned char audio_buffer_[720];
int audio_len_ = 0;
bool audio_buffer_fresh_ = false;
bool need_to_rejoin_ = false;
bool just_created_ = false;
std::string controlled_remote_id_ = "";
std::string focused_remote_id_ = "";
bool need_to_send_host_info_ = false;
SDL_Event last_mouse_event;
SDL_AudioStream* output_stream_;
uint32_t STREAM_REFRESH_EVENT = 0;
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;
int mouse_pos_x_ = 0;
int mouse_pos_y_ = 0;
int mouse_pos_x_last_ = 0;
int mouse_pos_y_last_ = 0;
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;
// 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;
int texture_width_ = 1280;
int texture_height_ = 720;
int video_width_ = 1280;
int video_height_ = 720;
size_t video_size_ = 1280 * 720 * 3;
SDL_Window *main_window_ = nullptr;
SDL_Renderer *main_renderer_ = nullptr;
ImGuiContext *main_ctx_ = nullptr;
SDL_Window *stream_window_ = nullptr;
SDL_Renderer *stream_renderer_ = nullptr;
ImGuiContext *stream_ctx_ = nullptr;
bool stream_window_created_ = false;
bool stream_window_inited_ = false;
// recent connections
std::map<std::string, SDL_Texture *> recent_connection_textures_;
int recent_connection_image_width_ = 160;
int recent_connection_image_height_ = 90;
uint32_t recent_connection_image_save_time_ = 0;
// video window
SDL_Texture *stream_texture_ = nullptr;
SDL_Rect stream_render_rect_;
SDL_Rect stream_render_rect_last_;
uint32_t stream_pixformat_ = 0;
std::string host_name_ = "";
unsigned char aes128_key_[16];
unsigned char aes128_iv_[16];
std::unique_ptr<Thumbnail> thumbnail_;
bool resizable_ = false;
bool label_inited_ = false;
bool exit_ = false;
bool exit_video_window_ = false;
bool connection_established_ = false;
bool connect_button_pressed_ = false;
bool password_validating_ = false;
uint32_t password_validating_time_ = 0;
bool fullscreen_button_pressed_ = false;
bool net_traffic_stats_button_pressed_ = false;
bool mouse_control_button_pressed_ = false;
bool audio_capture_button_pressed_ = false;
bool show_settings_window_ = false;
bool is_create_connection_ = false;
bool audio_buffer_fresh_ = false;
bool rejoin_ = false;
bool control_mouse_ = false;
bool stream_window_grabbed_ = false;
bool audio_capture_ = true;
bool local_id_copied_ = false;
bool show_password_ = true;
bool password_inited_ = false;
bool regenerate_password_ = false;
bool show_about_window_ = false;
bool show_connection_status_window_ = false;
bool show_reset_password_window_ = false;
bool fullscreen_button_pressed_ = false;
bool focus_on_input_widget_ = true;
bool window_maximized_ = false;
bool streaming_ = false;
bool is_client_mode_ = false;
bool is_control_bar_in_left_ = true;
bool is_control_bar_in_top_ = true;
bool control_bar_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 reload_recent_connections_ = true;
bool hostname_sent_ = false;
bool show_confirm_delete_connection_ = false;
bool delete_connection_ = false;
bool remember_password_ = false;
bool is_tab_bar_hovered_ = false;
std::string delete_connection_name_ = "";
bool re_enter_remote_id_ = false;
double copy_start_time_ = 0;
double regenerate_password_start_time_ = 0;
double control_bar_button_pressed_time_ = 0;
double net_traffic_stats_button_pressed_time_ = 0;
ImVec2 control_winodw_pos_;
int fps_ = 0;
uint32_t start_time_;
uint32_t end_time_;
uint32_t elapsed_time_;
uint32_t frame_count_ = 0;
private:
ConnectionStatus connection_status_ = ConnectionStatus::Closed;
SignalStatus signal_status_ = SignalStatus::SignalClosed;
std::string signal_status_str_ = "";
std::string connection_status_str_ = "";
bool signal_connected_ = false;
bool p2p_mode_ = true;
private:
PeerPtr *peer_ = nullptr;
PeerPtr *peer_reserved_ = nullptr;
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_;
TraversalMode traversal_mode_ = TraversalMode::UnknownMode;
XNetTrafficStats net_traffic_stats_;
private:
SDL_AudioDeviceID input_dev_;
SDL_AudioDeviceID output_dev_;
unsigned char audio_buffer_[720];
int audio_len_ = 0;
unsigned char *dst_buffer_ = nullptr;
size_t dst_buffer_capacity_ = 0;
private:
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;
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_;
private:
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;
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;
private:
std::atomic<bool> start_screen_capturer_{false};
std::atomic<bool> start_mouse_controller_{false};
std::atomic<bool> start_keyboard_capturer_{false};
std::atomic<bool> screen_capturer_is_started_{false};
std::atomic<bool> mouse_controller_is_started_{false};
std::atomic<bool> keyboard_capturer_is_started_{false};
private:
bool enable_srtp_last_ = false;
bool settings_window_pos_reset_ = true;
/* ------ main window property end ------ */
/* ------ sub stream window property start ------ */
std::unordered_map<std::string, std::shared_ptr<SubStreamWindowProperties>>
client_properties_;
void CloseTab(decltype(client_properties_)::iterator& it);
/* ------ stream window property end ------ */
};
#endif

View File

@@ -4,86 +4,14 @@
#include "rd_log.h"
#include "render.h"
// Refresh Event
#define REFRESH_EVENT (SDL_USEREVENT + 1)
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#ifdef REMOTE_DESK_DEBUG
#ifdef DESK_PORT_DEBUG
#else
#define MOUSE_CONTROL 1
#endif
int Render::ProcessMouseKeyEvent(SDL_Event &event) {
if (!control_mouse_ || !connection_established_) {
return 0;
}
if (SDL_KEYDOWN == event.type || SDL_KEYUP == event.type) {
} else {
ProcessMouseEvent(event);
}
return 0;
}
int Render::ProcessMouseEvent(SDL_Event &event) {
float ratio_x = (float)video_width_ / (float)stream_render_rect_.w;
float ratio_y = (float)video_height_ / (float)stream_render_rect_.h;
if (event.button.x <= stream_render_rect_.x) {
event.button.x = 0;
} else if (event.button.x > stream_render_rect_.x &&
event.button.x < stream_render_rect_.x + stream_render_rect_.w) {
event.button.x -= stream_render_rect_.x;
} else if (event.button.x >= stream_render_rect_.x + stream_render_rect_.w) {
event.button.x = stream_render_rect_.w;
}
if (event.button.y <= stream_render_rect_.y) {
event.button.y = 0;
} else if (event.button.y > stream_render_rect_.y &&
event.button.y < stream_render_rect_.y + stream_render_rect_.h) {
event.button.y -= stream_render_rect_.y;
} else if (event.button.y >= stream_render_rect_.y + stream_render_rect_.h) {
event.button.y = stream_render_rect_.h;
}
RemoteAction remote_action;
remote_action.m.x = (size_t)(event.button.x * ratio_x);
remote_action.m.y = (size_t)(event.button.y * ratio_y);
if (SDL_MOUSEBUTTONDOWN == event.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == event.button.button) {
remote_action.m.flag = MouseFlag::left_down;
} else if (SDL_BUTTON_RIGHT == event.button.button) {
remote_action.m.flag = MouseFlag::right_down;
}
if (control_bar_hovered_) {
remote_action.m.flag = MouseFlag::move;
}
SendDataFrame(peer_, (const char *)&remote_action, sizeof(remote_action));
} else if (SDL_MOUSEBUTTONUP == event.type) {
remote_action.type = ControlType::mouse;
if (SDL_BUTTON_LEFT == event.button.button) {
remote_action.m.flag = MouseFlag::left_up;
} else if (SDL_BUTTON_RIGHT == event.button.button) {
remote_action.m.flag = MouseFlag::right_up;
}
if (control_bar_hovered_) {
remote_action.m.flag = MouseFlag::move;
}
SendDataFrame(peer_, (const char *)&remote_action, sizeof(remote_action));
} else if (SDL_MOUSEMOTION == event.type) {
remote_action.type = ControlType::mouse;
remote_action.m.flag = MouseFlag::move;
SendDataFrame(peer_, (const char *)&remote_action, sizeof(remote_action));
}
return 0;
}
int Render::SendKeyEvent(int key_code, bool is_down) {
int Render::SendKeyCommand(int key_code, bool is_down) {
RemoteAction remote_action;
remote_action.type = ControlType::keyboard;
if (is_down) {
@@ -93,29 +21,133 @@ int Render::SendKeyEvent(int key_code, bool is_down) {
}
remote_action.k.key_value = key_code;
SendDataFrame(peer_, (const char *)&remote_action, sizeof(remote_action));
return 0;
}
int Render::ProcessKeyEvent(int key_code, bool is_down) {
if (keyboard_capturer_) {
keyboard_capturer_->SendKeyboardCommand(key_code, is_down);
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;
}
void Render::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
Render *render = (Render *)userdata;
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) {
if ("Connected" == render->connection_status_str_) {
SendAudioFrame(render->peer_, (const char *)stream, len);
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;
@@ -124,12 +156,15 @@ void Render::SdlCaptureAudioIn(void *userdata, Uint8 *stream, int len) {
}
}
void Render::SdlCaptureAudioOut([[maybe_unused]] void *userdata,
[[maybe_unused]] Uint8 *stream,
void Render::SdlCaptureAudioOut([[maybe_unused]] void* userdata,
[[maybe_unused]] Uint8* stream,
[[maybe_unused]] int len) {
// Render *render = (Render *)userdata;
// if ("Connected" == render->connection_status_str_) {
// SendAudioFrame(render->peer_, (const char *)stream, len);
// 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_) {
@@ -149,217 +184,354 @@ void Render::SdlCaptureAudioOut([[maybe_unused]] void *userdata,
// render->audio_buffer_fresh_ = false;
}
void Render::OnReceiveVideoBufferCb(const XVideoFrame *video_frame,
[[maybe_unused]] const char *user_id,
[[maybe_unused]] size_t user_id_size,
void *user_data) {
Render *render = (Render *)user_data;
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;
}
if (render->connection_established_) {
if (!render->dst_buffer_) {
render->dst_buffer_capacity_ = video_frame->size;
render->dst_buffer_ = new unsigned char[video_frame->size];
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 (render->dst_buffer_capacity_ < video_frame->size) {
delete render->dst_buffer_;
render->dst_buffer_capacity_ = video_frame->size;
render->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(render->dst_buffer_, video_frame->data, video_frame->size);
render->video_width_ = video_frame->width;
render->video_height_ = video_frame->height;
render->video_size_ = 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 = REFRESH_EVENT;
event.type = render->STREAM_REFRESH_EVENT;
event.user.data1 = props;
SDL_PushEvent(&event);
render->streaming_ = true;
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,
[[maybe_unused]] const char *user_id,
[[maybe_unused]] size_t user_id_size,
void *user_data) {
Render *render = (Render *)user_data;
void Render::OnReceiveAudioBufferCb(const char* data, size_t size,
const char* user_id, size_t user_id_size,
void* user_data) {
Render* render = (Render*)user_data;
if (!render) {
return;
}
render->audio_buffer_fresh_ = true;
SDL_QueueAudio(render->output_dev_, data, (uint32_t)size);
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;
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;
}
std::string user(user_id, user_id_size);
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_->SendCommand(remote_action);
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->ProcessKeyEvent((int)remote_action.k.key_value,
} 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::host_infomation == remote_action.type) {
render->host_name_ =
std::string(remote_action.i.host_name, remote_action.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", render->host_name_);
} 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, void *user_data) {
Render *render = (Render *)user_data;
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_status_str_ = "SignalConnecting";
render->signal_connected_ = false;
} else if (SignalStatus::SignalConnected == status) {
render->signal_status_str_ = "SignalConnected";
render->signal_connected_ = true;
LOG_INFO("[{}] connected to signal server", client_id);
} else if (SignalStatus::SignalFailed == status) {
render->signal_status_str_ = "SignalFailed";
render->signal_connected_ = false;
} else if (SignalStatus::SignalClosed == status) {
render->signal_status_str_ = "SignalClosed";
render->signal_connected_ = false;
} else if (SignalStatus::SignalReconnecting == status) {
render->signal_status_str_ = "SignalReconnecting";
render->signal_connected_ = false;
} else if (SignalStatus::SignalServerClosed == status) {
render->signal_status_str_ = "SignalServerClosed";
render->signal_connected_ = false;
render->is_create_connection_ = false;
}
}
void Render::OnConnectionStatusCb(ConnectionStatus status,
[[maybe_unused]] const char *user_id,
[[maybe_unused]] const size_t user_id_size,
void *user_data) {
Render *render = (Render *)user_data;
if (!render) {
} else {
if (client_id.rfind("C-", 0) != 0) {
return;
}
render->connection_status_ = status;
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;
if (ConnectionStatus::Connecting == status) {
render->connection_status_str_ = "Connecting";
} else if (ConnectionStatus::Gathering == status) {
render->connection_status_str_ = "Gathering";
} else if (ConnectionStatus::Connected == status) {
render->connection_status_str_ = "Connected";
render->connection_established_ = true;
if (render->peer_reserved_ || !render->is_client_mode_) {
render->start_screen_capturer_ = true;
render->start_mouse_controller_ = 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;
}
if (!render->hostname_sent_) {
// TODO: self and remote hostname
std::string host_name = GetHostName();
RemoteAction remote_action;
remote_action.type = ControlType::host_infomation;
memcpy(&remote_action.i.host_name, host_name.data(), host_name.size());
remote_action.i.host_name_size = host_name.size();
int ret = SendDataFrame(render->peer_, (const char *)&remote_action,
sizeof(remote_action));
if (0 == ret) {
render->hostname_sent_ = true;
}
}
} else if (ConnectionStatus::Disconnected == status) {
render->connection_status_str_ = "Disconnected";
render->password_validating_time_ = 0;
} else if (ConnectionStatus::Failed == status) {
render->connection_status_str_ = "Failed";
render->password_validating_time_ = 0;
} else if (ConnectionStatus::Closed == status) {
render->connection_status_str_ = "Closed";
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->connection_established_ = false;
render->control_mouse_ = false;
render->mouse_control_button_pressed_ = false;
render->start_keyboard_capturer_ = false;
render->hostname_sent_ = false;
if (render->audio_capture_) {
render->StopSpeakerCapturer();
render->audio_capture_ = false;
render->audio_capture_button_pressed_ = 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->exit_video_window_ = false;
if (!render->rejoin_) {
memset(render->remote_password_, 0, sizeof(render->remote_password_));
}
if (render->dst_buffer_) {
memset(render->dst_buffer_, 0, render->dst_buffer_capacity_);
SDL_UpdateTexture(render->stream_texture_, NULL, render->dst_buffer_,
render->texture_width_);
}
} else if (ConnectionStatus::IncorrectPassword == status) {
render->connection_status_str_ = "Incorrect password";
render->CleanSubStreamWindowProperties(props);
break;
case ConnectionStatus::IncorrectPassword:
render->password_validating_ = false;
render->password_validating_time_++;
if (render->connect_button_pressed_) {
render->connection_established_ = false;
render->connect_button_pressed_ = false;
props->connection_established_ = false;
render->connect_button_label_ =
render->connect_button_pressed_
? localization::disconnect[render->localization_language_index_]
: localization::connect[render->localization_language_index_];
localization::connect[render->localization_language_index_];
}
} else if (ConnectionStatus::NoSuchTransmissionId == status) {
render->connection_status_str_ = "No such transmission id";
break;
case ConnectionStatus::NoSuchTransmissionId:
if (render->connect_button_pressed_) {
render->connection_established_ = false;
props->connection_established_ = false;
render->connect_button_label_ =
render->connect_button_pressed_
? localization::disconnect[render->localization_language_index_]
: localization::connect[render->localization_language_index_];
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,
void Render::NetStatusReport(const char* client_id, size_t client_id_size,
TraversalMode mode,
const XNetTrafficStats *net_traffic_stats,
void *user_data) {
Render *render = (Render *)user_data;
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 (0 == strcmp(render->client_id_, "")) {
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_));
memcpy(render->client_id_, client_id, client_id_size);
LOG_INFO("Use client id [{}] and save id into cache file", 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();
}
if (render->traversal_mode_ != mode) {
render->traversal_mode_ = mode;
LOG_INFO("Net mode: [{}]", int(render->traversal_mode_));
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) {
@@ -368,6 +540,6 @@ void Render::NetStatusReport(const char *client_id, size_t client_id_size,
// only display client side net status if connected to itself
if (!(render->peer_reserved_ && !strstr(client_id, "C-"))) {
render->net_traffic_stats_ = *net_traffic_stats;
props->net_traffic_stats_ = *net_traffic_stats;
}
}

View File

@@ -6,7 +6,7 @@
int Render::SettingWindow() {
if (show_settings_window_) {
if (settings_window_pos_reset_) {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
const ImGuiViewport* viewport = ImGui::GetMainViewport();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetNextWindowPos(
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x -
@@ -36,6 +36,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);
@@ -49,11 +52,12 @@ int Render::SettingWindow() {
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
{
const char *language_items[] = {
const char* language_items[] = {
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 + 2);
ImGui::Text(
"%s", localization::language[localization_language_index_].c_str());
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
@@ -61,7 +65,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,
@@ -70,12 +74,12 @@ int Render::SettingWindow() {
ImGui::Separator();
if (streaming_) {
if (stream_window_inited_) {
ImGui::BeginDisabled();
}
{
const char *video_quality_items[] = {
const char* video_quality_items[] = {
localization::video_quality_high[localization_language_index_]
.c_str(),
localization::video_quality_medium[localization_language_index_]
@@ -83,7 +87,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 + 2);
ImGui::Text(
"%s",
localization::video_quality[localization_language_index_].c_str());
@@ -93,7 +98,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_,
@@ -103,11 +108,36 @@ int Render::SettingWindow() {
ImGui::Separator();
{
const char *video_encode_format_items[] = {
const char* video_frame_rate_items[] = {"30", "60"};
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
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::av1[localization_language_index_].c_str(),
localization::h264[localization_language_index_].c_str()};
ImGui::SetCursorPosY(92);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text(
"%s",
localization::video_encode_format[localization_language_index_]
@@ -118,7 +148,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(
@@ -129,7 +159,8 @@ int Render::SettingWindow() {
ImGui::Separator();
{
ImGui::SetCursorPosY(122);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text("%s", localization::enable_hardware_video_codec
[localization_language_index_]
.c_str());
@@ -139,7 +170,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_);
}
@@ -147,7 +178,8 @@ int Render::SettingWindow() {
ImGui::Separator();
{
ImGui::SetCursorPosY(152);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
ImGui::Text(
"%s",
localization::enable_turn[localization_language_index_].c_str());
@@ -157,11 +189,29 @@ 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_);
}
if (streaming_) {
ImGui::Separator();
{
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset + 2);
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_);
}
if (stream_window_inited_) {
ImGui::EndDisabled();
}
@@ -170,7 +220,9 @@ int Render::SettingWindow() {
} else {
ImGui::SetCursorPosX(SETTINGS_OK_BUTTON_PADDING_EN);
}
ImGui::SetCursorPosY(190.0f);
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::PopStyleVar();
// OK
@@ -227,6 +279,14 @@ int Render::SettingWindow() {
}
enable_turn_last_ = enable_turn_;
// SRTP
if (enable_srtp_) {
config_center_.SetSrtp(true);
} else {
config_center_.SetSrtp(false);
}
enable_srtp_last_ = enable_srtp_;
SaveSettingsIntoCacheFile();
settings_window_pos_reset_ = true;
@@ -234,10 +294,9 @@ int Render::SettingWindow() {
LoadSettingsFromCacheFile();
// Recreate peer instance
if (!streaming_) {
if (!stream_window_inited_) {
LOG_INFO("Recreate peer instance");
DestroyPeer(&peer_);
is_create_connection_ = false;
CleanupPeers();
CreateConnectionPeer();
}
}

View File

@@ -711,7 +711,7 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32) == 4 ? 1 : -1];
#ifdef STBI_HAS_LROTL
#define stbi_lrot(x, y) _lrotl(x, y)
#else
#define stbi_lrot(x, y) (((x) << (y)) | ((x) >> (-(y)&31)))
#define stbi_lrot(x, y) (((x) << (y)) | ((x) >> (-(y) & 31)))
#endif
#if defined(STBI_MALLOC) && defined(STBI_FREE) && \
@@ -1784,7 +1784,7 @@ static stbi__uint32 stbi__get32le(stbi__context *s) {
#endif
#define STBI__BYTECAST(x) \
((stbi_uc)((x)&255)) // truncate int to byte without warnings
((stbi_uc)((x) & 255)) // truncate int to byte without warnings
#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && \
defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && \
@@ -1831,7 +1831,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n,
unsigned char *src = data + j * x * img_n;
unsigned char *dest = good + j * x * req_comp;
#define STBI__COMBO(a, b) ((a)*8 + (b))
#define STBI__COMBO(a, b) ((a) * 8 + (b))
#define STBI__CASE(a, b) \
case STBI__COMBO(a, b): \
for (i = x - 1; i >= 0; --i, src += a, dest += b)
@@ -1931,7 +1931,7 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n,
stbi__uint16 *src = data + j * x * img_n;
stbi__uint16 *dest = good + j * x * req_comp;
#define STBI__COMBO(a, b) ((a)*8 + (b))
#define STBI__COMBO(a, b) ((a) * 8 + (b))
#define STBI__CASE(a, b) \
case STBI__COMBO(a, b): \
for (i = x - 1; i >= 0; --i, src += a, dest += b)
@@ -2590,8 +2590,8 @@ stbi_inline static stbi_uc stbi__clamp(int x) {
return (stbi_uc)x;
}
#define stbi__f2f(x) ((int)(((x)*4096 + 0.5)))
#define stbi__fsh(x) ((x)*4096)
#define stbi__f2f(x) ((int)(((x) * 4096 + 0.5)))
#define stbi__fsh(x) ((x) * 4096)
// derived from jidctint -- DCT_ISLOW
#define STBI__IDCT_1D(s0, s1, s2, s3, s4, s5, s6, s7) \
@@ -3694,7 +3694,7 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) {
if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
m = stbi__get_marker(j);
while (!stbi__EOI(m)) {
if (stbi__SOS(m)) {
if (stbi__SOS(m) == true) {
if (!stbi__process_scan_header(j)) return 0;
if (!stbi__parse_entropy_coded_data(j)) return 0;
if (j->marker == STBI__MARKER_none) {
@@ -3704,7 +3704,7 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) {
}
m = stbi__get_marker(j);
if (STBI__RESTART(m)) m = stbi__get_marker(j);
} else if (stbi__DNL(m)) {
} else if (stbi__DNL(m) == true) {
int Ld = stbi__get16be(j->s);
stbi__uint32 NL = stbi__get16be(j->s);
if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
@@ -3929,7 +3929,7 @@ static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near,
// this is a reduced-precision calculation of YCbCr-to-RGB introduced
// to make sure the code produces the same results in both SIMD and scalar
#define stbi__float2fixed(x) (((int)((x)*4096.0f + 0.5f)) << 8)
#define stbi__float2fixed(x) (((int)((x) * 4096.0f + 0.5f)) << 8)
static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y,
const stbi_uc *pcb, const stbi_uc *pcr,
int count, int step) {
@@ -7579,8 +7579,7 @@ static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) {
buffer[len++] = c;
if (len == STBI__HDR_BUFLEN - 1) {
// flush to end of line
while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
;
while (!stbi__at_eof(z) && stbi__get8(z) != '\n');
break;
}
c = (char)stbi__get8(z);

View File

@@ -270,7 +270,7 @@ STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
#define STBIW_ASSERT(x) assert(x)
#endif
#define STBIW_UCHAR(x) (unsigned char)((x)&0xff)
#define STBIW_UCHAR(x) (unsigned char)((x) & 0xff)
#ifdef STB_IMAGE_WRITE_STATIC
static int stbi_write_png_compression_level = 8;
@@ -816,8 +816,8 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp,
sprintf_s(buffer, sizeof(buffer),
"EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#else
len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n",
y, x);
len = snprintf(buffer, sizeof(buffer),
"EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#endif
s->func(s->context, buffer, len);
@@ -857,7 +857,7 @@ STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp,
#ifndef STBIW_ZLIB_COMPRESS
// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount()
// == vector<>::size()
#define stbiw__sbraw(a) ((int *)(void *)(a)-2)
#define stbiw__sbraw(a) ((int *)(void *)(a) - 2)
#define stbiw__sbm(a) stbiw__sbraw(a)[0]
#define stbiw__sbn(a) stbiw__sbraw(a)[1]
@@ -931,9 +931,9 @@ static unsigned int stbiw__zhash(unsigned char *data) {
#define stbiw__zlib_huffa(b, c) stbiw__zlib_add(stbiw__zlib_bitrev(b, c), c)
// default huffman tables
#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8)
#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9)
#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256, 7)
#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280, 8)
#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n) - 144, 9)
#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n) - 256, 7)
#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n) - 280, 8)
#define stbiw__zlib_huff(n) \
((n) <= 143 ? stbiw__zlib_huff1(n) \
: (n) <= 255 ? stbiw__zlib_huff2(n) \
@@ -1024,12 +1024,10 @@ STBIWDEF unsigned char *stbi_zlib_compress(unsigned char *data, int data_len,
if (bestloc) {
int d = (int)(data + i - bestloc); // distance back
STBIW_ASSERT(d <= 32767 && best <= 258);
for (j = 0; best > lengthc[j + 1] - 1; ++j)
;
for (j = 0; best > lengthc[j + 1] - 1; ++j);
stbiw__zlib_huff(j + 257);
if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]);
for (j = 0; d > distc[j + 1] - 1; ++j)
;
for (j = 0; d > distc[j + 1] - 1; ++j);
stbiw__zlib_add(stbiw__zlib_bitrev(j, 5), 5);
if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]);
i += best;

View File

@@ -2,25 +2,201 @@
#include "rd_log.h"
#include "render.h"
void Render::DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties>& props) {
std::string text;
switch (props->connection_status_) {
case ConnectionStatus::Disconnected:
text = localization::p2p_disconnected[localization_language_index_];
break;
case ConnectionStatus::Failed:
text = localization::p2p_failed[localization_language_index_];
break;
case ConnectionStatus::Closed:
text = localization::p2p_closed[localization_language_index_];
break;
default:
break;
}
if (!text.empty()) {
ImVec2 size = ImGui::GetWindowSize();
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
ImGui::SetCursorPos(
ImVec2((size.x - text_size.x) * 0.5f,
(size.y - text_size.y - title_bar_height_) * 0.5f));
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", text.c_str());
}
}
void Render::CloseTab(decltype(client_properties_)::iterator& it) {
CleanupPeer(it->second);
it = client_properties_.erase(it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
}
int Render::StreamWindow() {
ImGui::SetNextWindowPos(
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
ImGuiCond_Always);
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_, stream_window_height_ - title_bar_height_),
ImGui::SetNextWindowSize(ImVec2(stream_window_width_, stream_window_height_),
ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 0));
ImGui::PushStyleColor(ImGuiCol_Border,
ImVec4(178 / 255.0f, 178 / 255.0f, 178 / 255.0f,
fullscreen_button_pressed_ ? 0 : 1.0f));
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_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor(2);
ImGui::PopStyleVar();
ControlWindow();
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;
}

View File

@@ -23,87 +23,6 @@
static std::string test;
void ScaleYUV420pToABGR(char* dst_buffer_, int video_width_, int video_height_,
int scaled_video_width_, int scaled_video_height_,
char* rgba_buffer_) {
int src_y_size = video_width_ * video_height_;
int src_uv_size = (video_width_ + 1) / 2 * (video_height_ + 1) / 2;
int dst_y_size = scaled_video_width_ * scaled_video_height_;
int dst_uv_size =
(scaled_video_width_ + 1) / 2 * (scaled_video_height_ + 1) / 2;
uint8_t* src_y = reinterpret_cast<uint8_t*>(dst_buffer_);
uint8_t* src_u = src_y + src_y_size;
uint8_t* src_v = src_u + src_uv_size;
std::unique_ptr<uint8_t[]> dst_y(new uint8_t[dst_y_size]);
std::unique_ptr<uint8_t[]> dst_u(new uint8_t[dst_uv_size]);
std::unique_ptr<uint8_t[]> dst_v(new uint8_t[dst_uv_size]);
libyuv::I420Scale(src_y, video_width_, src_u, (video_width_ + 1) / 2, src_v,
(video_width_ + 1) / 2, video_width_, video_height_,
dst_y.get(), scaled_video_width_, dst_u.get(),
(scaled_video_width_ + 1) / 2, dst_v.get(),
(scaled_video_width_ + 1) / 2, scaled_video_width_,
scaled_video_height_, libyuv::kFilterBilinear);
libyuv::I420ToABGR(
dst_y.get(), scaled_video_width_, dst_u.get(),
(scaled_video_width_ + 1) / 2, dst_v.get(), (scaled_video_width_ + 1) / 2,
reinterpret_cast<uint8_t*>(rgba_buffer_), scaled_video_width_ * 4,
scaled_video_width_, scaled_video_height_);
}
Thumbnail::Thumbnail() {
RAND_bytes(aes128_key_, sizeof(aes128_key_));
RAND_bytes(aes128_iv_, sizeof(aes128_iv_));
std::filesystem::create_directory(image_path_);
}
Thumbnail::Thumbnail(unsigned char* aes128_key, unsigned char* aes128_iv) {
memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
std::filesystem::create_directory(image_path_);
}
Thumbnail::~Thumbnail() {
if (rgba_buffer_) {
delete[] rgba_buffer_;
rgba_buffer_ = nullptr;
}
}
int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
const std::string& remote_id,
const std::string& host_name,
const std::string& password) {
if (!rgba_buffer_) {
rgba_buffer_ = new char[thumbnail_width_ * thumbnail_height_ * 4];
}
if (yuv420p) {
ScaleYUV420pToABGR((char*)yuv420p, width, height, thumbnail_width_,
thumbnail_height_, rgba_buffer_);
std::string image_name;
if (password.empty()) {
image_name = remote_id + 'N' + host_name;
} else {
// delete the file which has no password in its name
std::string filename_without_password = remote_id + "N" + host_name;
DeleteThumbnail(filename_without_password);
image_name = remote_id + 'Y' + password + host_name;
}
std::string ciphertext = AES_encrypt(image_name, aes128_key_, aes128_iv_);
std::string file_path = image_path_ + ciphertext;
stbi_write_png(file_path.data(), thumbnail_width_, thumbnail_height_, 4,
rgba_buffer_, thumbnail_width_ * 4);
}
return 0;
}
bool LoadTextureFromMemory(const void* data, size_t data_size,
SDL_Renderer* renderer, SDL_Texture** out_texture,
int* out_width, int* out_height) {
@@ -119,9 +38,10 @@ bool LoadTextureFromMemory(const void* data, size_t data_size,
}
// ABGR
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
(void*)image_data, image_width, image_height, channels * 8,
channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
int pitch = image_width * channels;
SDL_Surface* surface =
SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32,
(void*)image_data, pitch);
if (surface == nullptr) {
LOG_ERROR("Failed to create SDL surface: [{}]", SDL_GetError());
return false;
@@ -136,7 +56,7 @@ bool LoadTextureFromMemory(const void* data, size_t data_size,
*out_width = image_width;
*out_height = image_height;
SDL_FreeSurface(surface);
SDL_DestroySurface(surface);
stbi_image_free(image_data);
return true;
@@ -163,6 +83,205 @@ bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer,
return ret;
}
void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
char* dst_rgba) {
uint8_t* y = reinterpret_cast<uint8_t*>(src);
uint8_t* uv = y + src_w * src_h;
float src_aspect = float(src_w) / src_h;
float dst_aspect = float(dst_w) / dst_h;
int fit_w = dst_w, fit_h = dst_h;
if (src_aspect > dst_aspect) {
fit_h = int(dst_w / src_aspect);
} else {
fit_w = int(dst_h * src_aspect);
}
std::vector<uint8_t> y_i420(src_w * src_h);
std::vector<uint8_t> u_i420((src_w / 2) * (src_h / 2));
std::vector<uint8_t> v_i420((src_w / 2) * (src_h / 2));
libyuv::NV12ToI420(y, src_w, uv, src_w, y_i420.data(), src_w, u_i420.data(),
src_w / 2, v_i420.data(), src_w / 2, src_w, src_h);
std::vector<uint8_t> y_fit(fit_w * fit_h);
std::vector<uint8_t> u_fit((fit_w + 1) / 2 * (fit_h + 1) / 2);
std::vector<uint8_t> v_fit((fit_w + 1) / 2 * (fit_h + 1) / 2);
libyuv::I420Scale(y_i420.data(), src_w, u_i420.data(), src_w / 2,
v_i420.data(), src_w / 2, src_w, src_h, y_fit.data(), fit_w,
u_fit.data(), (fit_w + 1) / 2, v_fit.data(),
(fit_w + 1) / 2, fit_w, fit_h, libyuv::kFilterBilinear);
std::vector<uint8_t> abgr(fit_w * fit_h * 4);
libyuv::I420ToABGR(y_fit.data(), fit_w, u_fit.data(), (fit_w + 1) / 2,
v_fit.data(), (fit_w + 1) / 2, abgr.data(), fit_w * 4,
fit_w, fit_h);
memset(dst_rgba, 0, dst_w * dst_h * 4);
for (int i = 0; i < dst_w * dst_h; ++i) {
dst_rgba[i * 4 + 3] = 0xFF;
}
for (int y = 0; y < fit_h; ++y) {
int dst_offset =
((y + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
memcpy(dst_rgba + dst_offset, abgr.data() + y * fit_w * 4, fit_w * 4);
}
}
Thumbnail::Thumbnail(std::string save_path) {
if (!save_path.empty()) {
save_path_ = save_path;
}
RAND_bytes(aes128_key_, sizeof(aes128_key_));
RAND_bytes(aes128_iv_, sizeof(aes128_iv_));
std::filesystem::create_directories(save_path_);
}
Thumbnail::Thumbnail(std::string save_path, unsigned char* aes128_key,
unsigned char* aes128_iv) {
if (!save_path.empty()) {
save_path_ = save_path;
}
memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
std::filesystem::create_directories(save_path_);
}
Thumbnail::~Thumbnail() {
if (rgba_buffer_) {
delete[] rgba_buffer_;
rgba_buffer_ = nullptr;
}
}
int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
const std::string& remote_id,
const std::string& host_name,
const std::string& password) {
if (!rgba_buffer_) {
rgba_buffer_ = new char[thumbnail_width_ * thumbnail_height_ * 4];
}
if (yuv420p) {
ScaleNv12ToABGR((char*)yuv420p, width, height, thumbnail_width_,
thumbnail_height_, rgba_buffer_);
} else {
// If yuv420p is null, fill the buffer with black pixels
memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
// Set alpha channel to opaque
rgba_buffer_[i * 4 + 3] = 0xFF;
}
}
std::string image_file_name;
if (password.empty()) {
return 0;
} else {
// delete the old thumbnail
std::string filename_with_remote_id = remote_id;
DeleteThumbnail(filename_with_remote_id);
}
std::string cipher_password = AES_encrypt(password, aes128_key_, aes128_iv_);
image_file_name = remote_id + 'Y' + host_name + '@' + cipher_password;
std::string file_path = save_path_ + image_file_name;
stbi_write_png(file_path.data(), thumbnail_width_, thumbnail_height_, 4,
rgba_buffer_, thumbnail_width_ * 4);
return 0;
}
int Thumbnail::LoadThumbnail(
SDL_Renderer* renderer,
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>&
recent_connections,
int* width, int* height) {
for (auto& it : recent_connections) {
if (it.second.texture != nullptr) {
SDL_DestroyTexture(it.second.texture);
it.second.texture = nullptr;
}
}
recent_connections.clear();
std::vector<std::filesystem::path> image_paths =
FindThumbnailPath(save_path_);
if (image_paths.size() == 0) {
return -1;
} else {
for (int i = 0; i < image_paths.size(); i++) {
size_t pos1 = image_paths[i].string().find('/') + 1;
std::string cipher_image_name = image_paths[i].filename().string();
std::string remote_id;
std::string cipher_password;
std::string remote_host_name;
std::string original_image_name;
if ('Y' == cipher_image_name[9] && cipher_image_name.size() >= 16) {
size_t pos_y = cipher_image_name.find('Y');
size_t pos_at = cipher_image_name.find('@');
if (pos_y == std::string::npos || pos_at == std::string::npos ||
pos_y >= pos_at) {
LOG_ERROR("Invalid filename");
continue;
}
remote_id = cipher_image_name.substr(0, pos_y);
remote_host_name =
cipher_image_name.substr(pos_y + 1, pos_at - pos_y - 1);
cipher_password = cipher_image_name.substr(pos_at + 1);
original_image_name =
remote_id + 'Y' + remote_host_name + "@" +
AES_decrypt(cipher_password, aes128_key_, aes128_iv_);
} else {
size_t pos_n = cipher_image_name.find('N');
size_t pos_at = cipher_image_name.find('@');
if (pos_n == std::string::npos) {
LOG_ERROR("Invalid filename");
continue;
}
remote_id = cipher_image_name.substr(0, pos_n);
remote_host_name = cipher_image_name.substr(pos_n + 1);
original_image_name =
remote_id + 'N' + remote_host_name + "@" +
AES_decrypt(cipher_password, aes128_key_, aes128_iv_);
}
std::string image_path = save_path_ + cipher_image_name;
recent_connections.emplace_back(
std::make_pair(original_image_name, Thumbnail::RecentConnection()));
LoadTextureFromFile(image_path.c_str(), renderer,
&(recent_connections[i].second.texture), width,
height);
}
return 0;
}
return 0;
}
int Thumbnail::DeleteThumbnail(const std::string& filename_keyword) {
for (const auto& entry : std::filesystem::directory_iterator(save_path_)) {
if (entry.is_regular_file()) {
const std::string filename = entry.path().filename().string();
std::string id_hostname = filename_keyword.substr(0, filename.find('@'));
if (filename.find(id_hostname) != std::string::npos) {
std::filesystem::remove(entry.path());
}
}
}
return 0;
}
std::vector<std::filesystem::path> Thumbnail::FindThumbnailPath(
const std::filesystem::path& directory) {
std::vector<std::filesystem::path> thumbnails_path;
@@ -172,75 +291,25 @@ std::vector<std::filesystem::path> Thumbnail::FindThumbnailPath(
return thumbnails_path;
}
thumbnails_sorted_by_write_time_.clear();
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (entry.is_regular_file()) {
std::time_t last_write_time = std::chrono::system_clock::to_time_t(
time_point_cast<std::chrono::system_clock::duration>(
entry.last_write_time() -
std::filesystem::file_time_type::clock::now() +
std::chrono::system_clock::now()));
thumbnails_sorted_by_write_time_[last_write_time] = entry.path();
thumbnails_path.push_back(entry.path());
}
}
for (auto it = thumbnails_sorted_by_write_time_.rbegin();
it != thumbnails_sorted_by_write_time_.rend(); ++it) {
thumbnails_path.push_back(it->second);
}
std::sort(thumbnails_path.begin(), thumbnails_path.end(),
[](const std::filesystem::path& a, const std::filesystem::path& b) {
return std::filesystem::last_write_time(a) >
std::filesystem::last_write_time(b);
});
return thumbnails_path;
}
int Thumbnail::LoadThumbnail(SDL_Renderer* renderer,
std::map<std::string, SDL_Texture*>& textures,
int* width, int* height) {
for (auto& it : textures) {
if (it.second != nullptr) {
SDL_DestroyTexture(it.second);
it.second = nullptr;
}
}
textures.clear();
std::vector<std::filesystem::path> image_paths =
FindThumbnailPath(image_path_);
if (image_paths.size() == 0) {
return -1;
} else {
for (int i = 0; i < image_paths.size(); i++) {
size_t pos1 = image_paths[i].string().find('/') + 1;
std::string cipher_image_name = image_paths[i].string().substr(pos1);
std::string original_image_name =
AES_decrypt(cipher_image_name, aes128_key_, aes128_iv_);
std::string image_path = image_path_ + cipher_image_name;
textures[original_image_name] = nullptr;
LoadTextureFromFile(image_path.c_str(), renderer,
&(textures[original_image_name]), width, height);
}
return 0;
}
return 0;
}
int Thumbnail::DeleteThumbnail(const std::string& file_name) {
std::string ciphertext = AES_encrypt(file_name, aes128_key_, aes128_iv_);
std::string file_path = image_path_ + ciphertext;
if (std::filesystem::exists(file_path)) {
std::filesystem::remove(file_path);
return 0;
} else {
return -1;
}
}
int Thumbnail::DeleteAllFilesInDirectory() {
if (std::filesystem::exists(image_path_) &&
std::filesystem::is_directory(image_path_)) {
for (const auto& entry : std::filesystem::directory_iterator(image_path_)) {
if (std::filesystem::exists(save_path_) &&
std::filesystem::is_directory(save_path_)) {
for (const auto& entry : std::filesystem::directory_iterator(save_path_)) {
if (std::filesystem::is_regular_file(entry.status())) {
std::filesystem::remove(entry.path());
}

View File

@@ -7,15 +7,27 @@
#ifndef _THUMBNAIL_H_
#define _THUMBNAIL_H_
#include <SDL.h>
#include <SDL3/SDL.h>
#include <filesystem>
#include <map>
#include <unordered_map>
#include <vector>
class Thumbnail {
public:
Thumbnail();
explicit Thumbnail(unsigned char* aes128_key, unsigned char* aes128_iv);
struct RecentConnection {
SDL_Texture* texture = nullptr;
std::string remote_id;
std::string remote_host_name;
std::string password;
bool remember_password = false;
};
public:
Thumbnail(std::string save_path);
explicit Thumbnail(std::string save_path, unsigned char* aes128_key,
unsigned char* aes128_iv);
~Thumbnail();
public:
@@ -24,11 +36,13 @@ class Thumbnail {
const std::string& host_name,
const std::string& password);
int LoadThumbnail(SDL_Renderer* renderer,
std::map<std::string, SDL_Texture*>& textures, int* width,
int* height);
int LoadThumbnail(
SDL_Renderer* renderer,
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>&
recent_connections,
int* width, int* height);
int DeleteThumbnail(const std::string& file_name);
int DeleteThumbnail(const std::string& filename_keyword);
int DeleteAllFilesInDirectory();
@@ -62,8 +76,7 @@ class Thumbnail {
int thumbnail_width_ = 160;
int thumbnail_height_ = 90;
char* rgba_buffer_ = nullptr;
std::string image_path_ = "thumbnails/";
std::map<std::time_t, std::filesystem::path> thumbnails_sorted_by_write_time_;
std::string save_path_ = "thumbnails/";
unsigned char aes128_key_[16];
unsigned char aes128_iv_[16];

View File

@@ -1,4 +1,5 @@
#include "localization.h"
#include "rd_log.h"
#include "render.h"
#define BUTTON_PADDING 36.0f
@@ -138,7 +139,7 @@ int Render::TitleBar(bool main_window) {
std::string close_button = "##xmark"; // ICON_FA_XMARK;
if (ImGui::Button(close_button.c_str(), ImVec2(BUTTON_PADDING, 30))) {
SDL_Event event;
event.type = SDL_QUIT;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,

View File

@@ -1,13 +1,268 @@
#include "speaker_capturer_linux.h"
SpeakerCapturerLinux::SpeakerCapturerLinux() {}
#include <pulse/error.h>
#include <pulse/introspect.h>
SpeakerCapturerLinux::~SpeakerCapturerLinux() {}
#include <condition_variable>
#include <iostream>
#include <thread>
int SpeakerCapturerLinux::Init(speaker_data_cb cb) { return 0; }
int SpeakerCapturerLinux::Destroy() { return 0; }
int SpeakerCapturerLinux::Start() { return 0; }
int SpeakerCapturerLinux::Stop() { return 0; }
#include "rd_log.h"
int SpeakerCapturerLinux::Pause() { return 0; }
int SpeakerCapturerLinux::Resume() { return 0; }
constexpr int kSampleRate = 48000;
constexpr pa_sample_format_t kFormat = PA_SAMPLE_S16LE;
constexpr int kChannels = 1;
constexpr size_t kFrameSizeBytes = 480 * sizeof(int16_t);
SpeakerCapturerLinux::SpeakerCapturerLinux()
: inited_(false), paused_(false), stop_flag_(false) {}
SpeakerCapturerLinux::~SpeakerCapturerLinux() {
Stop();
Destroy();
}
int SpeakerCapturerLinux::Init(speaker_data_cb cb) {
if (inited_) return 0;
cb_ = cb;
inited_ = true;
return 0;
}
int SpeakerCapturerLinux::Destroy() {
inited_ = false;
return 0;
}
std::string SpeakerCapturerLinux::GetDefaultMonitorSourceName() {
std::string monitor_name;
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
pa_mainloop* mainloop = pa_mainloop_new();
pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
pa_context* context = pa_context_new(api, "GetMonitor");
struct CallbackState {
std::string* name;
std::mutex* mtx;
std::condition_variable* cv;
bool* ready;
} state{&monitor_name, &mtx, &cv, &ready};
pa_context_set_state_callback(
context,
[](pa_context* c, void* userdata) {
auto* state = static_cast<CallbackState*>(userdata);
if (pa_context_get_state(c) == PA_CONTEXT_READY) {
pa_operation* operation = pa_context_get_server_info(
c,
[](pa_context*, const pa_server_info* info, void* userdata) {
auto* state = static_cast<CallbackState*>(userdata);
if (info && info->default_sink_name) {
*(state->name) =
std::string(info->default_sink_name) + ".monitor";
}
{
std::lock_guard<std::mutex> lock(*(state->mtx));
*(state->ready) = true;
}
state->cv->notify_one();
},
userdata);
if (operation) {
pa_operation_unref(operation);
}
}
},
&state);
pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
std::thread loop_thread([&]() {
int ret = 0;
pa_mainloop_run(mainloop, &ret);
});
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait_for(lock, std::chrono::seconds(2), [&] { return ready; });
}
pa_context_disconnect(context);
pa_context_unref(context);
pa_mainloop_quit(mainloop, 0);
loop_thread.join();
pa_mainloop_free(mainloop);
return monitor_name;
}
int SpeakerCapturerLinux::Start() {
if (!inited_ || mainloop_thread_.joinable()) return -1;
stop_flag_ = false;
mainloop_thread_ = std::thread([this]() {
std::string monitor_name = GetDefaultMonitorSourceName();
if (monitor_name.empty()) {
LOG_ERROR("Failed to get monitor source");
return;
}
mainloop_ = pa_threaded_mainloop_new();
pa_mainloop_api* api = pa_threaded_mainloop_get_api(mainloop_);
context_ = pa_context_new(api, "SpeakerCapturer");
pa_context_set_state_callback(
context_,
[](pa_context* c, void* userdata) {
auto self = static_cast<SpeakerCapturerLinux*>(userdata);
pa_context_state_t state = pa_context_get_state(c);
if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED ||
state == PA_CONTEXT_TERMINATED) {
pa_threaded_mainloop_signal(self->mainloop_, 0);
}
},
this);
if (pa_threaded_mainloop_start(mainloop_) < 0) {
LOG_ERROR("Failed to start mainloop");
Cleanup();
return;
}
pa_threaded_mainloop_lock(mainloop_);
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) <
0) {
LOG_ERROR("Failed to connect context");
pa_threaded_mainloop_unlock(mainloop_);
Cleanup();
return;
}
while (true) {
pa_context_state_t state = pa_context_get_state(context_);
if (state == PA_CONTEXT_READY) break;
if (!PA_CONTEXT_IS_GOOD(state) || stop_flag_) {
pa_threaded_mainloop_unlock(mainloop_);
Cleanup();
return;
}
pa_threaded_mainloop_wait(mainloop_);
}
pa_sample_spec ss = {kFormat, kSampleRate, kChannels};
stream_ = pa_stream_new(context_, "Capture", &ss, nullptr);
pa_stream_set_read_callback(
stream_,
[](pa_stream* s, size_t len, void* u) {
auto self = static_cast<SpeakerCapturerLinux*>(u);
if (self->paused_ || self->stop_flag_) return;
const void* data = nullptr;
if (pa_stream_peek(s, &data, &len) < 0 || !data) return;
const uint8_t* p = static_cast<const uint8_t*>(data);
self->frame_cache_.insert(self->frame_cache_.end(), p, p + len);
while (self->frame_cache_.size() >= kFrameSizeBytes) {
std::vector<uint8_t> temp_frame(
self->frame_cache_.begin(),
self->frame_cache_.begin() + kFrameSizeBytes);
self->cb_(temp_frame.data(), kFrameSizeBytes, "audio");
self->frame_cache_.erase(
self->frame_cache_.begin(),
self->frame_cache_.begin() + kFrameSizeBytes);
}
pa_stream_drop(s);
},
this);
pa_buffer_attr attr = {.maxlength = (uint32_t)-1,
.tlength = 0,
.prebuf = 0,
.minreq = 0,
.fragsize = (uint32_t)kFrameSizeBytes};
if (pa_stream_connect_record(stream_, monitor_name.c_str(), &attr,
PA_STREAM_ADJUST_LATENCY) < 0) {
LOG_ERROR("Failed to connect stream");
pa_threaded_mainloop_unlock(mainloop_);
Cleanup();
return;
}
while (true) {
pa_stream_state_t s = pa_stream_get_state(stream_);
if (s == PA_STREAM_READY) break;
if (!PA_STREAM_IS_GOOD(s) || stop_flag_) {
pa_threaded_mainloop_unlock(mainloop_);
Cleanup();
return;
}
pa_threaded_mainloop_wait(mainloop_);
}
pa_threaded_mainloop_unlock(mainloop_);
while (!stop_flag_)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
return 0;
}
int SpeakerCapturerLinux::Stop() {
stop_flag_ = true;
if (mainloop_) {
pa_threaded_mainloop_lock(mainloop_);
pa_threaded_mainloop_signal(mainloop_, 0);
pa_threaded_mainloop_unlock(mainloop_);
}
if (mainloop_thread_.joinable()) {
mainloop_thread_.join();
}
Cleanup();
return 0;
}
void SpeakerCapturerLinux::Cleanup() {
if (mainloop_) {
pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_lock(mainloop_);
if (stream_) {
pa_stream_disconnect(stream_);
pa_stream_unref(stream_);
stream_ = nullptr;
}
if (context_) {
pa_context_disconnect(context_);
pa_context_unref(context_);
context_ = nullptr;
}
pa_threaded_mainloop_unlock(mainloop_);
pa_threaded_mainloop_free(mainloop_);
mainloop_ = nullptr;
}
frame_cache_.clear();
}
int SpeakerCapturerLinux::Pause() {
paused_ = true;
return 0;
}
int SpeakerCapturerLinux::Resume() {
paused_ = false;
return 0;
}

View File

@@ -1,12 +1,18 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-08-02
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
* @Date: 2025-07-15
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SPEAKER_CAPTURER_LINUX_H_
#define _SPEAKER_CAPTURER_LINUX_H_
#include <pulse/pulseaudio.h>
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
@@ -17,22 +23,32 @@ class SpeakerCapturerLinux : public SpeakerCapturer {
SpeakerCapturerLinux();
~SpeakerCapturerLinux();
public:
virtual int Init(speaker_data_cb cb);
virtual int Destroy();
virtual int Start();
virtual int Stop();
int Init(speaker_data_cb cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
int Pause();
int Resume();
private:
speaker_data_cb cb_ = nullptr;
std::string GetDefaultMonitorSourceName();
void Cleanup();
private:
bool inited_ = false;
// thread
std::thread capture_thread_;
speaker_data_cb cb_ = nullptr;
std::atomic<bool> inited_;
std::atomic<bool> paused_;
std::atomic<bool> stop_flag_;
std::thread mainloop_thread_;
pa_threaded_mainloop* mainloop_ = nullptr;
pa_context* context_ = nullptr;
pa_stream* stream_ = nullptr;
std::mutex state_mtx_;
std::vector<uint8_t> frame_cache_;
};
#endif

View File

@@ -1,14 +0,0 @@
#include "speaker_capturer_macosx.h"
SpeakerCapturerMacosx::SpeakerCapturerMacosx() {}
SpeakerCapturerMacosx::~SpeakerCapturerMacosx() {}
int SpeakerCapturerMacosx::Init(speaker_data_cb cb) { return 0; }
int SpeakerCapturerMacosx::Destroy() { return 0; }
int SpeakerCapturerMacosx::Start() { return 0; }
int SpeakerCapturerMacosx::Stop() { return 0; }
int SpeakerCapturerMacosx::Pause() { return 0; }
int SpeakerCapturerMacosx::Resume() { return 0; }

View File

@@ -26,13 +26,12 @@ class SpeakerCapturerMacosx : public SpeakerCapturer {
int Pause();
int Resume();
private:
public:
speaker_data_cb cb_ = nullptr;
private:
bool inited_ = false;
// thread
std::thread capture_thread_;
class Impl;
Impl* impl_ = nullptr;
};
#endif

View File

@@ -0,0 +1,264 @@
#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#import <ScreenCaptureKit/ScreenCaptureKit.h>
#include "rd_log.h"
#include "speaker_capturer_macosx.h"
@interface SpeakerCaptureDelegate : NSObject <SCStreamDelegate, SCStreamOutput>
@property(nonatomic, assign) SpeakerCapturerMacosx* owner;
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner;
@end
@implementation SpeakerCaptureDelegate
- (instancetype)initWithOwner:(SpeakerCapturerMacosx*)owner {
self = [super init];
if (self) {
_owner = owner;
}
return self;
}
- (void)stream:(SCStream*)stream
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
ofType:(SCStreamOutputType)type {
if (type == SCStreamOutputTypeAudio) {
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length = CMBlockBufferGetDataLength(blockBuffer);
char* dataPtr = NULL;
CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, NULL, &dataPtr);
CMAudioFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
const AudioStreamBasicDescription* asbd =
CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
if (_owner->cb_ && dataPtr && length > 0 && asbd) {
std::vector<short> out_pcm16;
if (asbd->mFormatFlags & kAudioFormatFlagIsFloat) {
int channels = asbd->mChannelsPerFrame;
int samples = (int)(length / sizeof(float));
float* floatData = (float*)dataPtr;
std::vector<short> pcm16(samples);
for (int i = 0; i < samples; ++i) {
float v = floatData[i];
if (v > 1.0f) v = 1.0f;
if (v < -1.0f) v = -1.0f;
pcm16[i] = (short)(v * 32767.0f);
}
if (channels > 1) {
int mono_samples = samples / channels;
out_pcm16.resize(mono_samples);
for (int i = 0; i < mono_samples; ++i) {
int sum = 0;
for (int c = 0; c < channels; ++c) {
sum += pcm16[i * channels + c];
}
out_pcm16[i] = sum / channels;
}
} else {
out_pcm16 = std::move(pcm16);
}
} else if (asbd->mBitsPerChannel == 16) {
int channels = asbd->mChannelsPerFrame;
int samples = (int)(length / 2);
short* src = (short*)dataPtr;
if (channels > 1) {
int mono_samples = samples / channels;
out_pcm16.resize(mono_samples);
for (int i = 0; i < mono_samples; ++i) {
int sum = 0;
for (int c = 0; c < channels; ++c) {
sum += src[i * channels + c];
}
out_pcm16[i] = sum / channels;
}
} else {
out_pcm16.assign(src, src + samples);
}
}
size_t frame_bytes = 960; // 480 * 2
size_t total_bytes = out_pcm16.size() * sizeof(short);
unsigned char* p = (unsigned char*)out_pcm16.data();
for (size_t offset = 0; offset + frame_bytes <= total_bytes; offset += frame_bytes) {
_owner->cb_(p + offset, frame_bytes, "audio");
}
}
}
}
@end
class SpeakerCapturerMacosx::Impl {
public:
SCStreamConfiguration* config = nil;
SCStream* stream = nil;
SpeakerCaptureDelegate* delegate = nil;
dispatch_queue_t queue = nil;
SCShareableContent* content = nil;
SCDisplay* mainDisplay = nil;
~Impl() {
if (stream) {
[stream stopCaptureWithCompletionHandler:^(NSError* _Nullable error){
}];
stream = nil;
}
delegate = nil;
if (queue) {
queue = nil;
}
content = nil;
mainDisplay = nil;
config = nil;
}
};
SpeakerCapturerMacosx::SpeakerCapturerMacosx() {
impl_ = new Impl();
inited_ = false;
cb_ = nullptr;
}
SpeakerCapturerMacosx::~SpeakerCapturerMacosx() {
Destroy();
delete impl_;
impl_ = nullptr;
}
int SpeakerCapturerMacosx::Init(speaker_data_cb cb) {
if (inited_) {
return 0;
}
cb_ = cb;
impl_->config = [[SCStreamConfiguration alloc] init];
impl_->config.capturesAudio = YES;
impl_->config.sampleRate = 48000;
impl_->config.channelCount = 1;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block NSError* error = nil;
[SCShareableContent
getShareableContentWithCompletionHandler:^(SCShareableContent* c, NSError* e) {
impl_->content = c;
error = e;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (error || !impl_->content) {
LOG_ERROR("Failed to get shareable content: {}",
std::string([error.localizedDescription UTF8String]));
return -1;
}
CGDirectDisplayID mainDisplayId = CGMainDisplayID();
impl_->mainDisplay = nil;
for (SCDisplay* d in impl_->content.displays) {
if (d.displayID == mainDisplayId) {
impl_->mainDisplay = d;
break;
}
}
if (!impl_->mainDisplay) {
LOG_ERROR("Main display not found");
return -1;
}
if (!impl_->queue) {
impl_->queue = dispatch_queue_create("SpeakerAudio.Queue", DISPATCH_QUEUE_SERIAL);
}
inited_ = true;
return 0;
}
int SpeakerCapturerMacosx::Start() {
if (!inited_) {
return -1;
}
if (impl_->stream) {
dispatch_semaphore_t semaStop = dispatch_semaphore_create(0);
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
dispatch_semaphore_signal(semaStop);
}];
dispatch_semaphore_wait(semaStop, DISPATCH_TIME_FOREVER);
impl_->stream = nil;
impl_->delegate = nil;
}
impl_->delegate = [[SpeakerCaptureDelegate alloc] initWithOwner:this];
SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:impl_->mainDisplay
excludingWindows:@[]];
impl_->stream = [[SCStream alloc] initWithFilter:filter
configuration:impl_->config
delegate:impl_->delegate];
NSError* addOutputError = nil;
BOOL ok = [impl_->stream addStreamOutput:impl_->delegate
type:SCStreamOutputTypeAudio
sampleHandlerQueue:impl_->queue
error:&addOutputError];
if (!ok || addOutputError) {
LOG_ERROR("addStreamOutput error: {}",
std::string([addOutputError.localizedDescription UTF8String]));
impl_->stream = nil;
impl_->delegate = nil;
return -1;
}
dispatch_semaphore_t semaStart = dispatch_semaphore_create(0);
__block int ret = 0;
[impl_->stream startCaptureWithCompletionHandler:^(NSError* _Nullable error) {
if (error) {
LOG_ERROR("startCaptureWithCompletionHandler error: {}",
std::string([error.localizedDescription UTF8String]));
ret = -1;
}
dispatch_semaphore_signal(semaStart);
}];
dispatch_semaphore_wait(semaStart, DISPATCH_TIME_FOREVER);
return ret;
}
int SpeakerCapturerMacosx::Stop() {
if (!inited_) return -1;
if (!impl_->stream) return -1;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[impl_->stream stopCaptureWithCompletionHandler:^(NSError* error) {
if (error) {
LOG_ERROR("stopCaptureWithCompletionHandler error: {}",
std::string([error.localizedDescription UTF8String]));
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
impl_->stream = nil;
impl_->delegate = nil;
return 0;
}
int SpeakerCapturerMacosx::Destroy() {
Stop();
cb_ = nullptr;
if (impl_) {
impl_->config = nil;
impl_->content = nil;
impl_->mainDisplay = nil;
if (impl_->queue) {
impl_->queue = nil;
}
}
inited_ = false;
return 0;
}
int SpeakerCapturerMacosx::Pause() { return 0; }
int SpeakerCapturerMacosx::Resume() { return Start(); }

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