Compare commits

..

224 commits
v1.6.0 ... dev

Author SHA1 Message Date
Leonardo Chen
02ffb264c9 fix: windows build error
Some checks failed
Windows / Build (push) Has been cancelled
MacOS / Build (push) Has been cancelled
Ubuntu / Build (push) Has been cancelled
2025-04-10 08:42:47 +08:00
barry
314bd4f613 ui: update i18
Some checks failed
MacOS / Build (push) Has been cancelled
Ubuntu / Build (push) Has been cancelled
Windows / Build (push) Has been cancelled
2025-03-09 15:51:50 +08:00
barry
6f0f9447da feat: add for QuickAssistant 2025-03-09 15:46:12 +08:00
barry
cfe79c7d5a feat: config support set language 2025-03-09 15:40:06 +08:00
barry
dbf25166ea feat: add for QuickAssistant 2025-03-09 15:27:31 +08:00
Liu Jinchang
f5cccac0df feat: add IP history record feature
- Implement IP address history storage
- Add mechanism to record connection timestamps
- Ensure unique IP entries in history

Log: Add functionality to store and track IP address history

Issue: https://github.com/barry-ran/QtScrcpy/issues/1075
2025-03-06 14:18:13 +08:00
rankun
224f04ffa0 feat: add star badge 2025-03-04 17:21:22 +08:00
barry
a8d3609c19 feat: ad for QuickMirror
Some checks failed
MacOS / Build (push) Has been cancelled
Ubuntu / Build (push) Has been cancelled
Windows / Build (push) Has been cancelled
2025-02-21 22:25:18 +08:00
rankun
790f422f99 fix: not found adb on windows
Some checks are pending
MacOS / Build (push) Waiting to run
Ubuntu / Build (push) Waiting to run
Windows / Build (push) Waiting to run
2025-02-21 13:28:25 +08:00
re2zero
c1faff820d fix: #1117 Fix phone window show blank if run with Qt6
It needs to bind every time when GL paint on Qt6, and it works on Qt5 too.

Log: Fix phone window show blank if run with Qt6.
2025-02-21 11:11:11 +08:00
barry
5fa18219b6 fix: qt6 build error 2025-02-21 11:11:11 +08:00
barry
8196e46648 feat: update QtScrcpyCore 2025-02-21 11:11:11 +08:00
rankun
f863a91f94 feat: mac support arm64 2025-02-21 11:11:11 +08:00
rankun
ae1523b2a0 chore: add build annotation 2025-02-21 11:11:11 +08:00
re2zero
96fc6bfdf7 feat: Enable Qt6 build
Enable build with Qt6 and compat Qt5, the user can select which Qt version by configuration parameter.

Log: Enable Qt6 build.
2025-02-21 11:11:11 +08:00
barry
c1cb2dad3b fix: package x64 failed on mac arm 2025-02-21 11:11:11 +08:00
barry
7d1fdf4965 fix: record&screenshot failed on wireless device
Some checks failed
MacOS / Build (push) Has been cancelled
Ubuntu / Build (push) Has been cancelled
Windows / Build (push) Has been cancelled
2025-01-11 02:42:38 +08:00
rankun
f143c50596 feat: update config.ini server 3.1 2025-01-11 02:42:38 +08:00
rankun
97be13ec82 feat: update scrcpy-server 3.1 2025-01-11 02:42:38 +08:00
AbdAlrahman Ghanem
072d6c7c6c docs: add ScrcpyKeyMapper tool to key mapping documentation 2025-01-11 02:42:38 +08:00
AbdAlrahman Ghanem
49378f540c feat: add configurable drag speed and delay
Added new features to drag functionality:
- Added startDelay parameter to specify delay before drag movement
- Added dragSpeed parameter to control movement speed
- Updated documentation in both English and Chinese

This allows users to fine-tune both the timing and speed of drag movements.
2025-01-11 02:42:38 +08:00
Barry
ec5f413a38
Merge pull request #1073 from barry-ran/dev
Some checks failed
MacOS / Build (push) Has been cancelled
Ubuntu / Build (push) Has been cancelled
Windows / Build (push) Has been cancelled
sync dev
2024-12-07 17:26:04 +08:00
barry
16d693a618 fix: mac only run macos14 2024-12-07 17:04:00 +08:00
barry
8e8df04213 refactor: 适配3.0.2的电源接口 2024-12-07 12:25:47 +08:00
Barry
0a9871290f
Merge pull request #1071 from barry-ran/dev
Some checks are pending
MacOS / Build (push) Waiting to run
Ubuntu / Build (push) Waiting to run
Windows / Build (push) Waiting to run
sync dev
2024-12-07 00:37:30 +08:00
barry
723b4efb0f fix: wireles connect btn too big
Some checks are pending
MacOS / Build (push) Waiting to run
Ubuntu / Build (push) Waiting to run
Windows / Build (push) Waiting to run
2024-12-07 00:24:44 +08:00
barry
a60c2ee906 chore: update ci action 2024-12-07 00:05:37 +08:00
barry
43f5747cfe chore: update ci mac 14 2024-12-07 00:02:18 +08:00
barry
9e613986bf chore: update upload-artifact v4 2024-12-06 23:51:41 +08:00
barry
7c0b32de9a feat: add star history 2024-12-06 23:49:07 +08:00
barry
d0d6356f37 fix: 适配3.0.2的锁定采集方向 2024-12-06 23:43:53 +08:00
barry
9fa4b6672c fix: 录制最后一帧pts不对 2024-12-06 22:25:28 +08:00
barry
1496b767b7 feat: 更新scrcpy-server 3.0.2 2024-12-06 22:03:30 +08:00
Fanxing
5354ae0173
feat: Option to show/hide the toolbar when connecting a device. (#1042)
Some checks are pending
MacOS / Build (push) Waiting to run
Ubuntu / Build (push) Waiting to run
Windows / Build (push) Waiting to run
* Option to show/hide the toolbar when connecting a device.

* Modify the code as required.
2024-12-06 16:33:15 +08:00
Barry
076f9ba4c7
Merge pull request #1000 from teohhanhui/patch-1
Fix outdated link to GitHub Actions workflow runs
2024-08-03 14:03:15 +08:00
Teoh Han Hui
8baa406bc6
Fix outdated link to GitHub Actions workflow runs 2024-07-09 17:12:45 +08:00
Barry
05bfbe6e5c
docs: update README_zh.md 2024-05-26 16:53:01 +08:00
rankun
bc2687df7c feat: update core 2024-05-11 15:40:11 +08:00
Barry
ae1851abec Merge pull request #944 from barry-ran/dev
sync dev
2024-04-08 15:07:49 +08:00
Barry
971e94b682 feat: update server 2.4 2024-04-08 15:07:00 +08:00
Barry
e7125d80b7 chore: remove server code 2024-04-08 15:06:48 +08:00
Barry
6147963a1c Merge pull request #943 from barry-ran/dev
merge dev
2024-04-08 14:43:27 +08:00
Barry
4f50092fb1 fix: build error on version repeat 2024-04-08 14:39:25 +08:00
Barry
44575ff658 fix: build error with xcode 2024-04-08 14:17:09 +08:00
Barry
9bfe67455b fix: build error with version repeat 2024-04-08 14:16:48 +08:00
Barry
5b7b54bad9 fix: QtScrcpyCore mac编译错误 2024-04-08 14:14:34 +08:00
Barry
1aa191764e fix: record failed 2024-04-08 13:16:09 +08:00
Barry
5537a15a48 Merge pull request #909 from barry-ran/dev
sync
2024-02-25 17:58:52 +08:00
barry
00a4882263 feat: update ci 2024-02-25 17:56:37 +08:00
barry
1107980188 feat: aad quickmirror 2024-02-25 16:45:36 +08:00
barry
e07a69737e feat: 更新scrcpy-server 2.1.1 2024-02-25 16:32:11 +08:00
barry
332e36972b feat: add quickmirror des 2023-10-23 01:35:51 +08:00
Barry
035dc51d46 Merge pull request #770 from FrzMtrsprt/kill_tray_message
fix: don't show tray message twice
2023-03-20 12:07:27 +08:00
FrzMtrsprt
9a3cfc62f9 fix: build 2023-02-26 00:31:33 +08:00
FrzMtrsprt
4aa8cebcd9 fix: save tray message shown status 2023-02-26 00:22:43 +08:00
FrzMtrsprt
a09a59b52d fix: don't show tray message twice 2023-02-25 23:53:24 +08:00
Barry
e701d959ca Merge pull request #715 from barry-ran/dev
sync dev
2022-10-31 10:39:15 +08:00
rankun
587c1f5872 fix: config path error on mac 2022-10-30 19:25:26 +08:00
Barry
f1e24fe81a Merge pull request #706 from barry-ran/fix/sanxing_bug
fix: samsung error stack corruption detected
2022-10-21 12:16:43 +08:00
Barry
bdcd9c227f fix: samsung error stack corruption detected 2022-10-21 11:44:58 +08:00
Barry
0d0a7edf27 Merge pull request #701 from barry-ran/dev
sync dev
2022-10-18 10:11:34 +08:00
Barry
3658ebbef0 docs: update readme 2022-10-18 10:10:11 +08:00
Barry
1f776d842f docs: update readme 2022-10-18 10:07:39 +08:00
Barry
4b0e42c285 fix: server start failed on samsung 2022-10-18 10:03:41 +08:00
Barry
79a184145e Merge pull request #690 from FrzMtrsprt/dark_border
Enable dark window border on Windows
2022-10-17 11:16:24 +08:00
Barry
fa943f75a3 Merge pull request #699 from barry-ran/dev
fix: mac read client version is 1.21
2022-10-17 11:10:08 +08:00
Barry
8c0b3d4870 fix: mac read client version is 1.21 2022-10-17 10:45:09 +08:00
FrzMtrsprt
ab4fea7b03 Merge branch 'dark_border' of https://github.com/FrzMtrsprt/QtScrcpy into dark_border 2022-10-16 21:30:47 +08:00
FrzMtrsprt
5a31a8fa48 Merge branch 'barry-ran:dev' into dark_border 2022-10-16 21:30:18 +08:00
FrzMtrsprt
bd51fa19a0 Fix build error 2022-10-16 21:29:47 +08:00
Barry
2dc9189473 Merge pull request #696 from barry-ran/dev
merge dev
2022-10-16 19:26:27 +08:00
Barry
01bbff8fe4 fix: build error 2022-10-16 19:09:10 +08:00
Barry
c0ac2de19e feat: update server 1.24 2022-10-16 14:29:59 +08:00
FrzMtrsprt
f5380bc514 Enable dark window border on Windows 2022-10-07 12:20:14 +08:00
Barry
ca55caa0f0 Merge pull request #666 from re2zero/dev
feat: enable back to original size after exit fullscreen.
2022-09-14 10:03:50 +08:00
re2zero
ef2e822c13 Merge branch 'barry-ran:dev' into dev 2022-09-13 19:05:46 +08:00
Barry
b44edf076a fix: drag ui delay recv video 2022-09-12 21:53:53 +08:00
Barry
c237a17b06 feat: update server source code 2022-09-12 10:35:19 +08:00
Barry
3fd25c367f fix: record failed 2022-09-12 10:17:46 +08:00
Barry
5a20373c88 Merge pull request #656 from UjhhgtgTeams/dev
Set up Linux Github Actions Build & Add Linux Build Instructions & Update Readme
2022-09-12 10:15:37 +08:00
Ujhhgtg
45fbbbf813 scrcpy-server: update to 1.24
Signed-off-by: Ujhhgtg <feyxiexzf@gmail.com>
2022-08-22 15:06:10 +08:00
Ujhhgtg
ab3541a8a8 gh-actions: remove gen-ver as it causes problems 2022-08-22 14:56:50 +08:00
Ujhhgtg
cb73720d74 ci: fix a problem caused by renaming 2022-08-22 14:28:28 +08:00
Ujhhgtg
5e328a7be7 docs + ci: update readme and rename linux build script as i have tested it on many platforms 2022-08-22 14:16:53 +08:00
YangWu
56b7c03748 feat: enable back to original size after exit fullscreen.
After exit fullscreen, it still show as fullscreen with title, this make user feel unwell. Record the normal size and then recover will be beter.

Log: support recover size.
2022-08-18 10:07:45 +08:00
Ujhhgtg
1af89bcb04 gh-actions: use relwithdebinfo for actions and release for releases 2022-08-08 12:43:53 +08:00
Ujhhgtg
c7e0727e5d ci / gh-actions : update linux build script
The script is tested on my computer with zsh, qt 5.15.2
2022-08-08 11:48:39 +08:00
Ujhhgtg
eb3579482d docs: update chinese readme 2022-08-06 13:25:45 +00:00
Ujhhgtg
b66b1b6e71 docs: update readme again 2022-08-06 13:25:35 +00:00
Ujhhgtg
2e2423c6a1 docs: update readme 2022-08-06 13:12:46 +00:00
Ujhhgtg
e8d073dfae gh-actions: fix & update actions 2022-08-06 13:00:09 +00:00
Ujhhgtg
26b0c896ad gh-actions: try to standardize the process 2022-08-06 12:51:14 +00:00
Ujhhgtg
90dfcbd075 gh-actions: fix grammar 2022-08-06 12:32:39 +00:00
Ujhhgtg
29e7654c38 gh-actions: make artifacts' name more clear 2022-08-06 12:30:26 +00:00
Ujhhgtg
21f67a0aa5 gh-actions: update qt 2022-08-06 12:26:58 +00:00
Ujhhgtg
ab4175489d [Github Actions] Capture linux build artifacts 2022-08-06 12:17:24 +00:00
Barry
d1d2f0454f Merge pull request #640 from barry-ran/dev
sync
2022-07-10 14:53:56 +08:00
Barry
e253e63b00 Merge pull request #638 from Zeroo28/patch-1
Improve documentation
2022-07-10 09:23:05 +08:00
Zeroo
86d20e653b Merge branch 'patch-1' of https://github.com/Zeroo28/QtScrcpy into patch-1 2022-07-06 08:34:28 +07:00
Zeroo
0158707fb5 docs: fixes grammar mistakes and typos
_I have used Grammarly to fixed them_
2022-07-06 08:32:34 +07:00
Zeroo
d6896e30df revert(vcs): removed .vscode directory from .gitignore 2022-07-06 08:26:46 +07:00
Zeroo
d956c1fa68 Revert "docs(readme): Fixed 'build' category link"
This reverts commit b1dbbfcdb0.
2022-06-29 16:32:09 +07:00
Zeroo
44e815f373 chore: removed .vscode directory
Removed vscode's config directory from version control.
2022-06-29 14:16:38 +07:00
Zeroo
6bddc2de95 docs(readme): Fixed 'build' category link 2022-06-29 13:39:32 +07:00
Barry
c9ecb8bda7 fix: ui translate error 2022-06-22 21:34:09 +08:00
Barry
a78d9d0304 fix: single mode resize bug 2022-06-22 21:26:28 +08:00
Barry
89fda3a56c fix: audio DELETE_FAILED_INTERNAL_ERROR 2022-06-22 20:48:51 +08:00
Barry
6a2b0eeb1c fix: error log open file failed 2022-06-22 20:34:39 +08:00
Barry
2a4093a487 Merge pull request #628 from barry-ran/dev
sync
2022-06-20 13:27:06 +08:00
Barry
cfc17759eb docs: update readme 2022-06-20 13:25:37 +08:00
Barry
d77355a20d feat: auto update device 2022-06-20 13:12:01 +08:00
冉坤
8bef579494 feat: update ffmpeg to n4.4.2 2022-06-20 07:48:13 +08:00
冉坤
3ac2bcdeef fix: ma debug adb path 2022-06-20 07:47:59 +08:00
Barry
0af13110eb feat: install only not wait 2022-06-15 07:48:09 +08:00
冉坤
c2a3aa106b fix: mac no sound 2022-06-14 22:12:05 +08:00
冉坤
ff7db18d5f feat: support audio on mac 2022-06-14 21:56:58 +08:00
冉坤
fb632ffb25 fix: mac build error 2022-06-14 16:34:56 +08:00
Barry
e70070c0d6 feat: support audio on win 2022-06-14 15:57:32 +08:00
Barry
055315f411 fix: language is invalid 2022-06-12 18:22:15 +08:00
冉坤
ace268b4be feat: adjust Dialog to Widget 2022-06-12 17:39:10 +08:00
冉坤
51fa52accb fix: system tray bug on mac 2022-06-12 17:08:31 +08:00
Barry
9fd26b0583 fix: system tray bug 2022-06-12 16:16:28 +08:00
冉坤
55639f1625 feat: update ffmpeg&adb 2022-06-12 14:02:15 +08:00
冉坤
4f12e13a7e fix: crash on repeat disconnect 2022-06-11 22:31:19 +08:00
Barry
78cc026ddb fix: adb path error on debug 2022-06-09 18:22:08 +08:00
Barry
d5e915d404 Merge pull request #621 from barry-ran/dev
refactor: add QtScrcpyCore submodule
2022-06-09 07:40:35 +08:00
Barry
31736d9ae3 chore: fetch all tags 2022-06-09 07:20:58 +08:00
Barry
d7250be9bc chore: ci clone submodule 2022-06-08 21:33:16 +08:00
Barry
8a8eff2615 chore: ci checkout submodule 2022-06-08 21:25:21 +08:00
Barry
dddb815428 docs: update readme 2022-06-08 21:19:46 +08:00
Barry
d79122fab9 fix: build error on mac 2022-06-08 21:15:27 +08:00
Barry
89edbe6ce2 chore: add submodule QtScrcpyCore 2022-06-08 21:12:08 +08:00
Barry
2a7919850c chore: remove QtScrcpyCore 2022-06-08 21:00:14 +08:00
Barry
ee8be50e33 chore: add cmake log 2022-06-08 20:45:42 +08:00
Barry
d3b665037d feat: good translate 2022-06-08 13:59:09 +08:00
Barry
9daad1aaa6 chore: remove cmake translate 2022-06-08 11:45:35 +08:00
Barry
a894944973 fix: build error 2022-06-08 08:53:20 +08:00
Barry
ed24afb1a0 feat: add group control 2022-06-07 20:55:15 +08:00
Barry
335185ea08 fix: linux build error 2022-06-06 19:48:28 +08:00
Barry
b3030c292a fix: build error on win 2022-06-06 19:34:19 +08:00
Barry
1cc2c7d2c8 fix: linux build error 2022-06-06 18:35:30 +08:00
Barry
a5fe25767a chore: win build copy adb 2022-06-06 18:10:12 +08:00
冉坤
249041c3b2 fix: mac build error 2022-06-06 18:05:59 +08:00
Barry
15bff4f250 refactor: QtScrcpyCore static lib 2022-06-06 17:03:52 +08:00
Barry
8cefc7c24e docs: remove pro 2022-06-06 08:49:51 +08:00
Barry
f171608860 fix: build error on mac 2022-06-06 08:41:24 +08:00
Barry
dee737b0de refactor: move third_party to QtScrcpyCore 2022-06-06 08:31:02 +08:00
Barry
36d435b9b8 refactor: move adbprocess to QtScrcpyCore 2022-06-06 07:57:40 +08:00
Barry
a7df344d96 refactor: move bufferutil to QtScrcpyCore 2022-06-05 18:28:39 +08:00
Barry
f675e11f3c refactor: add QtScrcpyCore 2022-06-05 18:03:08 +08:00
Barry
86b5548a95 refactor: move out config from device 2022-06-05 17:21:20 +08:00
Barry
f40e97be2c refactor: adjust interface 2022-05-24 11:02:32 +08:00
Barry
77276119eb refactor: change device params 2022-05-24 10:42:04 +08:00
Barry
4b9a8cfe86 refactor: change device params 2022-05-24 10:12:57 +08:00
Barry
ea0d55b175 refactor: move videoform to outside of device 2022-05-23 09:33:36 +08:00
Barry
767bb71e7a refactor: device signal to method 2022-05-21 17:41:38 +08:00
Barry
687b146f6d refactor: add disconnected singal 2022-05-21 07:34:12 +08:00
Barry
c4fdbffbf4 refactor: add onDeviceConnected 2022-04-10 11:32:14 +08:00
Barry
6dbe96d3a8 fix: not dispaly must record 2022-04-10 11:06:54 +08:00
Barry
c480f30b88 refactor: devicemanage create&destroy device 2022-04-10 10:45:31 +08:00
Barry
2acb11ac23 refactor: move config from server 2022-04-09 20:57:10 +08:00
Barry
2088c7f76f refactor: reduced server signal 2022-04-09 20:40:03 +08:00
Barry
0403ce2569 fix: memory leak on server start failed 2022-04-09 20:29:07 +08:00
Barry
9a72d4626f chore: move ui to device outside 2022-04-09 20:03:26 +08:00
Barry
bb43261872 Merge pull request #600 from barry-ran/dev
feat: adjust ui
2022-04-09 17:35:25 +08:00
Barry
816ae74672 feat: update language 2022-04-09 17:30:46 +08:00
Barry
3f15d1292d fix: cant not open screen bug 2022-04-09 17:19:41 +08:00
Barry
7e781f46ae feat: good ui 2022-04-09 16:28:47 +08:00
Barry
6b792dcc1c docs: update readme 2022-04-09 15:18:08 +08:00
Barry
393dc3fda6 fix: grab rect dpi error on linux 2022-04-09 15:17:58 +08:00
Barry
1be849aa22 feat: change ui 2022-04-09 13:31:52 +08:00
Barry
8d360f62db fix: grab rect dpi error 2022-04-09 12:10:06 +08:00
Barry
2d235ee9a1 chore: 移除lock文件 2022-04-09 10:42:59 +08:00
Barry
b83c8cbe6f refactor: move VideoBuffer to Decoder 2022-04-09 10:12:34 +08:00
Barry
32cebd45aa refactor: controller and socket decoupling 2022-04-06 20:34:38 +08:00
Barry
d68d96b4ee fix: remove invalid quote 2022-04-06 11:47:49 +08:00
Barry
0b39bbc719 refactor: stream and decoder&recorder decoupling 2022-04-06 09:51:16 +08:00
Barry
dbbe5297b0 refactor: stream and socket decoupling 2022-03-30 19:21:50 +08:00
Barry
e0d879b11c Merge pull request #570 from matteobaccan/patch-1
Correct typo error
2022-03-15 19:45:44 +08:00
Matteo Baccan
416d2df3de Correct typo error
Correct "unkonow" -> "unknown"
2022-01-27 11:38:14 +01:00
Barry
44d2825a0d Merge pull request #562 from barry-ran/dev
Dev
2022-01-16 22:34:31 +08:00
Barry
51748710d4 fix: ubuntu build error 2022-01-16 22:29:00 +08:00
Barry
2f205d2e1b fix: mac start error 2022-01-16 22:17:02 +08:00
Barry
8bcd98491a fix: clipboard bug 2022-01-16 21:57:44 +08:00
Barry
7e1fa77ad1 docs: update readme 2022-01-16 20:12:02 +08:00
冉坤
c3e84c62f6 docs: update readme 2022-01-10 12:04:48 +08:00
冉坤
16ee41074f fix: build error on macos 2022-01-10 11:57:19 +08:00
Barry
fc400fabd4 Merge pull request #545 from barry-ran/dev
Dev
2022-01-09 17:11:41 +08:00
Barry
c71942cda6 feat: copy vcruntime dll 2022-01-09 16:12:11 +08:00
Barry
f2641816d1 feat: update server 2022-01-09 15:49:51 +08:00
Barry
5e1cc1b44f feat: add cmake (#544)
* feat: cmake win

* fix: win bug

* feat: mac

* feat: mac

* feat: linux

* feat: remove old cmake

* fix: build bug

* feat: ci mac build

* feat: language

* feat: build win

* fix: win buid error

* fix: mac build error

* fix: mac publish error

* feat: test

* feat: remove ubuntu 16

* feat: cmake build on ubuntu

* fix: ubuntu build error

* feat: test

* fix: build error on ubuntu

* fix: build error

* fix: build error

* fix

* fix

* fix: 1

* fix: 2

* fix: 3

* f

* a

* b

* 1

Co-authored-by: 冉坤 <rankun@bytedance.com>
2022-01-09 15:43:52 +08:00
Barry
1dced51f0b Merge pull request #536 from studycwq/support-android-12
升级scrcpy-server到最新1.21支持Android12
2022-01-05 15:02:03 +08:00
Will Chen
0ebd4dbb6b bump scrcpy-server to 1.21 2021-12-21 19:02:40 +08:00
leiyu
1e2de40dc8 chore: Adapter Qt5.11 2021-12-05 08:50:16 +08:00
RainbowXie
ddc9f222cb Qt api update
Qt::MidButton': MidButton is deprecated. Use MiddleButton instead
2021-12-05 08:50:16 +08:00
Barry
57ca7ce1a3 Merge pull request #470 from TheLumbee/patch-1
Update dialog.cpp
2021-11-25 07:15:07 +08:00
TheLumbee
c25fb44c59 Update dialog.cpp
Corrected grammar
2021-08-07 22:49:33 -07:00
Barry
03774244eb Merge pull request #469 from barry-ran/dev
Dev
2021-08-08 11:38:35 +08:00
Barry
2a91fcf721 Merge pull request #468 from LeiYu-uniontech/dev
feat: right click map to return
2021-08-08 11:27:11 +08:00
leiyu
e0cb232133 feat: right click map to return 2021-08-08 11:23:04 +08:00
Barry
d8ea9170ef Merge pull request #439 from ZXfkSIE/dev
Modify the input method of bitrate. Bug fix
2021-07-07 10:40:36 +08:00
Zhang Xiang
d787c3a9d4 Modify the input method of bitrate. Bug fix 2021-07-05 20:50:26 +08:00
Barry
e20d07fbf0 Merge pull request #438 from barry-ran/dev
Dev
2021-06-28 12:13:17 +08:00
Barry
b75edaae04 fix: drag delay timer too big bug 2021-06-28 09:13:39 +08:00
Barry
1373b0b84f feat: add drag delay 2021-06-28 09:00:46 +08:00
Barry
29c03c2285 fix: steer wheel delay bug 2021-06-27 22:08:47 +08:00
Barry
0a211854e9 feat: add steel wheel delay 2021-06-27 20:22:22 +08:00
Barry
f904a8c284 feat: quit to system tray 2021-06-27 14:27:10 +08:00
Barry
493c87ada1 feat: nickname for videoform title 2021-06-27 13:16:06 +08:00
Barry
47c3e6af60 Merge branch 'dev' of github.com:barry-ran/QtScrcpy into dev 2021-06-27 13:01:42 +08:00
Barry
6382be1421 Merge pull request #432 from ZXfkSIE/dev
Fix CMake errors and add MinGW support
2021-06-27 13:01:20 +08:00
Barry
e4b61054ea Merge branch 'dev' of github.com:barry-ran/QtScrcpy into dev 2021-06-27 12:58:35 +08:00
Zhang Xiang
c7ddc43acf Fix CMake errors under Linux 2021-06-24 13:41:25 +08:00
ZXfxSIE
26b5914c3a Fix several problems concerning CMake 2021-06-24 13:06:42 +08:00
Barry
48394b32e9 Merge pull request #430 from ZXfkSIE/dev
Fix CMake problems about output path and qm files generation
2021-06-23 11:32:16 +08:00
Zhang Xiang
83b0a9a92f Fix CMake problems about output path and qm files generation 2021-06-22 08:48:52 +08:00
Barry
250ec338c9 feat: Ported to CMake, rename i18n files, and other minor adjustments
Ported to CMake, rename i18n files, and other minor adjustments
2021-06-16 16:18:27 +08:00
Zhang Xiang
ba05a362c4 Ported to CMake, rename i18n files, and other minor adjustments 2021-06-16 11:31:25 +08:00
rankun
7b3e02232e fix: qmenu memory leak 2021-05-31 15:20:13 +08:00
Barry
44e6b5950c fix: Fix memory leak
fix: Fix memory leak
2021-05-31 15:19:03 +08:00
ifdu-code
3e5c5603c0 fix: Fix memory leak 2021-05-18 09:55:55 +08:00
Barry
b8000c2dbd feat: update dialog height 2021-05-16 16:58:12 +08:00
rankun
501365560b feat: adjust window height by simple mode 2021-05-16 13:00:26 +08:00
Barry
3bbea81e12 feat: change video window title 2021-04-17 19:00:34 +08:00
301 changed files with 4742 additions and 43104 deletions

View file

@ -13,12 +13,23 @@ on:
jobs:
build:
name: Build
runs-on: macos-latest
# install-qt-action在arm上执行macdeployqt会报parse otool错误所以在intel mac上执行:
# 用qt6时在arm mac上编译arm和intel都没有问题
# qt5+intel mac编译intel没问题
# qt5+arm mac编译intel会报错
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
runs-on: macos-13
strategy:
matrix:
qt-ver: [5.15.1]
qt-arch-install: [clang_64]
clang-arch: [x64]
qt-ver: [5.15.2, 6.5.3]
# 配置qt-ver的额外设置qt-arch-installbuild-arch
include:
- qt-ver: 5.15.2
qt-arch-install: clang_64
build-arch: x64
- qt-ver: 6.5.3
qt-arch-install: arm64
build-arch: arm64
env:
target-name: QtScrcpy
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
@ -26,25 +37,35 @@ jobs:
steps:
- name: Cache Qt
id: cache-qt
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install Qt
uses: jurplel/install-qt-action@v2.13.0
- name: Install Qt5
if: startsWith(matrix.qt-ver, '5.')
uses: jurplel/install-qt-action@v4.1.1
with:
version: ${{ matrix.qt-ver }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}
- uses: actions/checkout@v1
- name: Install Qt6
if: startsWith(matrix.qt-ver, '6.')
uses: jurplel/install-qt-action@v4.1.1
with:
fetch-depth: 1
version: ${{ matrix.qt-ver }}
modules: qtmultimedia
cached: ${{ steps.cache-qt.outputs.cache-hit }}
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: 'true'
ssh-key: ${{ secrets.BOT_SSH_KEY }}
# 编译
- name: Build MacOS
env:
ENV_QT_PATH: ${{ env.qt-install-path }}
run: |
python ci/generate-version.py
ci/mac/build_for_mac.sh release
ci/mac/build_for_mac.sh RelWithDebInfo ${{ matrix.build-arch }}
# 获取ref最后一个/后的内容
- name: Get the version
shell: bash
@ -56,17 +77,17 @@ jobs:
id: package
env:
ENV_QT_PATH: ${{ env.qt-install-path }}
publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.clang-arch }}-${{ steps.get-version.outputs.version }}
publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.build-arch }}-Qt${{matrix.qt-ver}}-${{ steps.get-version.outputs.version }}
run: |
ci/mac/publish_for_mac.sh ../build
ci/mac/publish_for_mac.sh ../build ${{ matrix.build-arch }}
ci/mac/package_for_mac.sh
mv ci/build/QtScrcpy.app ci/build/${{ env.publish_name }}.app
mv ci/build/QtScrcpy.dmg ci/build/${{ env.publish_name }}.dmg
echo "::set-output name=package-name::${{ env.publish_name }}"
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.package.outputs.package-name }}.zip
path: ci/build/${{ steps.package.outputs.package-name }}.app
path: ci/build/${{ steps.package.outputs.package-name }}.dmg
# Upload to release
- name: Upload Release
if: startsWith(github.ref, 'refs/tags/')

View file

@ -1,11 +1,11 @@
name: Ubuntu
# Qt官方没有linux平台的x86包
on:
push:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/ubuntu.yml'
- 'ci/linux/**'
pull_request:
paths:
- 'QtScrcpy/**'
@ -17,8 +17,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-16.04,ubuntu-18.04]
qt-ver: [5.15.1]
os: [ubuntu-22.04]
qt-ver: [5.15.2]
qt-arch-install: [gcc_64]
gcc-arch: [x64]
env:
@ -26,25 +26,56 @@ jobs:
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
plantform-des: ubuntu
steps:
- name: Cache Qt
id: cache-qt
uses: actions/cache@v1
with:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install Qt
uses: jurplel/install-qt-action@v2.13.0
uses: jurplel/install-qt-action@v4.1.1
with:
version: ${{ matrix.qt-ver }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}
- name: Ubuntu install GL library
run: sudo apt-get install -y libglew-dev libglfw3-dev
- uses: actions/checkout@v1
- name: Cache Qt
id: cache-qt
uses: actions/cache@v4
with:
fetch-depth: 1
- name: Build Ubuntu
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install GL library
run: sudo apt-get install -y libglew-dev libglfw3-dev
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: 'true'
ssh-key: ${{ secrets.BOT_SSH_KEY }}
- name: Build RelWithDebInfo
env:
ENV_QT_PATH: ${{ env.qt-install-path }}
run: |
python ci/generate-version.py
ci/linux/build_for_ubuntu.sh release
ci/linux/build_for_linux.sh "RelWithDebInfo"
- name: Upload RelWithDebInfo
uses: actions/upload-artifact@v4
with:
name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}-RelWithDebInfo
path: output/x64/RelWithDebInfo/*
- name: Build Release
env:
ENV_QT_PATH: ${{ env.qt-install-path }}
run: |
ci/linux/build_for_linux.sh "Release"
- name: Upload Release
uses: actions/upload-artifact@v4
with:
name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}-Release
path: output/x64/Release/*
- name: Install the zip utility
run: |
sudo apt install zip -y
- name: Zip the Artifacts
run: |
zip -r QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip output/x64/Release
- name: Upload to Releases
if: startsWith(github.ref, 'refs/tags/')
uses: svenstaro/upload-release-action@2.3.0
with:
file: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip
asset_name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true

View file

@ -24,7 +24,7 @@ jobs:
# 矩阵配置 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
strategy:
matrix:
qt-ver: [5.15.1]
qt-ver: [5.15.2]
qt-arch: [win64_msvc2019_64, win32_msvc2019]
# 配置qt-arch的额外设置msvc-archqt-arch-install
include:
@ -47,14 +47,14 @@ jobs:
steps:
- name: Cache Qt
id: cache-qt
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch }}
# 安装Qt
- name: Install Qt
# 使用外部action。这个action专门用来安装Qt
uses: jurplel/install-qt-action@v2.13.0
uses: jurplel/install-qt-action@v4.1.1
with:
# Version of Qt to install
version: ${{ matrix.qt-ver }}
@ -64,9 +64,11 @@ jobs:
arch: ${{ matrix.qt-arch }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}
# 拉取代码
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
fetch-depth: 0
submodules: 'true'
ssh-key: ${{ secrets.BOT_SSH_KEY }}
# 编译msvc
- name: Build MSVC
# shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
@ -76,7 +78,7 @@ jobs:
ENV_QT_PATH: ${{ env.qt-install-path }}
run: |
call python ci\generate-version.py
call "ci\win\build_for_win.bat" release ${{ matrix.msvc-arch }}
call "ci\win\build_for_win.bat" RelWithDebInfo ${{ matrix.msvc-arch }}
# 获取ref最后一个/后的内容
- name: Get the version
shell: bash
@ -98,7 +100,7 @@ jobs:
echo "::set-output name=package-name::${{ env.publish_name }}"
# 上传artifacts
# https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.package.outputs.package-name }}.zip
path: ci\build\${{ steps.package.outputs.package-name }}

4
.gitignore vendored
View file

@ -12,4 +12,6 @@
/build/
build-*
*.DS_Store
userdata.ini
userdata.ini
Info_Mac.plist
/ci/build_temp

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "QtScrcpy/QtScrcpyCore"]
path = QtScrcpy/QtScrcpyCore
url = git@github.com:barry-ran/QtScrcpyCore.git

4
CMakeLists.txt Executable file
View file

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
project(all)
add_subdirectory(QtScrcpy)

382
QtScrcpy/CMakeLists.txt Executable file
View file

@ -0,0 +1,382 @@
# For VS2019 and Xcode 12+ support.
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
#
# Global config
#
# QC is "Qt CMake"
# https://www.kdab.com/wp-content/uploads/stories/QTVTC20-Using-Modern-CMake-Kevin-Funk.pdf
# QC Custom config
set(QC_PROJECT_NAME "QtScrcpy")
# Read version numbers from file
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/appversion QC_FILE_VERSION)
set(QC_PROJECT_VERSION ${QC_FILE_VERSION})
# Project declare
project(${QC_PROJECT_NAME} VERSION ${QC_PROJECT_VERSION} LANGUAGES CXX)
message(STATUS "[${PROJECT_NAME}] Project ${PROJECT_NAME} ${PROJECT_VERSION}")
# QC define
# check arch
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(QC_CPU_ARCH x64)
else()
set(QC_CPU_ARCH x86)
endif()
# MacOS
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# mac default arch arm64
if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES arm64)
endif()
if (CMAKE_OSX_ARCHITECTURES MATCHES "arm64")
set(QC_CPU_ARCH arm64)
endif()
endif()
message(STATUS "[${PROJECT_NAME}] CPU_ARCH:${QC_CPU_ARCH}")
# CMake set
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# default RelWithDebInfo
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
message(STATUS "[${PROJECT_NAME}] BUILD_TYPE:${CMAKE_BUILD_TYPE}")
# Compiler set
message(STATUS "[${PROJECT_NAME}] C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
if (MSVC)
# FFmpeg cannot be compiled natively by MSVC version < 12.0 (2013)
if(MSVC_VERSION LESS 1800)
message(FATAL_ERROR "[${PROJECT_NAME}] ERROR: MSVC version is older than 12.0 (2013).")
endif()
message(STATUS "[${PROJECT_NAME}] Set Warnings as error")
# warning level 3 and all warnings as errors
add_compile_options(/W3 /WX /wd4566)
# avoid warning C4819
#add_compile_options(-source-charset:utf-8)
# /utf-8 will set source charset and execution charset to utf-8, so we don't need to set source-charset:utf-8
add_compile_options(/utf-8)
# ensure we use minimal "windows.h" lib without the crazy min max macros
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
# disable SAFESEH - avoid "LNK2026: module unsafe"(Qt5.15&&vs2019)
add_link_options(/SAFESEH:NO)
endif()
if (NOT MSVC)
message(STATUS "[${PROJECT_NAME}] Set warnings as error")
# lots of warnings and all warnings as errors
add_compile_options(-Wall -Wextra -pedantic -Werror)
# disable some warning
add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)
endif()
#
# Qt
#
# Find Qt version
if (NOT QT_DESIRED_VERSION)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
message(" >>> Found Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
set(QT_DESIRED_VERSION ${QT_VERSION_MAJOR})
endif()
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(qt_required_components Widgets Network Multimedia)
if (QT_DESIRED_VERSION MATCHES 6)
# list(APPEND qt_required_components Core5Compat)
list(APPEND qt_required_components OpenGL)
list(APPEND qt_required_components OpenGLWidgets)
else()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND qt_required_components X11Extras )
endif()
endif()
find_package(Qt${QT_DESIRED_VERSION} REQUIRED COMPONENTS ${qt_required_components})
set(LINK_LIBS
Qt${QT_DESIRED_VERSION}::Widgets
Qt${QT_DESIRED_VERSION}::Network
Qt${QT_DESIRED_VERSION}::Multimedia
)
if (QT_DESIRED_VERSION MATCHES 6)
# list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::Core5Compat)
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::GuiPrivate)
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGL)
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGLWidgets)
else()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::X11Extras)
endif()
endif()
message(STATUS "[${PROJECT_NAME}] Qt version is: ${QT_DESIRED_VERSION}")
#
# Sources
#
# fontawesome
set(QC_FONTAWESOME_SOURCES
fontawesome/iconhelper.h
fontawesome/iconhelper.cpp
)
source_group(fontawesome FILES ${QC_FONTAWESOME_SOURCES})
# uibase
set(QC_UIBASE_SOURCES
uibase/keepratiowidget.h
uibase/keepratiowidget.cpp
uibase/magneticwidget.h
uibase/magneticwidget.cpp
)
source_group(uibase FILES ${QC_UIBASE_SOURCES})
# audio
set(QC_AUDIO_SOURCES
audio/audiooutput.h
audio/audiooutput.cpp
)
source_group(audio FILES ${QC_AUDIO_SOURCES})
# ui
set(QC_UI_SOURCES
ui/toolform.h
ui/toolform.cpp
ui/toolform.ui
ui/videoform.h
ui/videoform.cpp
ui/videoform.ui
ui/dialog.cpp
ui/dialog.h
ui/dialog.ui
render/qyuvopenglwidget.h
render/qyuvopenglwidget.cpp
)
source_group(ui FILES ${QC_UI_SOURCES})
# group controller
set(QC_GROUP_CONTROLLER
groupcontroller/groupcontroller.h
groupcontroller/groupcontroller.cpp
)
source_group(groupcontroller FILES ${QC_GROUP_CONTROLLER})
# util
set(QC_UTIL_SOURCES
util/config.h
util/config.cpp
util/mousetap/mousetap.h
util/mousetap/mousetap.cpp
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES}
util/mousetap/winmousetap.h
util/mousetap/winmousetap.cpp
util/winutils.h
util/winutils.cpp
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES}
util/mousetap/xmousetap.h
util/mousetap/xmousetap.cpp
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES}
util/mousetap/cocoamousetap.h
util/mousetap/cocoamousetap.mm
util/path.h
util/path.mm
)
endif()
source_group(util FILES ${QC_UTIL_SOURCES})
# qrc
set(QC_QRC_SOURCES "res/res.qrc")
# main
set(QC_MAIN_SOURCES
main.cpp
${QC_QRC_SOURCES}
)
# plantform file
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
# Define VERSION macros for .rc file
add_compile_definitions(
VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
VERSION_MINOR=${PROJECT_VERSION_MINOR}
VERSION_PATCH=${PROJECT_VERSION_PATCH}
VERSION_RC_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"
)
set(QC_PLANTFORM_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.rc"
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# Step 1. add icns to source file, for MACOSX_PACKAGE_LOCATION copy
set(QC_PLANTFORM_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns"
)
endif()
# 翻译相关使用shell脚本替代cmake处理翻译
# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/res/i18n)
# all sources
set(QC_PROJECT_SOURCES
${QC_FONTAWESOME_SOURCES}
${QC_UIBASE_SOURCES}
${QC_UI_SOURCES}
${QC_UTIL_SOURCES}
${QC_MAIN_SOURCES}
${QC_GROUP_CONTROLLER}
${QC_PLANTFORM_SOURCES}
${QC_AUDIO_SOURCES}
)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(QC_RUNTIME_TYPE MACOSX_BUNDLE)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(QC_RUNTIME_TYPE WIN32)
endif()
add_executable(${PROJECT_NAME} ${QC_RUNTIME_TYPE} ${QC_PROJECT_SOURCES})
#
# Internal include path (todo: remove this, use absolute path include)
#
target_include_directories(${PROJECT_NAME} PRIVATE fontawesome)
target_include_directories(${PROJECT_NAME} PRIVATE util)
target_include_directories(${PROJECT_NAME} PRIVATE uibase)
target_include_directories(${PROJECT_NAME} PRIVATE ui)
target_include_directories(${PROJECT_NAME} PRIVATE render)
# output dir
# https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html
get_property(QC_IS_MUTIL_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
message(STATUS "multi config:" QC_IS_MUTIL_CONFIG)
# $<0:> 使用生成器表达式为每个config设置RUNTIME_OUTPUT_DIRECTORY这样multi config就不会自动追加CMAKE_BUILD_TYPE子目录了
# 1. multi config介绍 https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html
# 2. multi config在不用表达式生成器时自动追加子目录说明 https://cmake.org/cmake/help/latest/prop_tgt/RUNTIME_OUTPUT_DIRECTORY.html
# 3. 使用表达式生成器禁止multi config自动追加子目录解决方案 https://stackoverflow.com/questions/7747857/in-cmake-how-do-i-work-around-the-debug-and-release-directories-visual-studio-2
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../output/${QC_CPU_ARCH}/${CMAKE_BUILD_TYPE}/$<0:>"
)
#
# plantform deps
#
# windows
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.bat" "${QSC_BIN_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
)
endif()
# MacOS
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# qt6 need 10.15 or later
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")
# copy bundle file
get_target_property(MACOS_BUNDLE_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(MACOS_BUNDLE_PATH ${MACOS_BUNDLE_PATH}/${PROJECT_NAME}.app/Contents)
set(QSC_DEPLOY_PATH ${MACOS_BUNDLE_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# config file copy to Contents/MacOS/config
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini" "${MACOS_BUNDLE_PATH}/MacOS/config/config.ini"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${MACOS_BUNDLE_PATH}/MacOS"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${MACOS_BUNDLE_PATH}/MacOS"
)
# Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)
# use MACOSX_BUNDLE_INFO_PLIST custom plist, not use MACOSX_BUNDLE_BUNDLE_NAME etc..
set(INFO_PLIST_TEMPLATE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist.in")
set(INFO_PLIST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist")
file(READ "${INFO_PLIST_TEMPLATE_FILE}" plist_contents)
string(REPLACE "\${BUNDLE_VERSION}" "${PROJECT_VERSION}" plist_contents ${plist_contents})
file(WRITE ${INFO_PLIST_FILE} ${plist_contents})
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${INFO_PLIST_FILE}"
# "" disable code sign
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
)
# mac framework
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AppKit")
endif()
# Linux
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${QSC_BIN_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
# xcb https://doc.qt.io/qt-5/linux-requirements.html
xcb
# pthread
Threads::Threads
)
# linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853
endif()
#
# common deps
#
add_subdirectory(QtScrcpyCore)
# Qt
target_link_libraries(${PROJECT_NAME} PRIVATE
${LINK_LIBS}
QtScrcpyCore
)

View file

@ -1,222 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2018-10-07T12:36:10
#
#-------------------------------------------------
QT += core gui
QT += network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = QtScrcpy
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
msvc{
QMAKE_CFLAGS += -source-charset:utf-8
QMAKE_CXXFLAGS += -source-charset:utf-8
}
# warning as error
#4566 https://github.com/Chuyu-Team/VC-LTL/issues/27
*g++*: QMAKE_CXXFLAGS += -Werror
*msvc*: QMAKE_CXXFLAGS += /WX /wd4566
# run a server debugger and wait for a client to be attached
# DEFINES += SERVER_DEBUGGER
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
# DEFINES += SERVER_DEBUGGER_METHOD_NEW
# 源码
SOURCES += \
main.cpp \
dialog.cpp
HEADERS += \
dialog.h
FORMS += \
dialog.ui
# 试用检查
# DEFINES += TRIAL_EXPIRE_CHECK
DEFINES += TRIAL_TIMES=10
# 子工程
include ($$PWD/common/common.pri)
include ($$PWD/adb/adb.pri)
include ($$PWD/uibase/uibase.pri)
include ($$PWD/fontawesome/fontawesome.pri)
include ($$PWD/util/util.pri)
include ($$PWD/device/device.pri)
include ($$PWD/devicemanage/devicemanage.pri)
# 附加包含路径
INCLUDEPATH += \
$$PWD/common \
$$PWD/adb \
$$PWD/uibase \
$$PWD/util \
$$PWD/device \
$$PWD/devicemanage \
$$PWD/fontawesome
# 如果变量没有定义
# !defined(TEST_VAR, var) {
# message("test")
# }
# 从文件读取版本号
CAT_VERSION = $$cat($$PWD/version)
# 拆分出版本号
VERSION_MAJOR = $$section(CAT_VERSION, ., 0, 0)
VERSION_MINOR = $$section(CAT_VERSION, ., 1, 1)
VERSION_PATCH = $$section(CAT_VERSION, ., 2, 2)
message("version:" $${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_PATCH})
# qmake变量的方式定义版本号
VERSION = $${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_PATCH}
# ***********************************************************
# Win平台下配置
# ***********************************************************
win32 {
# 通过rc的方式的话VERSION变量rc中获取不到,定义为宏方便rc中使用
DEFINES += VERSION_MAJOR=$${VERSION_MAJOR}
DEFINES += VERSION_MINOR=$${VERSION_MINOR}
DEFINES += VERSION_PATCH=$${VERSION_PATCH}
DEFINES += VERSION_RC_STR=\\\"$${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_PATCH}\\\"
contains(QT_ARCH, x86_64) {
message("x64")
# 输出目录
CONFIG(debug, debug|release) {
DESTDIR = $$PWD/../output/win/x64/debug
} else {
DESTDIR = $$PWD/../output/win/x64/release
}
# 依赖模块
LIBS += \
-L$$PWD/../third_party/ffmpeg/lib/x64 -lavformat \
-L$$PWD/../third_party/ffmpeg/lib/x64 -lavcodec \
-L$$PWD/../third_party/ffmpeg/lib/x64 -lavutil \
-L$$PWD/../third_party/ffmpeg/lib/x64 -lswscale
WIN_FFMPEG_SRC = $$PWD/../third_party/ffmpeg/bin/x64/*.dll
} else {
message("x86")
# 输出目录
CONFIG(debug, debug|release) {
DESTDIR = $$PWD/../output/win/x86/debug
} else {
DESTDIR = $$PWD/../output/win/x86/release
}
# 依赖模块
LIBS += \
-L$$PWD/../third_party/ffmpeg/lib/x86 -lavformat \
-L$$PWD/../third_party/ffmpeg/lib/x86 -lavcodec \
-L$$PWD/../third_party/ffmpeg/lib/x86 -lavutil \
-L$$PWD/../third_party/ffmpeg/lib/x86 -lswscale
WIN_FFMPEG_SRC = $$PWD/../third_party/ffmpeg/bin/x86/*.dll
}
# 复制依赖库
WIN_DST = $$DESTDIR
WIN_FFMPEG_SRC ~= s,/,\\,g
WIN_DST ~= s,/,\\,g
QMAKE_POST_LINK += $$quote($$QMAKE_COPY $$WIN_FFMPEG_SRC $$WIN_DST$$escape_expand(\n\t))
# windows rc file
RC_FILE = $$PWD/res/QtScrcpy.rc
}
# ***********************************************************
# Mac平台下配置
# ***********************************************************
macos {
# 输出目录
CONFIG(debug, debug|release) {
DESTDIR = $$PWD/../output/mac/debug
} else {
DESTDIR = $$PWD/../output/mac/release
}
# 依赖模块
LIBS += \
-L$$PWD/../third_party/ffmpeg/lib -lavformat.58 \
-L$$PWD/../third_party/ffmpeg/lib -lavcodec.58 \
-L$$PWD/../third_party/ffmpeg/lib -lavutil.56 \
-L$$PWD/../third_party/ffmpeg/lib -lswscale.5
# mac bundle file
APP_SCRCPY_SERVER.files = $$files($$PWD/../third_party/scrcpy-server)
APP_SCRCPY_SERVER.path = Contents/MacOS
QMAKE_BUNDLE_DATA += APP_SCRCPY_SERVER
APP_ADB.files = $$files($$PWD/../third_party/adb/mac/adb)
APP_ADB.path = Contents/MacOS
QMAKE_BUNDLE_DATA += APP_ADB
APP_FFMPEG.files = $$files($$PWD/../third_party/ffmpeg/lib/*.dylib)
APP_FFMPEG.path = Contents/MacOS
QMAKE_BUNDLE_DATA += APP_FFMPEG
APP_CONFIG.files = $$files($$PWD/../config/config.ini)
APP_CONFIG.path = Contents/MacOS/config
QMAKE_BUNDLE_DATA += APP_CONFIG
# mac application icon
ICON = $$PWD/res/QtScrcpy.icns
QMAKE_INFO_PLIST = $$PWD/res/Info_Mac.plist
# 定义目标命令(修改版本号字段)
plistupdate.commands = /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString $$VERSION\" \
-c \"Set :CFBundleVersion $$VERSION\" \
$$DESTDIR/$${TARGET}.app/Contents/Info.plist
# 增加额外目标
QMAKE_EXTRA_TARGETS += plistupdate
# 设置为前置依赖
PRE_TARGETDEPS += plistupdate
}
# ***********************************************************
# Linux平台下配置
# ***********************************************************
linux {
# 输出目录
CONFIG(debug, debug|release) {
DESTDIR = $$PWD/../output/linux/debug
} else {
DESTDIR = $$PWD/../output/linux/release
}
# 依赖模块
LIBS += \
-L$$PWD/../third_party/ffmpeg/lib -lavformat \
-L$$PWD/../third_party/ffmpeg/lib -lavcodec \
-L$$PWD/../third_party/ffmpeg/lib -lavutil \
-L$$PWD/../third_party/ffmpeg/lib -lswscale
# linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853
}
# message("test")
RESOURCES += \
res/res.qrc

1
QtScrcpy/QtScrcpyCore Submodule

@ -0,0 +1 @@
Subproject commit 19e1ba8fb5c59c5a85c3c6a79967fab4c84739c7

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/adbprocess.h
SOURCES += \
$$PWD/adbprocess.cpp

View file

@ -1,236 +0,0 @@
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include "adbprocess.h"
#include "config.h"
QString AdbProcess::s_adbPath = "";
AdbProcess::AdbProcess(QObject *parent) : QProcess(parent)
{
initSignals();
}
AdbProcess::~AdbProcess()
{
if (isRuning()) {
close();
}
}
const QString &AdbProcess::getAdbPath()
{
if (s_adbPath.isEmpty()) {
s_adbPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_ADB_PATH"));
QFileInfo fileInfo(s_adbPath);
if (s_adbPath.isEmpty() || !fileInfo.isFile()) {
s_adbPath = Config::getInstance().getAdbPath();
}
fileInfo = s_adbPath;
if (s_adbPath.isEmpty() || !fileInfo.isFile()) {
s_adbPath = QCoreApplication::applicationDirPath() + "/adb";
}
qInfo("adb path: %s", QDir(s_adbPath).absolutePath().toUtf8().data());
}
return s_adbPath;
}
void AdbProcess::initSignals()
{
// aboutToQuit not exit event loop, so deletelater is ok
//connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &AdbProcess::deleteLater);
connect(this, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
if (NormalExit == exitStatus && 0 == exitCode) {
emit adbProcessResult(AER_SUCCESS_EXEC);
} else {
//P7C0218510000537 unauthorized ,手机端此时弹出调试认证,要允许调试
emit adbProcessResult(AER_ERROR_EXEC);
}
qDebug() << "adb return " << exitCode << "exit status " << exitStatus;
});
connect(this, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
if (QProcess::FailedToStart == error) {
emit adbProcessResult(AER_ERROR_MISSING_BINARY);
} else {
emit adbProcessResult(AER_ERROR_START);
QString err = QString("qprocess start error:%1 %2").arg(program()).arg(arguments().join(" "));
qCritical() << err.toStdString().c_str();
}
});
connect(this, &QProcess::readyReadStandardError, this, [this]() {
QString tmp = QString::fromUtf8(readAllStandardError()).trimmed();
m_errorOutput += tmp;
qWarning() << QString("AdbProcess::error:%1").arg(tmp).toStdString().data();
});
connect(this, &QProcess::readyReadStandardOutput, this, [this]() {
QString tmp = QString::fromUtf8(readAllStandardOutput()).trimmed();
m_standardOutput += tmp;
qInfo() << QString("AdbProcess::out:%1").arg(tmp).toStdString().data();
});
connect(this, &QProcess::started, this, [this]() { emit adbProcessResult(AER_SUCCESS_START); });
}
void AdbProcess::execute(const QString &serial, const QStringList &args)
{
m_standardOutput = "";
m_errorOutput = "";
QStringList adbArgs;
if (!serial.isEmpty()) {
adbArgs << "-s" << serial;
}
adbArgs << args;
qDebug() << getAdbPath() << adbArgs.join(" ");
start(getAdbPath(), adbArgs);
}
bool AdbProcess::isRuning()
{
if (QProcess::NotRunning == state()) {
return false;
} else {
return true;
}
}
void AdbProcess::setShowTouchesEnabled(const QString &serial, bool enabled)
{
QStringList adbArgs;
adbArgs << "shell"
<< "settings"
<< "put"
<< "system"
<< "show_touches";
adbArgs << (enabled ? "1" : "0");
execute(serial, adbArgs);
}
QStringList AdbProcess::getDevicesSerialFromStdOut()
{
// get devices serial by adb devices
QStringList serials;
QStringList devicesInfoList = m_standardOutput.split(QRegExp("\r\n|\n"), Qt::SkipEmptyParts);
for (QString deviceInfo : devicesInfoList) {
QStringList deviceInfos = deviceInfo.split(QRegExp("\t"), Qt::SkipEmptyParts);
if (2 == deviceInfos.count() && 0 == deviceInfos[1].compare("device")) {
serials << deviceInfos[0];
}
}
return serials;
}
QString AdbProcess::getDeviceIPFromStdOut()
{
QString ip = "";
#if 0
QString strIPExp = "inet [\\d.]*";
QRegExp ipRegExp(strIPExp,Qt::CaseInsensitive);
if (ipRegExp.indexIn(m_standardOutput) != -1) {
ip = ipRegExp.cap(0);
ip = ip.right(ip.size() - 5);
}
#else
QString strIPExp = "inet addr:[\\d.]*";
QRegExp ipRegExp(strIPExp, Qt::CaseInsensitive);
if (ipRegExp.indexIn(m_standardOutput) != -1) {
ip = ipRegExp.cap(0);
ip = ip.right(ip.size() - 10);
}
#endif
return ip;
}
QString AdbProcess::getDeviceIPByIpFromStdOut()
{
QString ip = "";
QString strIPExp = "wlan0 inet [\\d.]*";
QRegExp ipRegExp(strIPExp, Qt::CaseInsensitive);
if (ipRegExp.indexIn(m_standardOutput) != -1) {
ip = ipRegExp.cap(0);
ip = ip.right(ip.size() - 14);
}
qDebug() << "get ip: " << ip;
return ip;
}
QString AdbProcess::getStdOut()
{
return m_standardOutput;
}
QString AdbProcess::getErrorOut()
{
return m_errorOutput;
}
void AdbProcess::forward(const QString &serial, quint16 localPort, const QString &deviceSocketName)
{
QStringList adbArgs;
adbArgs << "forward";
adbArgs << QString("tcp:%1").arg(localPort);
adbArgs << QString("localabstract:%1").arg(deviceSocketName);
execute(serial, adbArgs);
}
void AdbProcess::forwardRemove(const QString &serial, quint16 localPort)
{
QStringList adbArgs;
adbArgs << "forward";
adbArgs << "--remove";
adbArgs << QString("tcp:%1").arg(localPort);
execute(serial, adbArgs);
}
void AdbProcess::reverse(const QString &serial, const QString &deviceSocketName, quint16 localPort)
{
QStringList adbArgs;
adbArgs << "reverse";
adbArgs << QString("localabstract:%1").arg(deviceSocketName);
adbArgs << QString("tcp:%1").arg(localPort);
execute(serial, adbArgs);
}
void AdbProcess::reverseRemove(const QString &serial, const QString &deviceSocketName)
{
QStringList adbArgs;
adbArgs << "reverse";
adbArgs << "--remove";
adbArgs << QString("localabstract:%1").arg(deviceSocketName);
execute(serial, adbArgs);
}
void AdbProcess::push(const QString &serial, const QString &local, const QString &remote)
{
QStringList adbArgs;
adbArgs << "push";
adbArgs << local;
adbArgs << remote;
execute(serial, adbArgs);
}
void AdbProcess::install(const QString &serial, const QString &local)
{
QStringList adbArgs;
adbArgs << "install";
adbArgs << "-r";
adbArgs << local;
execute(serial, adbArgs);
}
void AdbProcess::removePath(const QString &serial, const QString &path)
{
QStringList adbArgs;
adbArgs << "shell";
adbArgs << "rm";
adbArgs << path;
execute(serial, adbArgs);
}

View file

@ -1,53 +0,0 @@
#ifndef ADBPROCESS_H
#define ADBPROCESS_H
#include <QProcess>
class AdbProcess : public QProcess
{
Q_OBJECT
public:
enum ADB_EXEC_RESULT
{
AER_SUCCESS_START, // 启动成功
AER_ERROR_START, // 启动失败
AER_SUCCESS_EXEC, // 执行成功
AER_ERROR_EXEC, // 执行失败
AER_ERROR_MISSING_BINARY, // 找不到文件
};
explicit AdbProcess(QObject *parent = nullptr);
virtual ~AdbProcess();
void execute(const QString &serial, const QStringList &args);
void forward(const QString &serial, quint16 localPort, const QString &deviceSocketName);
void forwardRemove(const QString &serial, quint16 localPort);
void reverse(const QString &serial, const QString &deviceSocketName, quint16 localPort);
void reverseRemove(const QString &serial, const QString &deviceSocketName);
void push(const QString &serial, const QString &local, const QString &remote);
void install(const QString &serial, const QString &local);
void removePath(const QString &serial, const QString &path);
bool isRuning();
void setShowTouchesEnabled(const QString &serial, bool enabled);
QStringList getDevicesSerialFromStdOut();
QString getDeviceIPFromStdOut();
QString getDeviceIPByIpFromStdOut();
QString getStdOut();
QString getErrorOut();
static const QString &getAdbPath();
signals:
void adbProcessResult(ADB_EXEC_RESULT processResult);
private:
void initSignals();
private:
QString m_standardOutput = "";
QString m_errorOutput = "";
static QString s_adbPath;
};
#endif // ADBPROCESS_H

View file

@ -0,0 +1,235 @@
#include <QTcpSocket>
#include <QHostAddress>
#include <QAudioOutput>
#include <QTime>
#include <QElapsedTimer>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QAudioSink>
#include <QAudioDevice>
#include <QMediaDevices>
#endif
#include "audiooutput.h"
AudioOutput::AudioOutput(QObject *parent)
: QObject(parent)
{
m_running = false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
m_audioOutput = nullptr;
#else
m_audioSink = nullptr;
#endif
connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput());
});
connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError());
});
}
AudioOutput::~AudioOutput()
{
if (QProcess::NotRunning != m_sndcpy.state()) {
m_sndcpy.kill();
}
stop();
}
bool AudioOutput::start(const QString& serial, int port)
{
if (m_running) {
stop();
}
QElapsedTimer timeConsumeCount;
timeConsumeCount.start();
bool ret = runSndcpyProcess(serial, port);
qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds";
if (!ret) {
return ret;
}
startAudioOutput();
startRecvData(port);
m_running = true;
return true;
}
void AudioOutput::stop()
{
if (!m_running) {
return;
}
m_running = false;
stopRecvData();
stopAudioOutput();
}
void AudioOutput::installonly(const QString &serial, int port)
{
runSndcpyProcess(serial, port, false);
}
bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
{
if (QProcess::NotRunning != m_sndcpy.state()) {
m_sndcpy.kill();
}
#ifdef Q_OS_WIN32
QStringList params{serial, QString::number(port)};
m_sndcpy.start("sndcpy.bat", params);
#else
QStringList params{"sndcpy.sh", serial, QString::number(port)};
m_sndcpy.start("bash", params);
#endif
if (!wait) {
return true;
}
if (!m_sndcpy.waitForStarted()) {
qWarning() << "AudioOutput::start sndcpy process failed";
return false;
}
if (!m_sndcpy.waitForFinished()) {
qWarning() << "AudioOutput::sndcpy process crashed";
return false;
}
return true;
}
void AudioOutput::startAudioOutput()
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (m_audioOutput) {
return;
}
QAudioFormat format;
format.setSampleRate(48000);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(format)) {
qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
return;
}
m_audioOutput = new QAudioOutput(format, this);
connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) {
qInfo() << "AudioOutput::audio state changed:" << state;
});
m_audioOutput->setBufferSize(48000*2*15/1000 * 20);
m_outputDevice = m_audioOutput->start();
#else
if (m_audioSink) {
return;
}
QAudioFormat format;
format.setSampleRate(48000);
format.setChannelCount(2);
format.setSampleFormat(QAudioFormat::Int16);
QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();
if (!defaultDevice.isFormatSupported(format)) {
qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
return;
}
m_audioSink = new QAudioSink(defaultDevice, format, this);
m_outputDevice = m_audioSink->start();
if (!m_outputDevice) {
qWarning() << "AudioOutput::audio output device not available, cannot play audio.";
delete m_audioSink;
m_audioSink = nullptr;
return;
}
#endif
}
void AudioOutput::stopAudioOutput()
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (m_audioOutput) {
m_audioOutput->stop();
delete m_audioOutput;
m_audioOutput = nullptr;
}
#else
if (m_audioSink) {
m_audioSink->stop();
delete m_audioSink;
m_audioSink = nullptr;
}
#endif
m_outputDevice = nullptr;
}
void AudioOutput::startRecvData(int port)
{
if (m_workerThread.isRunning()) {
stopRecvData();
}
auto audioSocket = new QTcpSocket();
audioSocket->moveToThread(&m_workerThread);
connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater);
connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) {
audioSocket->connectToHost(QHostAddress::LocalHost, port);
if (!audioSocket->waitForConnected(500)) {
qWarning("AudioOutput::audio socket connect failed");
return;
}
qInfo("AudioOutput::audio socket connect success");
});
connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() {
qint64 recv = audioSocket->bytesAvailable();
//qDebug() << "AudioOutput::recv data:" << recv;
if (!m_outputDevice) {
return;
}
if (m_buffer.capacity() < recv) {
m_buffer.reserve(recv);
}
qint64 count = audioSocket->read(m_buffer.data(), recv);
m_outputDevice->write(m_buffer.data(), count);
});
connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) {
qInfo() << "AudioOutput::audio socket state changed:" << state;
});
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#else
connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#endif
m_workerThread.start();
emit connectTo(port);
}
void AudioOutput::stopRecvData()
{
if (!m_workerThread.isRunning()) {
return;
}
m_workerThread.quit();
m_workerThread.wait();
}

View file

@ -0,0 +1,46 @@
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H
#include <QThread>
#include <QProcess>
#include <QPointer>
#include <QVector>
class QAudioSink;
class QAudioOutput;
class QIODevice;
class AudioOutput : public QObject
{
Q_OBJECT
public:
explicit AudioOutput(QObject *parent = nullptr);
~AudioOutput();
bool start(const QString& serial, int port);
void stop();
void installonly(const QString& serial, int port);
private:
bool runSndcpyProcess(const QString& serial, int port, bool wait = true);
void startAudioOutput();
void stopAudioOutput();
void startRecvData(int port);
void stopRecvData();
signals:
void connectTo(int port);
private:
QPointer<QIODevice> m_outputDevice;
QThread m_workerThread;
QProcess m_sndcpy;
QVector<char> m_buffer;
bool m_running = false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QAudioOutput* m_audioOutput = nullptr;
#else
QAudioSink *m_audioSink = nullptr;
#endif
};
#endif // AUDIOOUTPUT_H

View file

@ -1,2 +0,0 @@
HEADERS += \
$$PWD/qscrcpyevent.h

View file

@ -1,22 +0,0 @@
#ifndef QSCRCPYEVENT_H
#define QSCRCPYEVENT_H
#include <QEvent>
class QScrcpyEvent : public QEvent
{
public:
enum Type
{
VideoSocket = QEvent::User + 1,
Control,
};
QScrcpyEvent(Type type) : QEvent(QEvent::Type(type)) {}
};
// VideoSocketEvent
class VideoSocketEvent : public QScrcpyEvent
{
public:
VideoSocketEvent() : QScrcpyEvent(VideoSocket) {}
};
#endif // QSCRCPYEVENT_H

View file

@ -1,3 +0,0 @@
HEADERS += \
$$PWD/input.h \
$$PWD/keycodes.h

View file

@ -1,840 +0,0 @@
// copied from <https://android.googlesource.com/platform/frameworks/native/+/master/include/android/input.h>
// blob 08299899b6305a0fe74d7d2b8471b7cd0af49dc7
// (and modified)
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _ANDROID_INPUT_H
#define _ANDROID_INPUT_H
/**
* Meta key / modifer state.
*/
enum AndroidMetastate
{
/** No meta keys are pressed. */
AMETA_NONE = 0,
/** This mask is used to check whether one of the ALT meta keys is pressed. */
AMETA_ALT_ON = 0x02,
/** This mask is used to check whether the left ALT meta key is pressed. */
AMETA_ALT_LEFT_ON = 0x10,
/** This mask is used to check whether the right ALT meta key is pressed. */
AMETA_ALT_RIGHT_ON = 0x20,
/** This mask is used to check whether one of the SHIFT meta keys is pressed. */
AMETA_SHIFT_ON = 0x01,
/** This mask is used to check whether the left SHIFT meta key is pressed. */
AMETA_SHIFT_LEFT_ON = 0x40,
/** This mask is used to check whether the right SHIFT meta key is pressed. */
AMETA_SHIFT_RIGHT_ON = 0x80,
/** This mask is used to check whether the SYM meta key is pressed. */
AMETA_SYM_ON = 0x04,
/** This mask is used to check whether the FUNCTION meta key is pressed. */
AMETA_FUNCTION_ON = 0x08,
/** This mask is used to check whether one of the CTRL meta keys is pressed. */
AMETA_CTRL_ON = 0x1000,
/** This mask is used to check whether the left CTRL meta key is pressed. */
AMETA_CTRL_LEFT_ON = 0x2000,
/** This mask is used to check whether the right CTRL meta key is pressed. */
AMETA_CTRL_RIGHT_ON = 0x4000,
/** This mask is used to check whether one of the META meta keys is pressed. */
AMETA_META_ON = 0x10000,
/** This mask is used to check whether the left META meta key is pressed. */
AMETA_META_LEFT_ON = 0x20000,
/** This mask is used to check whether the right META meta key is pressed. */
AMETA_META_RIGHT_ON = 0x40000,
/** This mask is used to check whether the CAPS LOCK meta key is on. */
AMETA_CAPS_LOCK_ON = 0x100000,
/** This mask is used to check whether the NUM LOCK meta key is on. */
AMETA_NUM_LOCK_ON = 0x200000,
/** This mask is used to check whether the SCROLL LOCK meta key is on. */
AMETA_SCROLL_LOCK_ON = 0x400000,
};
/**
* Input event types.
*/
enum AndroidInputEventType
{
/** Indicates that the input event is a key event. */
AINPUT_EVENT_TYPE_KEY = 1,
/** Indicates that the input event is a motion event. */
AINPUT_EVENT_TYPE_MOTION = 2
};
/**
* Key event actions.
*/
enum AndroidKeyeventAction
{
/** The key has been pressed down. */
AKEY_EVENT_ACTION_DOWN = 0,
/** The key has been released. */
AKEY_EVENT_ACTION_UP = 1,
/**
* Multiple duplicate key events have occurred in a row, or a
* complex string is being delivered. The repeat_count property
* of the key event contains the number of times the given key
* code should be executed.
*/
AKEY_EVENT_ACTION_MULTIPLE = 2
};
/**
* Key event flags.
*/
enum AndroidKeyeventFlags
{
/** This mask is set if the device woke because of this key event. */
AKEY_EVENT_FLAG_WOKE_HERE = 0x1,
/** This mask is set if the key event was generated by a software keyboard. */
AKEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
/** This mask is set if we don't want the key event to cause us to leave touch mode. */
AKEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
/**
* This mask is set if an event was known to come from a trusted
* part of the system. That is, the event is known to come from
* the user, and could not have been spoofed by a third party
* component.
*/
AKEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
/**
* This mask is used for compatibility, to identify enter keys that are
* coming from an IME whose enter key has been auto-labelled "next" or
* "done". This allows TextView to dispatch these as normal enter keys
* for old applications, but still do the appropriate action when
* receiving them.
*/
AKEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
/**
* When associated with up key events, this indicates that the key press
* has been canceled. Typically this is used with virtual touch screen
* keys, where the user can slide from the virtual key area on to the
* display: in that case, the application will receive a canceled up
* event and should not perform the action normally associated with the
* key. Note that for this to work, the application can not perform an
* action for a key until it receives an up or the long press timeout has
* expired.
*/
AKEY_EVENT_FLAG_CANCELED = 0x20,
/**
* This key event was generated by a virtual (on-screen) hard key area.
* Typically this is an area of the touchscreen, outside of the regular
* display, dedicated to "hardware" buttons.
*/
AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
/**
* This flag is set for the first key repeat that occurs after the
* long press timeout.
*/
AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
/**
* Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
* press action was executed while it was down.
*/
AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
/**
* Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
* tracked from its initial down. That is, somebody requested that tracking
* started on the key down and a long press has not caused
* the tracking to be canceled.
*/
AKEY_EVENT_FLAG_TRACKING = 0x200,
/**
* Set when a key event has been synthesized to implement default behavior
* for an event that the application did not handle.
* Fallback key events are generated by unhandled trackball motions
* (to emulate a directional keypad) and by certain unhandled key presses
* that are declared in the key map (such as special function numeric keypad
* keys when numlock is off).
*/
AKEY_EVENT_FLAG_FALLBACK = 0x400,
};
/**
* Bit shift for the action bits holding the pointer index as
* defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
*/
#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
/** Motion event actions */
enum AndroidMotioneventAction
{
/** Bit mask of the parts of the action code that are the action itself. */
AMOTION_EVENT_ACTION_MASK = 0xff,
/**
* Bits in the action code that represent a pointer index, used with
* AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
* down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
* index where the data for the pointer going up or down can be found.
*/
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00,
/** A pressed gesture has started, the motion contains the initial starting location. */
AMOTION_EVENT_ACTION_DOWN = 0,
/**
* A pressed gesture has finished, the motion contains the final release location
* as well as any intermediate points since the last down or move event.
*/
AMOTION_EVENT_ACTION_UP = 1,
/**
* A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
* AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
* any intermediate points since the last down or move event.
*/
AMOTION_EVENT_ACTION_MOVE = 2,
/**
* The current gesture has been aborted.
* You will not receive any more points in it. You should treat this as
* an up event, but not perform any action that you normally would.
*/
AMOTION_EVENT_ACTION_CANCEL = 3,
/**
* A movement has happened outside of the normal bounds of the UI element.
* This does not provide a full gesture, but only the initial location of the movement/touch.
*/
AMOTION_EVENT_ACTION_OUTSIDE = 4,
/**
* A non-primary pointer has gone down.
* The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
*/
AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
/**
* A non-primary pointer has gone up.
* The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
*/
AMOTION_EVENT_ACTION_POINTER_UP = 6,
/**
* A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
* The motion contains the most recent point, as well as any intermediate points since
* the last hover move event.
*/
AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
/**
* The motion event contains relative vertical and/or horizontal scroll offsets.
* Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
* and AMOTION_EVENT_AXIS_HSCROLL.
* The pointer may or may not be down when this event is dispatched.
* This action is always delivered to the winder under the pointer, which
* may not be the window currently touched.
*/
AMOTION_EVENT_ACTION_SCROLL = 8,
/** The pointer is not down but has entered the boundaries of a window or view. */
AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
/** The pointer is not down but has exited the boundaries of a window or view. */
AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
/* One or more buttons have been pressed. */
AMOTION_EVENT_ACTION_BUTTON_PRESS = 11,
/* One or more buttons have been released. */
AMOTION_EVENT_ACTION_BUTTON_RELEASE = 12,
};
/**
* Motion event flags.
*/
enum AndroidMotioneventFlags
{
/**
* This flag indicates that the window that received this motion event is partly
* or wholly obscured by another visible window above it. This flag is set to true
* even if the event did not directly pass through the obscured area.
* A security sensitive application can check this flag to identify situations in which
* a malicious application may have covered up part of its content for the purpose
* of misleading the user or hijacking touches. An appropriate response might be
* to drop the suspect touches or to take additional precautions to confirm the user's
* actual intent.
*/
AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
};
/**
* Motion event edge touch flags.
*/
enum AndroidMotioneventEdgeTouchTlags
{
/** No edges intersected. */
AMOTION_EVENT_EDGE_FLAG_NONE = 0,
/** Flag indicating the motion event intersected the top edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_TOP = 0x01,
/** Flag indicating the motion event intersected the bottom edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
/** Flag indicating the motion event intersected the left edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
/** Flag indicating the motion event intersected the right edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
};
/**
* Constants that identify each individual axis of a motion event.
* @anchor AMOTION_EVENT_AXIS
*/
enum AndroidMotioneventAxis
{
/**
* Axis constant: X axis of a motion event.
*
* - For a touch screen, reports the absolute X screen position of the center of
* the touch contact area. The units are display pixels.
* - For a touch pad, reports the absolute X surface position of the center of the touch
* contact area. The units are device-dependent.
* - For a mouse, reports the absolute X screen position of the mouse pointer.
* The units are display pixels.
* - For a trackball, reports the relative horizontal displacement of the trackball.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
* - For a joystick, reports the absolute X position of the joystick.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
*/
AMOTION_EVENT_AXIS_X = 0,
/**
* Axis constant: Y axis of a motion event.
*
* - For a touch screen, reports the absolute Y screen position of the center of
* the touch contact area. The units are display pixels.
* - For a touch pad, reports the absolute Y surface position of the center of the touch
* contact area. The units are device-dependent.
* - For a mouse, reports the absolute Y screen position of the mouse pointer.
* The units are display pixels.
* - For a trackball, reports the relative vertical displacement of the trackball.
* The value is normalized to a range from -1.0 (up) to 1.0 (down).
* - For a joystick, reports the absolute Y position of the joystick.
* The value is normalized to a range from -1.0 (up or far) to 1.0 (down or near).
*/
AMOTION_EVENT_AXIS_Y = 1,
/**
* Axis constant: Pressure axis of a motion event.
*
* - For a touch screen or touch pad, reports the approximate pressure applied to the surface
* by a finger or other tool. The value is normalized to a range from
* 0 (no pressure at all) to 1 (normal pressure), although values higher than 1
* may be generated depending on the calibration of the input device.
* - For a trackball, the value is set to 1 if the trackball button is pressed
* or 0 otherwise.
* - For a mouse, the value is set to 1 if the primary mouse button is pressed
* or 0 otherwise.
*/
AMOTION_EVENT_AXIS_PRESSURE = 2,
/**
* Axis constant: Size axis of a motion event.
*
* - For a touch screen or touch pad, reports the approximate size of the contact area in
* relation to the maximum detectable size for the device. The value is normalized
* to a range from 0 (smallest detectable size) to 1 (largest detectable size),
* although it is not a linear scale. This value is of limited use.
* To obtain calibrated size information, see
* {@link AMOTION_EVENT_AXIS_TOUCH_MAJOR} or {@link AMOTION_EVENT_AXIS_TOOL_MAJOR}.
*/
AMOTION_EVENT_AXIS_SIZE = 3,
/**
* Axis constant: TouchMajor axis of a motion event.
*
* - For a touch screen, reports the length of the major axis of an ellipse that
* represents the touch area at the point of contact.
* The units are display pixels.
* - For a touch pad, reports the length of the major axis of an ellipse that
* represents the touch area at the point of contact.
* The units are device-dependent.
*/
AMOTION_EVENT_AXIS_TOUCH_MAJOR = 4,
/**
* Axis constant: TouchMinor axis of a motion event.
*
* - For a touch screen, reports the length of the minor axis of an ellipse that
* represents the touch area at the point of contact.
* The units are display pixels.
* - For a touch pad, reports the length of the minor axis of an ellipse that
* represents the touch area at the point of contact.
* The units are device-dependent.
*
* When the touch is circular, the major and minor axis lengths will be equal to one another.
*/
AMOTION_EVENT_AXIS_TOUCH_MINOR = 5,
/**
* Axis constant: ToolMajor axis of a motion event.
*
* - For a touch screen, reports the length of the major axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* - For a touch pad, reports the length of the major axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* The units are device-dependent.
*
* When the touch is circular, the major and minor axis lengths will be equal to one another.
*
* The tool size may be larger than the touch size since the tool may not be fully
* in contact with the touch sensor.
*/
AMOTION_EVENT_AXIS_TOOL_MAJOR = 6,
/**
* Axis constant: ToolMinor axis of a motion event.
*
* - For a touch screen, reports the length of the minor axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* - For a touch pad, reports the length of the minor axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* The units are device-dependent.
*
* When the touch is circular, the major and minor axis lengths will be equal to one another.
*
* The tool size may be larger than the touch size since the tool may not be fully
* in contact with the touch sensor.
*/
AMOTION_EVENT_AXIS_TOOL_MINOR = 7,
/**
* Axis constant: Orientation axis of a motion event.
*
* - For a touch screen or touch pad, reports the orientation of the finger
* or tool in radians relative to the vertical plane of the device.
* An angle of 0 radians indicates that the major axis of contact is oriented
* upwards, is perfectly circular or is of unknown orientation. A positive angle
* indicates that the major axis of contact is oriented to the right. A negative angle
* indicates that the major axis of contact is oriented to the left.
* The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
* (finger pointing fully right).
* - For a stylus, the orientation indicates the direction in which the stylus
* is pointing in relation to the vertical axis of the current orientation of the screen.
* The range is from -PI radians to PI radians, where 0 is pointing up,
* -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
* is pointing right. See also {@link AMOTION_EVENT_AXIS_TILT}.
*/
AMOTION_EVENT_AXIS_ORIENTATION = 8,
/**
* Axis constant: Vertical Scroll axis of a motion event.
*
* - For a mouse, reports the relative movement of the vertical scroll wheel.
* The value is normalized to a range from -1.0 (down) to 1.0 (up).
*
* This axis should be used to scroll views vertically.
*/
AMOTION_EVENT_AXIS_VSCROLL = 9,
/**
* Axis constant: Horizontal Scroll axis of a motion event.
*
* - For a mouse, reports the relative movement of the horizontal scroll wheel.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
*
* This axis should be used to scroll views horizontally.
*/
AMOTION_EVENT_AXIS_HSCROLL = 10,
/**
* Axis constant: Z axis of a motion event.
*
* - For a joystick, reports the absolute Z position of the joystick.
* The value is normalized to a range from -1.0 (high) to 1.0 (low).
* <em>On game pads with two analog joysticks, this axis is often reinterpreted
* to report the absolute X position of the second joystick instead.</em>
*/
AMOTION_EVENT_AXIS_Z = 11,
/**
* Axis constant: X Rotation axis of a motion event.
*
* - For a joystick, reports the absolute rotation angle about the X axis.
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
*/
AMOTION_EVENT_AXIS_RX = 12,
/**
* Axis constant: Y Rotation axis of a motion event.
*
* - For a joystick, reports the absolute rotation angle about the Y axis.
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
*/
AMOTION_EVENT_AXIS_RY = 13,
/**
* Axis constant: Z Rotation axis of a motion event.
*
* - For a joystick, reports the absolute rotation angle about the Z axis.
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
* On game pads with two analog joysticks, this axis is often reinterpreted
* to report the absolute Y position of the second joystick instead.
*/
AMOTION_EVENT_AXIS_RZ = 14,
/**
* Axis constant: Hat X axis of a motion event.
*
* - For a joystick, reports the absolute X position of the directional hat control.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
*/
AMOTION_EVENT_AXIS_HAT_X = 15,
/**
* Axis constant: Hat Y axis of a motion event.
*
* - For a joystick, reports the absolute Y position of the directional hat control.
* The value is normalized to a range from -1.0 (up) to 1.0 (down).
*/
AMOTION_EVENT_AXIS_HAT_Y = 16,
/**
* Axis constant: Left Trigger axis of a motion event.
*
* - For a joystick, reports the absolute position of the left trigger control.
* The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
*/
AMOTION_EVENT_AXIS_LTRIGGER = 17,
/**
* Axis constant: Right Trigger axis of a motion event.
*
* - For a joystick, reports the absolute position of the right trigger control.
* The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
*/
AMOTION_EVENT_AXIS_RTRIGGER = 18,
/**
* Axis constant: Throttle axis of a motion event.
*
* - For a joystick, reports the absolute position of the throttle control.
* The value is normalized to a range from 0.0 (fully open) to 1.0 (fully closed).
*/
AMOTION_EVENT_AXIS_THROTTLE = 19,
/**
* Axis constant: Rudder axis of a motion event.
*
* - For a joystick, reports the absolute position of the rudder control.
* The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
*/
AMOTION_EVENT_AXIS_RUDDER = 20,
/**
* Axis constant: Wheel axis of a motion event.
*
* - For a joystick, reports the absolute position of the steering wheel control.
* The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
*/
AMOTION_EVENT_AXIS_WHEEL = 21,
/**
* Axis constant: Gas axis of a motion event.
*
* - For a joystick, reports the absolute position of the gas (accelerator) control.
* The value is normalized to a range from 0.0 (no acceleration)
* to 1.0 (maximum acceleration).
*/
AMOTION_EVENT_AXIS_GAS = 22,
/**
* Axis constant: Brake axis of a motion event.
*
* - For a joystick, reports the absolute position of the brake control.
* The value is normalized to a range from 0.0 (no braking) to 1.0 (maximum braking).
*/
AMOTION_EVENT_AXIS_BRAKE = 23,
/**
* Axis constant: Distance axis of a motion event.
*
* - For a stylus, reports the distance of the stylus from the screen.
* A value of 0.0 indicates direct contact and larger values indicate increasing
* distance from the surface.
*/
AMOTION_EVENT_AXIS_DISTANCE = 24,
/**
* Axis constant: Tilt axis of a motion event.
*
* - For a stylus, reports the tilt angle of the stylus in radians where
* 0 radians indicates that the stylus is being held perpendicular to the
* surface, and PI/2 radians indicates that the stylus is being held flat
* against the surface.
*/
AMOTION_EVENT_AXIS_TILT = 25,
/**
* Axis constant: Generic scroll axis of a motion event.
*
* - This is used for scroll axis motion events that can't be classified as strictly
* vertical or horizontal. The movement of a rotating scroller is an example of this.
*/
AMOTION_EVENT_AXIS_SCROLL = 26,
/**
* Axis constant: The movement of x position of a motion event.
*
* - For a mouse, reports a difference of x position between the previous position.
* This is useful when pointer is captured, in that case the mouse pointer doesn't
* change the location but this axis reports the difference which allows the app
* to see how the mouse is moved.
*/
AMOTION_EVENT_AXIS_RELATIVE_X = 27,
/**
* Axis constant: The movement of y position of a motion event.
*
* Same as {@link RELATIVE_X}, but for y position.
*/
AMOTION_EVENT_AXIS_RELATIVE_Y = 28,
/**
* Axis constant: Generic 1 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_1 = 32,
/**
* Axis constant: Generic 2 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_2 = 33,
/**
* Axis constant: Generic 3 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_3 = 34,
/**
* Axis constant: Generic 4 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_4 = 35,
/**
* Axis constant: Generic 5 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_5 = 36,
/**
* Axis constant: Generic 6 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_6 = 37,
/**
* Axis constant: Generic 7 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_7 = 38,
/**
* Axis constant: Generic 8 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_8 = 39,
/**
* Axis constant: Generic 9 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_9 = 40,
/**
* Axis constant: Generic 10 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_10 = 41,
/**
* Axis constant: Generic 11 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_11 = 42,
/**
* Axis constant: Generic 12 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_12 = 43,
/**
* Axis constant: Generic 13 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_13 = 44,
/**
* Axis constant: Generic 14 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_14 = 45,
/**
* Axis constant: Generic 15 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_15 = 46,
/**
* Axis constant: Generic 16 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_16 = 47,
// NOTE: If you add a new axis here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
};
/**
* Constants that identify buttons that are associated with motion events.
* Refer to the documentation on the MotionEvent class for descriptions of each button.
*/
enum AndroidMotioneventButtons
{
/** primary */
AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0,
/** secondary */
AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1,
/** tertiary */
AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
/** back */
AMOTION_EVENT_BUTTON_BACK = 1 << 3,
/** forward */
AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6,
};
/**
* Constants that identify tool types.
* Refer to the documentation on the MotionEvent class for descriptions of each tool type.
*/
enum AndroidMotioneventToolType
{
/** unknown */
AMOTION_EVENT_TOOL_TYPE_UNKNOWN = 0,
/** finger */
AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
/** stylus */
AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
/** mouse */
AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
/** eraser */
AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
};
/**
* Input source masks.
*
* Refer to the documentation on android.view.InputDevice for more details about input sources
* and their correct interpretation.
*/
enum AndroidInputSourceClass
{
/** mask */
AINPUT_SOURCE_CLASS_MASK = 0x000000ff,
/** none */
AINPUT_SOURCE_CLASS_NONE = 0x00000000,
/** button */
AINPUT_SOURCE_CLASS_BUTTON = 0x00000001,
/** pointer */
AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
/** navigation */
AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
/** position */
AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
/** joystick */
AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
};
/**
* Input sources.
*/
enum AndroidInputSource
{
/** unknown */
AINPUT_SOURCE_UNKNOWN = 0x00000000,
/** keyboard */
AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
/** dpad */
AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
/** gamepad */
AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
/** touchscreen */
AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
/** mouse */
AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
/** stylus */
AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
/** bluetooth stylus */
AINPUT_SOURCE_BLUETOOTH_STYLUS = 0x00008000 | AINPUT_SOURCE_STYLUS,
/** trackball */
AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
/** mouse relative */
AINPUT_SOURCE_MOUSE_RELATIVE = 0x00020000 | AINPUT_SOURCE_CLASS_NAVIGATION,
/** touchpad */
AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
/** navigation */
AINPUT_SOURCE_TOUCH_NAVIGATION = 0x00200000 | AINPUT_SOURCE_CLASS_NONE,
/** joystick */
AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
/** rotary encoder */
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
};
/**
* Keyboard types.
*
* Refer to the documentation on android.view.InputDevice for more details.
*/
enum AndroidKeyboardType
{
/** none */
AINPUT_KEYBOARD_TYPE_NONE = 0,
/** non alphabetic */
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
/** alphabetic */
AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
};
/**
* Constants used to retrieve information about the range of motion for a particular
* coordinate of a motion event.
*
* Refer to the documentation on android.view.InputDevice for more details about input sources
* and their correct interpretation.
*
* @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*} constants instead.
*/
enum AndroidMotionRange
{
/** x */
AINPUT_MOTION_RANGE_X = AMOTION_EVENT_AXIS_X,
/** y */
AINPUT_MOTION_RANGE_Y = AMOTION_EVENT_AXIS_Y,
/** pressure */
AINPUT_MOTION_RANGE_PRESSURE = AMOTION_EVENT_AXIS_PRESSURE,
/** size */
AINPUT_MOTION_RANGE_SIZE = AMOTION_EVENT_AXIS_SIZE,
/** touch major */
AINPUT_MOTION_RANGE_TOUCH_MAJOR = AMOTION_EVENT_AXIS_TOUCH_MAJOR,
/** touch minor */
AINPUT_MOTION_RANGE_TOUCH_MINOR = AMOTION_EVENT_AXIS_TOUCH_MINOR,
/** tool major */
AINPUT_MOTION_RANGE_TOOL_MAJOR = AMOTION_EVENT_AXIS_TOOL_MAJOR,
/** tool minor */
AINPUT_MOTION_RANGE_TOOL_MINOR = AMOTION_EVENT_AXIS_TOOL_MINOR,
/** orientation */
AINPUT_MOTION_RANGE_ORIENTATION = AMOTION_EVENT_AXIS_ORIENTATION,
};
#endif // _ANDROID_INPUT_H

View file

@ -1,746 +0,0 @@
// copied from <https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h>
// blob 2164d6163e1646c22825e364cad4f3c47638effd
// (and modified)
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _ANDROID_KEYCODES_H
#define _ANDROID_KEYCODES_H
/**
* Key codes.
*/
enum AndroidKeycode
{
/** Unknown key code. */
AKEYCODE_UNKNOWN = 0,
/** Soft Left key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom left
* of the display. */
AKEYCODE_SOFT_LEFT = 1,
/** Soft Right key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom right
* of the display. */
AKEYCODE_SOFT_RIGHT = 2,
/** Home key.
* This key is handled by the framework and is never delivered to applications. */
AKEYCODE_HOME = 3,
/** Back key. */
AKEYCODE_BACK = 4,
/** Call key. */
AKEYCODE_CALL = 5,
/** End Call key. */
AKEYCODE_ENDCALL = 6,
/** '0' key. */
AKEYCODE_0 = 7,
/** '1' key. */
AKEYCODE_1 = 8,
/** '2' key. */
AKEYCODE_2 = 9,
/** '3' key. */
AKEYCODE_3 = 10,
/** '4' key. */
AKEYCODE_4 = 11,
/** '5' key. */
AKEYCODE_5 = 12,
/** '6' key. */
AKEYCODE_6 = 13,
/** '7' key. */
AKEYCODE_7 = 14,
/** '8' key. */
AKEYCODE_8 = 15,
/** '9' key. */
AKEYCODE_9 = 16,
/** '*' key. */
AKEYCODE_STAR = 17,
/** '#' key. */
AKEYCODE_POUND = 18,
/** Directional Pad Up key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_UP = 19,
/** Directional Pad Down key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_DOWN = 20,
/** Directional Pad Left key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_LEFT = 21,
/** Directional Pad Right key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_RIGHT = 22,
/** Directional Pad Center key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_CENTER = 23,
/** Volume Up key.
* Adjusts the speaker volume up. */
AKEYCODE_VOLUME_UP = 24,
/** Volume Down key.
* Adjusts the speaker volume down. */
AKEYCODE_VOLUME_DOWN = 25,
/** Power key. */
AKEYCODE_POWER = 26,
/** Camera key.
* Used to launch a camera application or take pictures. */
AKEYCODE_CAMERA = 27,
/** Clear key. */
AKEYCODE_CLEAR = 28,
/** 'A' key. */
AKEYCODE_A = 29,
/** 'B' key. */
AKEYCODE_B = 30,
/** 'C' key. */
AKEYCODE_C = 31,
/** 'D' key. */
AKEYCODE_D = 32,
/** 'E' key. */
AKEYCODE_E = 33,
/** 'F' key. */
AKEYCODE_F = 34,
/** 'G' key. */
AKEYCODE_G = 35,
/** 'H' key. */
AKEYCODE_H = 36,
/** 'I' key. */
AKEYCODE_I = 37,
/** 'J' key. */
AKEYCODE_J = 38,
/** 'K' key. */
AKEYCODE_K = 39,
/** 'L' key. */
AKEYCODE_L = 40,
/** 'M' key. */
AKEYCODE_M = 41,
/** 'N' key. */
AKEYCODE_N = 42,
/** 'O' key. */
AKEYCODE_O = 43,
/** 'P' key. */
AKEYCODE_P = 44,
/** 'Q' key. */
AKEYCODE_Q = 45,
/** 'R' key. */
AKEYCODE_R = 46,
/** 'S' key. */
AKEYCODE_S = 47,
/** 'T' key. */
AKEYCODE_T = 48,
/** 'U' key. */
AKEYCODE_U = 49,
/** 'V' key. */
AKEYCODE_V = 50,
/** 'W' key. */
AKEYCODE_W = 51,
/** 'X' key. */
AKEYCODE_X = 52,
/** 'Y' key. */
AKEYCODE_Y = 53,
/** 'Z' key. */
AKEYCODE_Z = 54,
/** ',' key. */
AKEYCODE_COMMA = 55,
/** '.' key. */
AKEYCODE_PERIOD = 56,
/** Left Alt modifier key. */
AKEYCODE_ALT_LEFT = 57,
/** Right Alt modifier key. */
AKEYCODE_ALT_RIGHT = 58,
/** Left Shift modifier key. */
AKEYCODE_SHIFT_LEFT = 59,
/** Right Shift modifier key. */
AKEYCODE_SHIFT_RIGHT = 60,
/** Tab key. */
AKEYCODE_TAB = 61,
/** Space key. */
AKEYCODE_SPACE = 62,
/** Symbol modifier key.
* Used to enter alternate symbols. */
AKEYCODE_SYM = 63,
/** Explorer special function key.
* Used to launch a browser application. */
AKEYCODE_EXPLORER = 64,
/** Envelope special function key.
* Used to launch a mail application. */
AKEYCODE_ENVELOPE = 65,
/** Enter key. */
AKEYCODE_ENTER = 66,
/** Backspace key.
* Deletes characters before the insertion point, unlike {@link AKEYCODE_FORWARD_DEL}. */
AKEYCODE_DEL = 67,
/** '`' (backtick) key. */
AKEYCODE_GRAVE = 68,
/** '-'. */
AKEYCODE_MINUS = 69,
/** '=' key. */
AKEYCODE_EQUALS = 70,
/** '[' key. */
AKEYCODE_LEFT_BRACKET = 71,
/** ']' key. */
AKEYCODE_RIGHT_BRACKET = 72,
/** '\' key. */
AKEYCODE_BACKSLASH = 73,
/** ';' key. */
AKEYCODE_SEMICOLON = 74,
/** ''' (apostrophe) key. */
AKEYCODE_APOSTROPHE = 75,
/** '/' key. */
AKEYCODE_SLASH = 76,
/** '@' key. */
AKEYCODE_AT = 77,
/** Number modifier key.
* Used to enter numeric symbols.
* This key is not {@link AKEYCODE_NUM_LOCK}; it is more like {@link AKEYCODE_ALT_LEFT}. */
AKEYCODE_NUM = 78,
/** Headset Hook key.
* Used to hang up calls and stop media. */
AKEYCODE_HEADSETHOOK = 79,
/** Camera Focus key.
* Used to focus the camera. */
AKEYCODE_FOCUS = 80,
/** '+' key. */
AKEYCODE_PLUS = 81,
/** Menu key. */
AKEYCODE_MENU = 82,
/** Notification key. */
AKEYCODE_NOTIFICATION = 83,
/** Search key. */
AKEYCODE_SEARCH = 84,
/** Play/Pause media key. */
AKEYCODE_MEDIA_PLAY_PAUSE = 85,
/** Stop media key. */
AKEYCODE_MEDIA_STOP = 86,
/** Play Next media key. */
AKEYCODE_MEDIA_NEXT = 87,
/** Play Previous media key. */
AKEYCODE_MEDIA_PREVIOUS = 88,
/** Rewind media key. */
AKEYCODE_MEDIA_REWIND = 89,
/** Fast Forward media key. */
AKEYCODE_MEDIA_FAST_FORWARD = 90,
/** Mute key.
* Mutes the microphone, unlike {@link AKEYCODE_VOLUME_MUTE}. */
AKEYCODE_MUTE = 91,
/** Page Up key. */
AKEYCODE_PAGE_UP = 92,
/** Page Down key. */
AKEYCODE_PAGE_DOWN = 93,
/** Picture Symbols modifier key.
* Used to switch symbol sets (Emoji, Kao-moji). */
AKEYCODE_PICTSYMBOLS = 94,
/** Switch Charset modifier key.
* Used to switch character sets (Kanji, Katakana). */
AKEYCODE_SWITCH_CHARSET = 95,
/** A Button key.
* On a game controller, the A button should be either the button labeled A
* or the first button on the bottom row of controller buttons. */
AKEYCODE_BUTTON_A = 96,
/** B Button key.
* On a game controller, the B button should be either the button labeled B
* or the second button on the bottom row of controller buttons. */
AKEYCODE_BUTTON_B = 97,
/** C Button key.
* On a game controller, the C button should be either the button labeled C
* or the third button on the bottom row of controller buttons. */
AKEYCODE_BUTTON_C = 98,
/** X Button key.
* On a game controller, the X button should be either the button labeled X
* or the first button on the upper row of controller buttons. */
AKEYCODE_BUTTON_X = 99,
/** Y Button key.
* On a game controller, the Y button should be either the button labeled Y
* or the second button on the upper row of controller buttons. */
AKEYCODE_BUTTON_Y = 100,
/** Z Button key.
* On a game controller, the Z button should be either the button labeled Z
* or the third button on the upper row of controller buttons. */
AKEYCODE_BUTTON_Z = 101,
/** L1 Button key.
* On a game controller, the L1 button should be either the button labeled L1 (or L)
* or the top left trigger button. */
AKEYCODE_BUTTON_L1 = 102,
/** R1 Button key.
* On a game controller, the R1 button should be either the button labeled R1 (or R)
* or the top right trigger button. */
AKEYCODE_BUTTON_R1 = 103,
/** L2 Button key.
* On a game controller, the L2 button should be either the button labeled L2
* or the bottom left trigger button. */
AKEYCODE_BUTTON_L2 = 104,
/** R2 Button key.
* On a game controller, the R2 button should be either the button labeled R2
* or the bottom right trigger button. */
AKEYCODE_BUTTON_R2 = 105,
/** Left Thumb Button key.
* On a game controller, the left thumb button indicates that the left (or only)
* joystick is pressed. */
AKEYCODE_BUTTON_THUMBL = 106,
/** Right Thumb Button key.
* On a game controller, the right thumb button indicates that the right
* joystick is pressed. */
AKEYCODE_BUTTON_THUMBR = 107,
/** Start Button key.
* On a game controller, the button labeled Start. */
AKEYCODE_BUTTON_START = 108,
/** Select Button key.
* On a game controller, the button labeled Select. */
AKEYCODE_BUTTON_SELECT = 109,
/** Mode Button key.
* On a game controller, the button labeled Mode. */
AKEYCODE_BUTTON_MODE = 110,
/** Escape key. */
AKEYCODE_ESCAPE = 111,
/** Forward Delete key.
* Deletes characters ahead of the insertion point, unlike {@link AKEYCODE_DEL}. */
AKEYCODE_FORWARD_DEL = 112,
/** Left Control modifier key. */
AKEYCODE_CTRL_LEFT = 113,
/** Right Control modifier key. */
AKEYCODE_CTRL_RIGHT = 114,
/** Caps Lock key. */
AKEYCODE_CAPS_LOCK = 115,
/** Scroll Lock key. */
AKEYCODE_SCROLL_LOCK = 116,
/** Left Meta modifier key. */
AKEYCODE_META_LEFT = 117,
/** Right Meta modifier key. */
AKEYCODE_META_RIGHT = 118,
/** Function modifier key. */
AKEYCODE_FUNCTION = 119,
/** System Request / Print Screen key. */
AKEYCODE_SYSRQ = 120,
/** Break / Pause key. */
AKEYCODE_BREAK = 121,
/** Home Movement key.
* Used for scrolling or moving the cursor around to the start of a line
* or to the top of a list. */
AKEYCODE_MOVE_HOME = 122,
/** End Movement key.
* Used for scrolling or moving the cursor around to the end of a line
* or to the bottom of a list. */
AKEYCODE_MOVE_END = 123,
/** Insert key.
* Toggles insert / overwrite edit mode. */
AKEYCODE_INSERT = 124,
/** Forward key.
* Navigates forward in the history stack. Complement of {@link AKEYCODE_BACK}. */
AKEYCODE_FORWARD = 125,
/** Play media key. */
AKEYCODE_MEDIA_PLAY = 126,
/** Pause media key. */
AKEYCODE_MEDIA_PAUSE = 127,
/** Close media key.
* May be used to close a CD tray, for example. */
AKEYCODE_MEDIA_CLOSE = 128,
/** Eject media key.
* May be used to eject a CD tray, for example. */
AKEYCODE_MEDIA_EJECT = 129,
/** Record media key. */
AKEYCODE_MEDIA_RECORD = 130,
/** F1 key. */
AKEYCODE_F1 = 131,
/** F2 key. */
AKEYCODE_F2 = 132,
/** F3 key. */
AKEYCODE_F3 = 133,
/** F4 key. */
AKEYCODE_F4 = 134,
/** F5 key. */
AKEYCODE_F5 = 135,
/** F6 key. */
AKEYCODE_F6 = 136,
/** F7 key. */
AKEYCODE_F7 = 137,
/** F8 key. */
AKEYCODE_F8 = 138,
/** F9 key. */
AKEYCODE_F9 = 139,
/** F10 key. */
AKEYCODE_F10 = 140,
/** F11 key. */
AKEYCODE_F11 = 141,
/** F12 key. */
AKEYCODE_F12 = 142,
/** Num Lock key.
* This is the Num Lock key; it is different from {@link AKEYCODE_NUM}.
* This key alters the behavior of other keys on the numeric keypad. */
AKEYCODE_NUM_LOCK = 143,
/** Numeric keypad '0' key. */
AKEYCODE_NUMPAD_0 = 144,
/** Numeric keypad '1' key. */
AKEYCODE_NUMPAD_1 = 145,
/** Numeric keypad '2' key. */
AKEYCODE_NUMPAD_2 = 146,
/** Numeric keypad '3' key. */
AKEYCODE_NUMPAD_3 = 147,
/** Numeric keypad '4' key. */
AKEYCODE_NUMPAD_4 = 148,
/** Numeric keypad '5' key. */
AKEYCODE_NUMPAD_5 = 149,
/** Numeric keypad '6' key. */
AKEYCODE_NUMPAD_6 = 150,
/** Numeric keypad '7' key. */
AKEYCODE_NUMPAD_7 = 151,
/** Numeric keypad '8' key. */
AKEYCODE_NUMPAD_8 = 152,
/** Numeric keypad '9' key. */
AKEYCODE_NUMPAD_9 = 153,
/** Numeric keypad '/' key (for division). */
AKEYCODE_NUMPAD_DIVIDE = 154,
/** Numeric keypad '*' key (for multiplication). */
AKEYCODE_NUMPAD_MULTIPLY = 155,
/** Numeric keypad '-' key (for subtraction). */
AKEYCODE_NUMPAD_SUBTRACT = 156,
/** Numeric keypad '+' key (for addition). */
AKEYCODE_NUMPAD_ADD = 157,
/** Numeric keypad '.' key (for decimals or digit grouping). */
AKEYCODE_NUMPAD_DOT = 158,
/** Numeric keypad ',' key (for decimals or digit grouping). */
AKEYCODE_NUMPAD_COMMA = 159,
/** Numeric keypad Enter key. */
AKEYCODE_NUMPAD_ENTER = 160,
/** Numeric keypad '=' key. */
AKEYCODE_NUMPAD_EQUALS = 161,
/** Numeric keypad '(' key. */
AKEYCODE_NUMPAD_LEFT_PAREN = 162,
/** Numeric keypad ')' key. */
AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
/** Volume Mute key.
* Mutes the speaker, unlike {@link AKEYCODE_MUTE}.
* This key should normally be implemented as a toggle such that the first press
* mutes the speaker and the second press restores the original volume. */
AKEYCODE_VOLUME_MUTE = 164,
/** Info key.
* Common on TV remotes to show additional information related to what is
* currently being viewed. */
AKEYCODE_INFO = 165,
/** Channel up key.
* On TV remotes, increments the television channel. */
AKEYCODE_CHANNEL_UP = 166,
/** Channel down key.
* On TV remotes, decrements the television channel. */
AKEYCODE_CHANNEL_DOWN = 167,
/** Zoom in key. */
AKEYCODE_ZOOM_IN = 168,
/** Zoom out key. */
AKEYCODE_ZOOM_OUT = 169,
/** TV key.
* On TV remotes, switches to viewing live TV. */
AKEYCODE_TV = 170,
/** Window key.
* On TV remotes, toggles picture-in-picture mode or other windowing functions. */
AKEYCODE_WINDOW = 171,
/** Guide key.
* On TV remotes, shows a programming guide. */
AKEYCODE_GUIDE = 172,
/** DVR key.
* On some TV remotes, switches to a DVR mode for recorded shows. */
AKEYCODE_DVR = 173,
/** Bookmark key.
* On some TV remotes, bookmarks content or web pages. */
AKEYCODE_BOOKMARK = 174,
/** Toggle captions key.
* Switches the mode for closed-captioning text, for example during television shows. */
AKEYCODE_CAPTIONS = 175,
/** Settings key.
* Starts the system settings activity. */
AKEYCODE_SETTINGS = 176,
/** TV power key.
* On TV remotes, toggles the power on a television screen. */
AKEYCODE_TV_POWER = 177,
/** TV input key.
* On TV remotes, switches the input on a television screen. */
AKEYCODE_TV_INPUT = 178,
/** Set-top-box power key.
* On TV remotes, toggles the power on an external Set-top-box. */
AKEYCODE_STB_POWER = 179,
/** Set-top-box input key.
* On TV remotes, switches the input mode on an external Set-top-box. */
AKEYCODE_STB_INPUT = 180,
/** A/V Receiver power key.
* On TV remotes, toggles the power on an external A/V Receiver. */
AKEYCODE_AVR_POWER = 181,
/** A/V Receiver input key.
* On TV remotes, switches the input mode on an external A/V Receiver. */
AKEYCODE_AVR_INPUT = 182,
/** Red "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
AKEYCODE_PROG_RED = 183,
/** Green "programmable" key.
* On TV remotes, actsas a contextual/programmable key. */
AKEYCODE_PROG_GREEN = 184,
/** Yellow "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
AKEYCODE_PROG_YELLOW = 185,
/** Blue "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
AKEYCODE_PROG_BLUE = 186,
/** App switch key.
* Should bring up the application switcher dialog. */
AKEYCODE_APP_SWITCH = 187,
/** Generic Game Pad Button #1.*/
AKEYCODE_BUTTON_1 = 188,
/** Generic Game Pad Button #2.*/
AKEYCODE_BUTTON_2 = 189,
/** Generic Game Pad Button #3.*/
AKEYCODE_BUTTON_3 = 190,
/** Generic Game Pad Button #4.*/
AKEYCODE_BUTTON_4 = 191,
/** Generic Game Pad Button #5.*/
AKEYCODE_BUTTON_5 = 192,
/** Generic Game Pad Button #6.*/
AKEYCODE_BUTTON_6 = 193,
/** Generic Game Pad Button #7.*/
AKEYCODE_BUTTON_7 = 194,
/** Generic Game Pad Button #8.*/
AKEYCODE_BUTTON_8 = 195,
/** Generic Game Pad Button #9.*/
AKEYCODE_BUTTON_9 = 196,
/** Generic Game Pad Button #10.*/
AKEYCODE_BUTTON_10 = 197,
/** Generic Game Pad Button #11.*/
AKEYCODE_BUTTON_11 = 198,
/** Generic Game Pad Button #12.*/
AKEYCODE_BUTTON_12 = 199,
/** Generic Game Pad Button #13.*/
AKEYCODE_BUTTON_13 = 200,
/** Generic Game Pad Button #14.*/
AKEYCODE_BUTTON_14 = 201,
/** Generic Game Pad Button #15.*/
AKEYCODE_BUTTON_15 = 202,
/** Generic Game Pad Button #16.*/
AKEYCODE_BUTTON_16 = 203,
/** Language Switch key.
* Toggles the current input language such as switching between English and Japanese on
* a QWERTY keyboard. On some devices, the same function may be performed by
* pressing Shift+Spacebar. */
AKEYCODE_LANGUAGE_SWITCH = 204,
/** Manner Mode key.
* Toggles silent or vibrate mode on and off to make the device behave more politely
* in certain settings such as on a crowded train. On some devices, the key may only
* operate when long-pressed. */
AKEYCODE_MANNER_MODE = 205,
/** 3D Mode key.
* Toggles the display between 2D and 3D mode. */
AKEYCODE_3D_MODE = 206,
/** Contacts special function key.
* Used to launch an address book application. */
AKEYCODE_CONTACTS = 207,
/** Calendar special function key.
* Used to launch a calendar application. */
AKEYCODE_CALENDAR = 208,
/** Music special function key.
* Used to launch a music player application. */
AKEYCODE_MUSIC = 209,
/** Calculator special function key.
* Used to launch a calculator application. */
AKEYCODE_CALCULATOR = 210,
/** Japanese full-width / half-width key. */
AKEYCODE_ZENKAKU_HANKAKU = 211,
/** Japanese alphanumeric key. */
AKEYCODE_EISU = 212,
/** Japanese non-conversion key. */
AKEYCODE_MUHENKAN = 213,
/** Japanese conversion key. */
AKEYCODE_HENKAN = 214,
/** Japanese katakana / hiragana key. */
AKEYCODE_KATAKANA_HIRAGANA = 215,
/** Japanese Yen key. */
AKEYCODE_YEN = 216,
/** Japanese Ro key. */
AKEYCODE_RO = 217,
/** Japanese kana key. */
AKEYCODE_KANA = 218,
/** Assist key.
* Launches the global assist activity. Not delivered to applications. */
AKEYCODE_ASSIST = 219,
/** Brightness Down key.
* Adjusts the screen brightness down. */
AKEYCODE_BRIGHTNESS_DOWN = 220,
/** Brightness Up key.
* Adjusts the screen brightness up. */
AKEYCODE_BRIGHTNESS_UP = 221,
/** Audio Track key.
* Switches the audio tracks. */
AKEYCODE_MEDIA_AUDIO_TRACK = 222,
/** Sleep key.
* Puts the device to sleep. Behaves somewhat like {@link AKEYCODE_POWER} but it
* has no effect if the device is already asleep. */
AKEYCODE_SLEEP = 223,
/** Wakeup key.
* Wakes up the device. Behaves somewhat like {@link AKEYCODE_POWER} but it
* has no effect if the device is already awake. */
AKEYCODE_WAKEUP = 224,
/** Pairing key.
* Initiates peripheral pairing mode. Useful for pairing remote control
* devices or game controllers, especially if no other input mode is
* available. */
AKEYCODE_PAIRING = 225,
/** Media Top Menu key.
* Goes to the top of media menu. */
AKEYCODE_MEDIA_TOP_MENU = 226,
/** '11' key. */
AKEYCODE_11 = 227,
/** '12' key. */
AKEYCODE_12 = 228,
/** Last Channel key.
* Goes to the last viewed channel. */
AKEYCODE_LAST_CHANNEL = 229,
/** TV data service key.
* Displays data services like weather, sports. */
AKEYCODE_TV_DATA_SERVICE = 230,
/** Voice Assist key.
* Launches the global voice assist activity. Not delivered to applications. */
AKEYCODE_VOICE_ASSIST = 231,
/** Radio key.
* Toggles TV service / Radio service. */
AKEYCODE_TV_RADIO_SERVICE = 232,
/** Teletext key.
* Displays Teletext service. */
AKEYCODE_TV_TELETEXT = 233,
/** Number entry key.
* Initiates to enter multi-digit channel nubmber when each digit key is assigned
* for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
* User Control Code. */
AKEYCODE_TV_NUMBER_ENTRY = 234,
/** Analog Terrestrial key.
* Switches to analog terrestrial broadcast service. */
AKEYCODE_TV_TERRESTRIAL_ANALOG = 235,
/** Digital Terrestrial key.
* Switches to digital terrestrial broadcast service. */
AKEYCODE_TV_TERRESTRIAL_DIGITAL = 236,
/** Satellite key.
* Switches to digital satellite broadcast service. */
AKEYCODE_TV_SATELLITE = 237,
/** BS key.
* Switches to BS digital satellite broadcasting service available in Japan. */
AKEYCODE_TV_SATELLITE_BS = 238,
/** CS key.
* Switches to CS digital satellite broadcasting service available in Japan. */
AKEYCODE_TV_SATELLITE_CS = 239,
/** BS/CS key.
* Toggles between BS and CS digital satellite services. */
AKEYCODE_TV_SATELLITE_SERVICE = 240,
/** Toggle Network key.
* Toggles selecting broacast services. */
AKEYCODE_TV_NETWORK = 241,
/** Antenna/Cable key.
* Toggles broadcast input source between antenna and cable. */
AKEYCODE_TV_ANTENNA_CABLE = 242,
/** HDMI #1 key.
* Switches to HDMI input #1. */
AKEYCODE_TV_INPUT_HDMI_1 = 243,
/** HDMI #2 key.
* Switches to HDMI input #2. */
AKEYCODE_TV_INPUT_HDMI_2 = 244,
/** HDMI #3 key.
* Switches to HDMI input #3. */
AKEYCODE_TV_INPUT_HDMI_3 = 245,
/** HDMI #4 key.
* Switches to HDMI input #4. */
AKEYCODE_TV_INPUT_HDMI_4 = 246,
/** Composite #1 key.
* Switches to composite video input #1. */
AKEYCODE_TV_INPUT_COMPOSITE_1 = 247,
/** Composite #2 key.
* Switches to composite video input #2. */
AKEYCODE_TV_INPUT_COMPOSITE_2 = 248,
/** Component #1 key.
* Switches to component video input #1. */
AKEYCODE_TV_INPUT_COMPONENT_1 = 249,
/** Component #2 key.
* Switches to component video input #2. */
AKEYCODE_TV_INPUT_COMPONENT_2 = 250,
/** VGA #1 key.
* Switches to VGA (analog RGB) input #1. */
AKEYCODE_TV_INPUT_VGA_1 = 251,
/** Audio description key.
* Toggles audio description off / on. */
AKEYCODE_TV_AUDIO_DESCRIPTION = 252,
/** Audio description mixing volume up key.
* Louden audio description volume as compared with normal audio volume. */
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253,
/** Audio description mixing volume down key.
* Lessen audio description volume as compared with normal audio volume. */
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254,
/** Zoom mode key.
* Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.) */
AKEYCODE_TV_ZOOM_MODE = 255,
/** Contents menu key.
* Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
* Code */
AKEYCODE_TV_CONTENTS_MENU = 256,
/** Media context menu key.
* Goes to the context menu of media contents. Corresponds to Media Context-sensitive
* Menu (0x11) of CEC User Control Code. */
AKEYCODE_TV_MEDIA_CONTEXT_MENU = 257,
/** Timer programming key.
* Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
* CEC User Control Code. */
AKEYCODE_TV_TIMER_PROGRAMMING = 258,
/** Help key. */
AKEYCODE_HELP = 259,
AKEYCODE_NAVIGATE_PREVIOUS = 260,
AKEYCODE_NAVIGATE_NEXT = 261,
AKEYCODE_NAVIGATE_IN = 262,
AKEYCODE_NAVIGATE_OUT = 263,
/** Primary stem key for Wear
* Main power/reset button on watch. */
AKEYCODE_STEM_PRIMARY = 264,
/** Generic stem key 1 for Wear */
AKEYCODE_STEM_1 = 265,
/** Generic stem key 2 for Wear */
AKEYCODE_STEM_2 = 266,
/** Generic stem key 3 for Wear */
AKEYCODE_STEM_3 = 267,
/** Directional Pad Up-Left */
AKEYCODE_DPAD_UP_LEFT = 268,
/** Directional Pad Down-Left */
AKEYCODE_DPAD_DOWN_LEFT = 269,
/** Directional Pad Up-Right */
AKEYCODE_DPAD_UP_RIGHT = 270,
/** Directional Pad Down-Right */
AKEYCODE_DPAD_DOWN_RIGHT = 271,
/** Skip forward media key */
AKEYCODE_MEDIA_SKIP_FORWARD = 272,
/** Skip backward media key */
AKEYCODE_MEDIA_SKIP_BACKWARD = 273,
/** Step forward media key.
* Steps media forward one from at a time. */
AKEYCODE_MEDIA_STEP_FORWARD = 274,
/** Step backward media key.
* Steps media backward one from at a time. */
AKEYCODE_MEDIA_STEP_BACKWARD = 275,
/** Put device to sleep unless a wakelock is held. */
AKEYCODE_SOFT_SLEEP = 276,
/** Cut key. */
AKEYCODE_CUT = 277,
/** Copy key. */
AKEYCODE_COPY = 278,
/** Paste key. */
AKEYCODE_PASTE = 279,
/** fingerprint navigation key, up. */
AKEYCODE_SYSTEM_NAVIGATION_UP = 280,
/** fingerprint navigation key, down. */
AKEYCODE_SYSTEM_NAVIGATION_DOWN = 281,
/** fingerprint navigation key, left. */
AKEYCODE_SYSTEM_NAVIGATION_LEFT = 282,
/** fingerprint navigation key, right. */
AKEYCODE_SYSTEM_NAVIGATION_RIGHT = 283,
/** all apps */
AKEYCODE_ALL_APPS = 284
};
#endif // _ANDROID_KEYCODES_H

View file

@ -1,248 +0,0 @@
#include <QApplication>
#include <QClipboard>
#include "controller.h"
#include "controlmsg.h"
#include "inputconvertgame.h"
#include "receiver.h"
#include "videosocket.h"
Controller::Controller(QString gameScript, QObject *parent) : QObject(parent)
{
m_receiver = new Receiver(this);
Q_ASSERT(m_receiver);
updateScript(gameScript);
}
Controller::~Controller() {}
void Controller::setControlSocket(QTcpSocket *controlSocket)
{
if (m_controlSocket || !controlSocket) {
return;
}
m_controlSocket = controlSocket;
m_receiver->setControlSocket(controlSocket);
}
void Controller::postControlMsg(ControlMsg *controlMsg)
{
if (controlMsg) {
QCoreApplication::postEvent(this, controlMsg);
}
}
void Controller::test(QRect rc)
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH);
controlMsg->setInjectTouchMsgData(static_cast<quint64>(POINTER_ID_MOUSE), AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_BUTTON_PRIMARY, rc, 1.0f);
postControlMsg(controlMsg);
}
void Controller::updateScript(QString gameScript)
{
if (m_inputConvert) {
delete m_inputConvert;
}
if (!gameScript.isEmpty()) {
InputConvertGame *convertgame = new InputConvertGame(this);
convertgame->loadKeyMap(gameScript);
m_inputConvert = convertgame;
} else {
m_inputConvert = new InputConvertNormal(this);
}
Q_ASSERT(m_inputConvert);
connect(m_inputConvert, &InputConvertBase::grabCursor, this, &Controller::grabCursor);
}
bool Controller::isCurrentCustomKeymap()
{
if (!m_inputConvert) {
return false;
}
return m_inputConvert->isCurrentCustomKeymap();
}
void Controller::onPostBackOrScreenOn()
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_BACK_OR_SCREEN_ON);
if (!controlMsg) {
return;
}
postControlMsg(controlMsg);
}
void Controller::onPostGoHome()
{
postKeyCodeClick(AKEYCODE_HOME);
}
void Controller::onPostGoMenu()
{
postKeyCodeClick(AKEYCODE_MENU);
}
void Controller::onPostGoBack()
{
postKeyCodeClick(AKEYCODE_BACK);
}
void Controller::onPostAppSwitch()
{
postKeyCodeClick(AKEYCODE_APP_SWITCH);
}
void Controller::onPostPower()
{
postKeyCodeClick(AKEYCODE_POWER);
}
void Controller::onPostVolumeUp()
{
postKeyCodeClick(AKEYCODE_VOLUME_UP);
}
void Controller::onPostVolumeDown()
{
postKeyCodeClick(AKEYCODE_VOLUME_DOWN);
}
void Controller::onCopy()
{
postKeyCodeClick(AKEYCODE_COPY);
}
void Controller::onCut()
{
postKeyCodeClick(AKEYCODE_CUT);
}
void Controller::onExpandNotificationPanel()
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_EXPAND_NOTIFICATION_PANEL);
if (!controlMsg) {
return;
}
postControlMsg(controlMsg);
}
void Controller::onCollapseNotificationPanel()
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_COLLAPSE_NOTIFICATION_PANEL);
if (!controlMsg) {
return;
}
postControlMsg(controlMsg);
}
void Controller::onRequestDeviceClipboard()
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_GET_CLIPBOARD);
if (!controlMsg) {
return;
}
postControlMsg(controlMsg);
}
void Controller::onSetDeviceClipboard(bool pause)
{
QClipboard *board = QApplication::clipboard();
QString text = board->text();
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_SET_CLIPBOARD);
if (!controlMsg) {
return;
}
controlMsg->setSetClipboardMsgData(text, pause);
postControlMsg(controlMsg);
}
void Controller::onClipboardPaste()
{
QClipboard *board = QApplication::clipboard();
QString text = board->text();
onPostTextInput(text);
}
void Controller::onPostTextInput(QString &text)
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TEXT);
if (!controlMsg) {
return;
}
controlMsg->setInjectTextMsgData(text);
postControlMsg(controlMsg);
}
void Controller::onSetScreenPowerMode(ControlMsg::ScreenPowerMode mode)
{
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_SET_SCREEN_POWER_MODE);
if (!controlMsg) {
return;
}
controlMsg->setSetScreenPowerModeData(mode);
postControlMsg(controlMsg);
}
void Controller::onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (m_inputConvert) {
m_inputConvert->mouseEvent(from, frameSize, showSize);
}
}
void Controller::onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (m_inputConvert) {
m_inputConvert->wheelEvent(from, frameSize, showSize);
}
}
void Controller::onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (m_inputConvert) {
m_inputConvert->keyEvent(from, frameSize, showSize);
}
}
bool Controller::event(QEvent *event)
{
if (event && static_cast<ControlMsg::Type>(event->type()) == ControlMsg::Control) {
ControlMsg *controlMsg = dynamic_cast<ControlMsg *>(event);
if (controlMsg) {
sendControl(controlMsg->serializeData());
}
return true;
}
return QObject::event(event);
}
bool Controller::sendControl(const QByteArray &buffer)
{
if (buffer.isEmpty()) {
return false;
}
qint32 len = 0;
if (m_controlSocket) {
len = static_cast<qint32>(m_controlSocket->write(buffer.data(), buffer.length()));
}
return len == buffer.length() ? true : false;
}
void Controller::postKeyCodeClick(AndroidKeycode keycode)
{
ControlMsg *controlEventDown = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
if (!controlEventDown) {
return;
}
controlEventDown->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_DOWN, keycode, 0, AMETA_NONE);
postControlMsg(controlEventDown);
ControlMsg *controlEventUp = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
if (!controlEventUp) {
return;
}
controlEventUp->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_UP, keycode, 0, AMETA_NONE);
postControlMsg(controlEventUp);
}

View file

@ -1,68 +0,0 @@
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include <QPointer>
#include "inputconvertbase.h"
class QTcpSocket;
class Receiver;
class InputConvertBase;
class Controller : public QObject
{
Q_OBJECT
public:
Controller(QString gameScript = "", QObject *parent = Q_NULLPTR);
virtual ~Controller();
void setControlSocket(QTcpSocket *controlSocket);
void postControlMsg(ControlMsg *controlMsg);
void test(QRect rc);
void updateScript(QString gameScript = "");
bool isCurrentCustomKeymap();
public slots:
void onPostGoBack();
void onPostGoHome();
void onPostGoMenu();
void onPostAppSwitch();
void onPostPower();
void onPostVolumeUp();
void onPostVolumeDown();
void onCopy();
void onCut();
void onExpandNotificationPanel();
void onCollapseNotificationPanel();
void onSetScreenPowerMode(ControlMsg::ScreenPowerMode mode);
// for input convert
void onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
void onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
void onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
// turn the screen on if it was off, press BACK otherwise
void onPostBackOrScreenOn();
void onRequestDeviceClipboard();
void onSetDeviceClipboard(bool pause = true);
void onClipboardPaste();
void onPostTextInput(QString &text);
signals:
void grabCursor(bool grab);
protected:
bool event(QEvent *event);
private:
bool sendControl(const QByteArray &buffer);
void postKeyCodeClick(AndroidKeycode keycode);
private:
QPointer<QTcpSocket> m_controlSocket;
QPointer<Receiver> m_receiver;
QPointer<InputConvertBase> m_inputConvert;
};
#endif // CONTROLLER_H

View file

@ -1,14 +0,0 @@
HEADERS += \
$$PWD/controller.h
SOURCES += \
$$PWD/controller.cpp
include ($$PWD/receiver/receiver.pri)
include ($$PWD/inputconvert/inputconvert.pri)
INCLUDEPATH += \
$$PWD/receiver \
$$PWD/inputconvert

View file

@ -1,148 +0,0 @@
#include <QDebug>
#include "bufferutil.h"
#include "controlmsg.h"
ControlMsg::ControlMsg(ControlMsgType controlMsgType) : QScrcpyEvent(Control)
{
m_data.type = controlMsgType;
}
ControlMsg::~ControlMsg()
{
if (CMT_SET_CLIPBOARD == m_data.type && Q_NULLPTR != m_data.setClipboard.text) {
delete m_data.setClipboard.text;
m_data.setClipboard.text = Q_NULLPTR;
} else if (CMT_INJECT_TEXT == m_data.type && Q_NULLPTR != m_data.injectText.text) {
delete m_data.injectText.text;
m_data.injectText.text = Q_NULLPTR;
}
}
void ControlMsg::setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, quint32 repeat, AndroidMetastate metastate)
{
m_data.injectKeycode.action = action;
m_data.injectKeycode.keycode = keycode;
m_data.injectKeycode.repeat = repeat;
m_data.injectKeycode.metastate = metastate;
}
void ControlMsg::setInjectTextMsgData(QString &text)
{
// write length (2 byte) + string (non nul-terminated)
if (CONTROL_MSG_INJECT_TEXT_MAX_LENGTH < text.length()) {
// injecting a text takes time, so limit the text length
text = text.left(CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
}
QByteArray tmp = text.toUtf8();
m_data.injectText.text = new char[tmp.length() + 1];
memcpy(m_data.injectText.text, tmp.data(), tmp.length());
m_data.injectText.text[tmp.length()] = '\0';
}
void ControlMsg::setInjectTouchMsgData(quint64 id, AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position, float pressure)
{
m_data.injectTouch.id = id;
m_data.injectTouch.action = action;
m_data.injectTouch.buttons = buttons;
m_data.injectTouch.position = position;
m_data.injectTouch.pressure = pressure;
}
void ControlMsg::setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll)
{
m_data.injectScroll.position = position;
m_data.injectScroll.hScroll = hScroll;
m_data.injectScroll.vScroll = vScroll;
}
void ControlMsg::setSetClipboardMsgData(QString &text, bool paste)
{
if (text.isEmpty()) {
return;
}
if (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH < text.length()) {
text = text.left(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
}
QByteArray tmp = text.toUtf8();
m_data.setClipboard.text = new char[tmp.length() + 1];
memcpy(m_data.setClipboard.text, tmp.data(), tmp.length());
m_data.setClipboard.text[tmp.length()] = '\0';
m_data.setClipboard.paste = paste;
}
void ControlMsg::setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode)
{
m_data.setScreenPowerMode.mode = mode;
}
void ControlMsg::writePosition(QBuffer &buffer, const QRect &value)
{
BufferUtil::write32(buffer, value.left());
BufferUtil::write32(buffer, value.top());
BufferUtil::write16(buffer, value.width());
BufferUtil::write16(buffer, value.height());
}
quint16 ControlMsg::toFixedPoint16(float f)
{
Q_ASSERT(f >= 0.0f && f <= 1.0f);
quint32 u = f * 0x1p16f; // 2^16
if (u >= 0xffff) {
u = 0xffff;
}
return (quint16)u;
}
QByteArray ControlMsg::serializeData()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QBuffer::WriteOnly);
buffer.putChar(m_data.type);
switch (m_data.type) {
case CMT_INJECT_KEYCODE:
buffer.putChar(m_data.injectKeycode.action);
BufferUtil::write32(buffer, m_data.injectKeycode.keycode);
BufferUtil::write32(buffer, m_data.injectKeycode.repeat);
BufferUtil::write32(buffer, m_data.injectKeycode.metastate);
break;
case CMT_INJECT_TEXT:
BufferUtil::write32(buffer, static_cast<quint32>(strlen(m_data.injectText.text)));
buffer.write(m_data.injectText.text, strlen(m_data.injectText.text));
break;
case CMT_INJECT_TOUCH: {
buffer.putChar(m_data.injectTouch.action);
BufferUtil::write64(buffer, m_data.injectTouch.id);
writePosition(buffer, m_data.injectTouch.position);
quint16 pressure = toFixedPoint16(m_data.injectTouch.pressure);
BufferUtil::write16(buffer, pressure);
BufferUtil::write32(buffer, m_data.injectTouch.buttons);
} break;
case CMT_INJECT_SCROLL:
writePosition(buffer, m_data.injectScroll.position);
BufferUtil::write32(buffer, m_data.injectScroll.hScroll);
BufferUtil::write32(buffer, m_data.injectScroll.vScroll);
break;
case CMT_SET_CLIPBOARD:
buffer.putChar(!!m_data.setClipboard.paste);
BufferUtil::write32(buffer, static_cast<quint32>(strlen(m_data.setClipboard.text)));
buffer.write(m_data.setClipboard.text, strlen(m_data.setClipboard.text));
break;
case CMT_SET_SCREEN_POWER_MODE:
buffer.putChar(m_data.setScreenPowerMode.mode);
break;
case CMT_BACK_OR_SCREEN_ON:
case CMT_EXPAND_NOTIFICATION_PANEL:
case CMT_COLLAPSE_NOTIFICATION_PANEL:
case CMT_GET_CLIPBOARD:
break;
default:
qDebug() << "Unknown event type:" << m_data.type;
break;
}
buffer.close();
return byteArray;
}

View file

@ -1,115 +0,0 @@
#ifndef CONTROLMSG_H
#define CONTROLMSG_H
#include <QBuffer>
#include <QRect>
#include <QString>
#include "input.h"
#include "keycodes.h"
#include "qscrcpyevent.h"
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH \
(CONTROL_MSG_MAX_SIZE - 6)
#define POINTER_ID_MOUSE static_cast<quint64>(-1)
// ControlMsg
class ControlMsg : public QScrcpyEvent
{
public:
enum ControlMsgType
{
CMT_NULL = -1,
CMT_INJECT_KEYCODE = 0,
CMT_INJECT_TEXT,
CMT_INJECT_TOUCH,
CMT_INJECT_SCROLL,
CMT_BACK_OR_SCREEN_ON,
CMT_EXPAND_NOTIFICATION_PANEL,
CMT_COLLAPSE_NOTIFICATION_PANEL,
CMT_GET_CLIPBOARD,
CMT_SET_CLIPBOARD,
CMT_SET_SCREEN_POWER_MODE
};
enum ScreenPowerMode
{
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
SPM_OFF = 0,
SPM_NORMAL = 2,
};
ControlMsg(ControlMsgType controlMsgType);
virtual ~ControlMsg();
void setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, quint32 repeat, AndroidMetastate metastate);
void setInjectTextMsgData(QString &text);
// id 代表一个触摸点最多支持10个触摸点[0,9]
// action 只能是AMOTION_EVENT_ACTION_DOWNAMOTION_EVENT_ACTION_UPAMOTION_EVENT_ACTION_MOVE
// position action动作对应的位置
void setInjectTouchMsgData(quint64 id, AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position, float pressure);
void setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll);
void setSetClipboardMsgData(QString &text, bool paste);
void setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode);
QByteArray serializeData();
private:
void writePosition(QBuffer &buffer, const QRect &value);
quint16 toFixedPoint16(float f);
private:
struct ControlMsgData
{
ControlMsgType type = CMT_NULL;
union
{
struct
{
AndroidKeyeventAction action;
AndroidKeycode keycode;
quint32 repeat;
AndroidMetastate metastate;
} injectKeycode;
struct
{
char *text = Q_NULLPTR;
} injectText;
struct
{
quint64 id;
AndroidMotioneventAction action;
AndroidMotioneventButtons buttons;
QRect position;
float pressure;
} injectTouch;
struct
{
QRect position;
qint32 hScroll;
qint32 vScroll;
} injectScroll;
struct
{
char *text = Q_NULLPTR;
bool paste = true;
} setClipboard;
struct
{
ScreenPowerMode mode;
} setScreenPowerMode;
};
ControlMsgData() {}
~ControlMsgData() {}
};
ControlMsgData m_data;
};
#endif // CONTROLMSG_H

View file

@ -1,17 +0,0 @@
HEADERS += \
$$PWD/inputconvertbase.h \
$$PWD/inputconvertgame.h \
$$PWD/inputconvertnormal.h \
$$PWD/controlmsg.h
SOURCES += \
$$PWD/inputconvertbase.cpp \
$$PWD/inputconvertgame.cpp \
$$PWD/inputconvertnormal.cpp \
$$PWD/controlmsg.cpp
include ($$PWD/keymap/keymap.pri)
INCLUDEPATH += \
$$PWD/keymap

View file

@ -1,16 +0,0 @@
#include "inputconvertbase.h"
#include "controller.h"
InputConvertBase::InputConvertBase(Controller *controller) : QObject(controller), m_controller(controller)
{
Q_ASSERT(controller);
}
InputConvertBase::~InputConvertBase() {}
void InputConvertBase::sendControlMsg(ControlMsg *msg)
{
if (msg && m_controller) {
m_controller->postControlMsg(msg);
}
}

View file

@ -1,41 +0,0 @@
#ifndef INPUTCONVERTBASE_H
#define INPUTCONVERTBASE_H
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPointer>
#include <QWheelEvent>
#include "controlmsg.h"
class Controller;
class InputConvertBase : public QObject
{
Q_OBJECT
public:
InputConvertBase(Controller *controller);
virtual ~InputConvertBase();
// the frame size may be different from the real device size, so we need the size
// to which the absolute position apply, to scale it accordingly
virtual void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) = 0;
virtual void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) = 0;
virtual void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) = 0;
virtual bool isCurrentCustomKeymap()
{
return false;
}
signals:
void grabCursor(bool grab);
protected:
void sendControlMsg(ControlMsg *msg);
QPointer<Controller> m_controller;
// Qt reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned m_repeat = 0;
};
#endif // INPUTCONVERTBASE_H

View file

@ -1,544 +0,0 @@
#include <QDebug>
#include <QCursor>
#include <QGuiApplication>
#include <QTimer>
#include "inputconvertgame.h"
#define CURSOR_POS_CHECK 50
InputConvertGame::InputConvertGame(Controller *controller) : InputConvertNormal(controller) {}
InputConvertGame::~InputConvertGame() {}
void InputConvertGame::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
// 处理开关按键
if (m_keyMap.isSwitchOnKeyboard() == false && m_keyMap.getSwitchKey() == static_cast<int>(from->button())) {
if (from->type() != QEvent::MouseButtonPress) {
return;
}
if (!switchGameMap()) {
m_needBackMouseMove = false;
}
return;
}
if (!m_needBackMouseMove && m_gameMap) {
updateSize(frameSize, showSize);
// mouse move
if (m_keyMap.isValidMouseMoveMap()) {
if (processMouseMove(from)) {
return;
}
}
// mouse click
if (processMouseClick(from)) {
return;
}
}
InputConvertNormal::mouseEvent(from, frameSize, showSize);
}
void InputConvertGame::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (m_gameMap) {
updateSize(frameSize, showSize);
} else {
InputConvertNormal::wheelEvent(from, frameSize, showSize);
}
}
void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
// 处理开关按键
if (m_keyMap.isSwitchOnKeyboard() && m_keyMap.getSwitchKey() == from->key()) {
if (QEvent::KeyPress != from->type()) {
return;
}
if (!switchGameMap()) {
m_needBackMouseMove = false;
}
return;
}
const KeyMap::KeyMapNode &node = m_keyMap.getKeyMapNodeKey(from->key());
// 处理特殊按键:可以释放出鼠标的按键
if (m_needBackMouseMove && KeyMap::KMT_CLICK == node.type && node.data.click.switchMap) {
updateSize(frameSize, showSize);
// Qt::Key_Tab Qt::Key_M for PUBG mobile
processKeyClick(node.data.click.keyNode.pos, false, node.data.click.switchMap, from);
return;
}
if (m_gameMap) {
updateSize(frameSize, showSize);
if (!from || from->isAutoRepeat()) {
return;
}
// small eyes
if (m_keyMap.isValidMouseMoveMap() && from->key() == m_keyMap.getMouseMoveMap().data.mouseMove.smallEyes.key) {
m_ctrlMouseMove.smallEyes = (QEvent::KeyPress == from->type());
if (QEvent::KeyPress == from->type()) {
m_processMouseMove = false;
int delay = 30;
QTimer::singleShot(delay, this, [this]() { mouseMoveStopTouch(); });
QTimer::singleShot(delay * 2, this, [this]() {
mouseMoveStartTouch(nullptr);
m_processMouseMove = true;
});
stopMouseMoveTimer();
} else {
mouseMoveStopTouch();
mouseMoveStartTouch(nullptr);
}
return;
}
switch (node.type) {
// 处理方向盘
case KeyMap::KMT_STEER_WHEEL:
processSteerWheel(node, from);
return;
// 处理普通按键
case KeyMap::KMT_CLICK:
processKeyClick(node.data.click.keyNode.pos, false, node.data.click.switchMap, from);
return;
case KeyMap::KMT_CLICK_TWICE:
processKeyClick(node.data.clickTwice.keyNode.pos, true, false, from);
return;
case KeyMap::KMT_CLICK_MULTI:
processKeyClickMulti(node.data.clickMulti.keyNode.delayClickNodes, node.data.clickMulti.keyNode.delayClickNodesCount, from);
return;
case KeyMap::KMT_DRAG:
processKeyDrag(node.data.drag.keyNode.pos, node.data.drag.keyNode.extendPos, from);
return;
default:
break;
}
} else {
InputConvertNormal::keyEvent(from, frameSize, showSize);
}
}
bool InputConvertGame::isCurrentCustomKeymap()
{
return m_gameMap;
}
void InputConvertGame::loadKeyMap(const QString &json)
{
m_keyMap.loadKeyMap(json);
}
void InputConvertGame::updateSize(const QSize &frameSize, const QSize &showSize)
{
if (showSize != m_showSize) {
if (m_gameMap && m_keyMap.isValidMouseMoveMap()) {
// show size change, resize grab cursor
emit grabCursor(true);
}
}
m_frameSize = frameSize;
m_showSize = showSize;
}
void InputConvertGame::sendTouchDownEvent(int id, QPointF pos)
{
sendTouchEvent(id, pos, AMOTION_EVENT_ACTION_DOWN);
}
void InputConvertGame::sendTouchMoveEvent(int id, QPointF pos)
{
sendTouchEvent(id, pos, AMOTION_EVENT_ACTION_MOVE);
}
void InputConvertGame::sendTouchUpEvent(int id, QPointF pos)
{
sendTouchEvent(id, pos, AMOTION_EVENT_ACTION_UP);
}
void InputConvertGame::sendTouchEvent(int id, QPointF pos, AndroidMotioneventAction action)
{
if (0 > id || MULTI_TOUCH_MAX_NUM - 1 < id) {
Q_ASSERT(0);
return;
}
//qDebug() << "id:" << id << " pos:" << pos << " action" << action;
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH);
if (!controlMsg) {
return;
}
QPoint absolutePos = calcFrameAbsolutePos(pos).toPoint();
static QPoint lastAbsolutePos = absolutePos;
if (AMOTION_EVENT_ACTION_MOVE == action && lastAbsolutePos == absolutePos) {
delete controlMsg;
return;
}
lastAbsolutePos = absolutePos;
controlMsg->setInjectTouchMsgData(static_cast<quint64>(id), action,
static_cast<AndroidMotioneventButtons>(0),
QRect(absolutePos, m_frameSize),
AMOTION_EVENT_ACTION_DOWN == action? 1.0f : 0.0f);
sendControlMsg(controlMsg);
}
QPointF InputConvertGame::calcFrameAbsolutePos(QPointF relativePos)
{
QPointF absolutePos;
absolutePos.setX(m_frameSize.width() * relativePos.x());
absolutePos.setY(m_frameSize.height() * relativePos.y());
return absolutePos;
}
QPointF InputConvertGame::calcScreenAbsolutePos(QPointF relativePos)
{
QPointF absolutePos;
absolutePos.setX(m_showSize.width() * relativePos.x());
absolutePos.setY(m_showSize.height() * relativePos.y());
return absolutePos;
}
int InputConvertGame::attachTouchID(int key)
{
for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) {
if (0 == m_multiTouchID[i]) {
m_multiTouchID[i] = key;
return i;
}
}
return -1;
}
void InputConvertGame::detachTouchID(int key)
{
for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) {
if (key == m_multiTouchID[i]) {
m_multiTouchID[i] = 0;
return;
}
}
}
int InputConvertGame::getTouchID(int key)
{
for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) {
if (key == m_multiTouchID[i]) {
return i;
}
}
return -1;
}
// -------- steer wheel event --------
void InputConvertGame::processSteerWheel(const KeyMap::KeyMapNode &node, const QKeyEvent *from)
{
int key = from->key();
bool flag = from->type() == QEvent::KeyPress;
// identify keys
if (key == node.data.steerWheel.up.key) {
m_ctrlSteerWheel.pressedUp = flag;
} else if (key == node.data.steerWheel.right.key) {
m_ctrlSteerWheel.pressedRight = flag;
} else if (key == node.data.steerWheel.down.key) {
m_ctrlSteerWheel.pressedDown = flag;
} else { // left
m_ctrlSteerWheel.pressedLeft = flag;
}
// calc offset and pressed number
QPointF offset(0.0, 0.0);
int pressedNum = 0;
if (m_ctrlSteerWheel.pressedUp) {
++pressedNum;
offset.ry() -= node.data.steerWheel.up.extendOffset;
}
if (m_ctrlSteerWheel.pressedRight) {
++pressedNum;
offset.rx() += node.data.steerWheel.right.extendOffset;
}
if (m_ctrlSteerWheel.pressedDown) {
++pressedNum;
offset.ry() += node.data.steerWheel.down.extendOffset;
}
if (m_ctrlSteerWheel.pressedLeft) {
++pressedNum;
offset.rx() -= node.data.steerWheel.left.extendOffset;
}
// action
if (pressedNum == 0) {
// touch up release all
int id = getTouchID(m_ctrlSteerWheel.touchKey);
sendTouchUpEvent(id, node.data.steerWheel.centerPos + m_ctrlSteerWheel.lastOffset);
detachTouchID(m_ctrlSteerWheel.touchKey);
} else {
int id;
// first press, get key and touch down
if (pressedNum == 1 && flag) {
m_ctrlSteerWheel.touchKey = from->key();
id = attachTouchID(m_ctrlSteerWheel.touchKey);
sendTouchDownEvent(id, node.data.steerWheel.centerPos);
} else {
// jsut get touch id and move
id = getTouchID(m_ctrlSteerWheel.touchKey);
}
sendTouchMoveEvent(id, node.data.steerWheel.centerPos + offset);
}
m_ctrlSteerWheel.lastOffset = offset;
return;
}
// -------- key event --------
void InputConvertGame::processKeyClick(const QPointF &clickPos, bool clickTwice, bool switchMap, const QKeyEvent *from)
{
if (switchMap && QEvent::KeyRelease == from->type()) {
m_needBackMouseMove = !m_needBackMouseMove;
hideMouseCursor(!m_needBackMouseMove);
}
if (QEvent::KeyPress == from->type()) {
int id = attachTouchID(from->key());
sendTouchDownEvent(id, clickPos);
if (clickTwice) {
sendTouchUpEvent(getTouchID(from->key()), clickPos);
detachTouchID(from->key());
}
} else if (QEvent::KeyRelease == from->type()) {
if (clickTwice) {
int id = attachTouchID(from->key());
sendTouchDownEvent(id, clickPos);
}
sendTouchUpEvent(getTouchID(from->key()), clickPos);
detachTouchID(from->key());
}
}
void InputConvertGame::processKeyClickMulti(const KeyMap::DelayClickNode *nodes, const int count, const QKeyEvent *from)
{
if (QEvent::KeyPress != from->type()) {
return;
}
int key = from->key();
int delay = 0;
QPointF clickPos;
for (int i = 0; i < count; i++) {
delay += nodes[i].delay;
clickPos = nodes[i].pos;
QTimer::singleShot(delay, this, [this, key, clickPos]() {
int id = attachTouchID(key);
sendTouchDownEvent(id, clickPos);
});
// Don't up it too fast
delay += 20;
QTimer::singleShot(delay, this, [this, key, clickPos]() {
int id = getTouchID(key);
sendTouchUpEvent(id, clickPos);
detachTouchID(key);
});
}
}
void InputConvertGame::processKeyDrag(const QPointF &startPos, QPointF endPos, const QKeyEvent *from)
{
if (QEvent::KeyPress == from->type()) {
int id = attachTouchID(from->key());
sendTouchDownEvent(id, startPos);
sendTouchMoveEvent(id, endPos);
}
if (QEvent::KeyRelease == from->type()) {
int id = getTouchID(from->key());
sendTouchUpEvent(id, endPos);
detachTouchID(from->key());
}
}
// -------- mouse event --------
bool InputConvertGame::processMouseClick(const QMouseEvent *from)
{
const KeyMap::KeyMapNode &node = m_keyMap.getKeyMapNodeMouse(from->button());
if (KeyMap::KMT_INVALID == node.type) {
return false;
}
if (QEvent::MouseButtonPress == from->type() || QEvent::MouseButtonDblClick == from->type()) {
int id = attachTouchID(from->button());
sendTouchDownEvent(id, node.data.click.keyNode.pos);
return true;
}
if (QEvent::MouseButtonRelease == from->type()) {
int id = getTouchID(from->button());
sendTouchUpEvent(id, node.data.click.keyNode.pos);
detachTouchID(from->button());
return true;
}
return false;
}
bool InputConvertGame::processMouseMove(const QMouseEvent *from)
{
if (QEvent::MouseMove != from->type()) {
return false;
}
if (checkCursorPos(from)) {
m_ctrlMouseMove.lastPos = QPointF(0.0, 0.0);
return true;
}
if (!m_ctrlMouseMove.lastPos.isNull() && m_processMouseMove) {
QPointF distance_raw{from->localPos() - m_ctrlMouseMove.lastPos};
QPointF speedRatio {m_keyMap.getMouseMoveMap().data.mouseMove.speedRatio};
QPointF distance {distance_raw.x() / speedRatio.x(), distance_raw.y() / speedRatio.y()};
mouseMoveStartTouch(from);
startMouseMoveTimer();
m_ctrlMouseMove.lastConverPos.setX(m_ctrlMouseMove.lastConverPos.x() + distance.x() / m_showSize.width());
m_ctrlMouseMove.lastConverPos.setY(m_ctrlMouseMove.lastConverPos.y() + distance.y() / m_showSize.height());
if (m_ctrlMouseMove.lastConverPos.x() < 0.05 || m_ctrlMouseMove.lastConverPos.x() > 0.95 || m_ctrlMouseMove.lastConverPos.y() < 0.05
|| m_ctrlMouseMove.lastConverPos.y() > 0.95) {
if (m_ctrlMouseMove.smallEyes) {
m_processMouseMove = false;
int delay = 30;
QTimer::singleShot(delay, this, [this]() { mouseMoveStopTouch(); });
QTimer::singleShot(delay * 2, this, [this]() {
mouseMoveStartTouch(nullptr);
m_processMouseMove = true;
});
} else {
mouseMoveStopTouch();
mouseMoveStartTouch(from);
}
}
sendTouchMoveEvent(getTouchID(Qt::ExtraButton24), m_ctrlMouseMove.lastConverPos);
}
m_ctrlMouseMove.lastPos = from->localPos();
return true;
}
bool InputConvertGame::checkCursorPos(const QMouseEvent *from)
{
bool moveCursor = false;
QPoint pos = from->pos();
if (pos.x() < CURSOR_POS_CHECK) {
pos.setX(m_showSize.width() - CURSOR_POS_CHECK);
moveCursor = true;
} else if (pos.x() > m_showSize.width() - CURSOR_POS_CHECK) {
pos.setX(CURSOR_POS_CHECK);
moveCursor = true;
} else if (pos.y() < CURSOR_POS_CHECK) {
pos.setY(m_showSize.height() - CURSOR_POS_CHECK);
moveCursor = true;
} else if (pos.y() > m_showSize.height() - CURSOR_POS_CHECK) {
pos.setY(CURSOR_POS_CHECK);
moveCursor = true;
}
if (moveCursor) {
moveCursorTo(from, pos);
}
return moveCursor;
}
void InputConvertGame::moveCursorTo(const QMouseEvent *from, const QPoint &localPosPixel)
{
QPoint posOffset = from->pos() - localPosPixel;
QPoint globalPos = from->globalPos();
globalPos -= posOffset;
//qDebug()<<"move cursor to "<<globalPos<<" offset "<<posOffset;
QCursor::setPos(globalPos);
}
void InputConvertGame::mouseMoveStartTouch(const QMouseEvent *from)
{
Q_UNUSED(from)
if (!m_ctrlMouseMove.touching) {
QPointF mouseMoveStartPos
= m_ctrlMouseMove.smallEyes ? m_keyMap.getMouseMoveMap().data.mouseMove.smallEyes.pos : m_keyMap.getMouseMoveMap().data.mouseMove.startPos;
int id = attachTouchID(Qt::ExtraButton24);
sendTouchDownEvent(id, mouseMoveStartPos);
m_ctrlMouseMove.lastConverPos = mouseMoveStartPos;
m_ctrlMouseMove.touching = true;
}
}
void InputConvertGame::mouseMoveStopTouch()
{
if (m_ctrlMouseMove.touching) {
sendTouchUpEvent(getTouchID(Qt::ExtraButton24), m_ctrlMouseMove.lastConverPos);
detachTouchID(Qt::ExtraButton24);
m_ctrlMouseMove.touching = false;
}
}
void InputConvertGame::startMouseMoveTimer()
{
stopMouseMoveTimer();
m_ctrlMouseMove.timer = startTimer(500);
}
void InputConvertGame::stopMouseMoveTimer()
{
if (0 != m_ctrlMouseMove.timer) {
killTimer(m_ctrlMouseMove.timer);
m_ctrlMouseMove.timer = 0;
}
}
bool InputConvertGame::switchGameMap()
{
m_gameMap = !m_gameMap;
qInfo() << tr("current keymap mode: %1").arg(m_gameMap ? tr("custom") : tr("normal"));
if (!m_keyMap.isValidMouseMoveMap()) {
return m_gameMap;
}
// grab cursor and set cursor only mouse move map
emit grabCursor(m_gameMap);
hideMouseCursor(m_gameMap);
if (!m_gameMap) {
stopMouseMoveTimer();
mouseMoveStopTouch();
}
return m_gameMap;
}
void InputConvertGame::hideMouseCursor(bool hide)
{
if (hide) {
#ifdef QT_NO_DEBUG
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
#else
QGuiApplication::setOverrideCursor(QCursor(Qt::CrossCursor));
#endif
} else {
QGuiApplication::restoreOverrideCursor();
}
}
void InputConvertGame::timerEvent(QTimerEvent *event)
{
if (m_ctrlMouseMove.timer == event->timerId()) {
stopMouseMoveTimer();
mouseMoveStopTouch();
}
}

View file

@ -1,100 +0,0 @@
#ifndef INPUTCONVERTGAME_H
#define INPUTCONVERTGAME_H
#include <QPointF>
#include "inputconvertnormal.h"
#include "keymap.h"
#define MULTI_TOUCH_MAX_NUM 10
class InputConvertGame : public InputConvertNormal
{
Q_OBJECT
public:
InputConvertGame(Controller *controller);
virtual ~InputConvertGame();
virtual void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
virtual void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
virtual void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
virtual bool isCurrentCustomKeymap();
void loadKeyMap(const QString &json);
protected:
void updateSize(const QSize &frameSize, const QSize &showSize);
void sendTouchDownEvent(int id, QPointF pos);
void sendTouchMoveEvent(int id, QPointF pos);
void sendTouchUpEvent(int id, QPointF pos);
void sendTouchEvent(int id, QPointF pos, AndroidMotioneventAction action);
QPointF calcFrameAbsolutePos(QPointF relativePos);
QPointF calcScreenAbsolutePos(QPointF relativePos);
// multi touch id
int attachTouchID(int key);
void detachTouchID(int key);
int getTouchID(int key);
// steer wheel
void processSteerWheel(const KeyMap::KeyMapNode &node, const QKeyEvent *from);
// click
void processKeyClick(const QPointF &clickPos, bool clickTwice, bool switchMap, const QKeyEvent *from);
// click mutil
void processKeyClickMulti(const KeyMap::DelayClickNode *nodes, const int count, const QKeyEvent *from);
// drag
void processKeyDrag(const QPointF &startPos, QPointF endPos, const QKeyEvent *from);
// mouse
bool processMouseClick(const QMouseEvent *from);
bool processMouseMove(const QMouseEvent *from);
void moveCursorTo(const QMouseEvent *from, const QPoint &localPosPixel);
void mouseMoveStartTouch(const QMouseEvent *from);
void mouseMoveStopTouch();
void startMouseMoveTimer();
void stopMouseMoveTimer();
bool switchGameMap();
bool checkCursorPos(const QMouseEvent *from);
void hideMouseCursor(bool hide);
protected:
void timerEvent(QTimerEvent *event);
private:
QSize m_frameSize;
QSize m_showSize;
bool m_gameMap = false;
bool m_needBackMouseMove = false;
int m_multiTouchID[MULTI_TOUCH_MAX_NUM] = { 0 };
KeyMap m_keyMap;
bool m_processMouseMove = true;
// steer wheel
struct
{
// the first key pressed
int touchKey = Qt::Key_unknown;
bool pressedUp = false;
bool pressedDown = false;
bool pressedLeft = false;
bool pressedRight = false;
// for last up
QPointF lastOffset;
} m_ctrlSteerWheel;
// mouse move
struct
{
QPointF lastConverPos;
QPointF lastPos = { 0.0, 0.0 };
bool touching = false;
int timer = 0;
bool smallEyes = false;
} m_ctrlMouseMove;
};
#endif // INPUTCONVERTGAME_H

View file

@ -1,415 +0,0 @@
#include <cmath>
#include <QDebug>
#include "inputconvertnormal.h"
#include "controller.h"
InputConvertNormal::InputConvertNormal(Controller *controller) : InputConvertBase(controller) {}
InputConvertNormal::~InputConvertNormal() {}
void InputConvertNormal::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (!from) {
return;
}
// action
AndroidMotioneventAction action;
switch (from->type()) {
case QEvent::MouseButtonPress:
action = AMOTION_EVENT_ACTION_DOWN;
break;
case QEvent::MouseButtonRelease:
action = AMOTION_EVENT_ACTION_UP;
break;
case QEvent::MouseMove:
// only support left button drag
if (!(from->buttons() & Qt::LeftButton)) {
return;
}
action = AMOTION_EVENT_ACTION_MOVE;
break;
default:
return;
}
// pos
QPointF pos = from->localPos();
// convert pos
pos.setX(pos.x() * frameSize.width() / showSize.width());
pos.setY(pos.y() * frameSize.height() / showSize.height());
// set data
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH);
if (!controlMsg) {
return;
}
controlMsg->setInjectTouchMsgData(
static_cast<quint64>(POINTER_ID_MOUSE), action,
convertMouseButtons(from->buttons()),
QRect(pos.toPoint(), frameSize),
AMOTION_EVENT_ACTION_DOWN == action? 1.0f : 0.0f);
sendControlMsg(controlMsg);
}
void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
if (!from || from->angleDelta().isNull()) {
return;
}
// delta
qint32 hScroll = from->angleDelta().x() == 0 ? 0 : from->angleDelta().x() / abs(from->angleDelta().x()) * 2;
qint32 vScroll = from->angleDelta().y() == 0 ? 0 : from->angleDelta().y() / abs(from->angleDelta().y()) * 2;
// pos
QPointF pos = from->position();
// convert pos
pos.setX(pos.x() * frameSize.width() / showSize.width());
pos.setY(pos.y() * frameSize.height() / showSize.height());
// set data
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_SCROLL);
if (!controlMsg) {
return;
}
controlMsg->setInjectScrollMsgData(QRect(pos.toPoint(), frameSize), hScroll, vScroll);
sendControlMsg(controlMsg);
}
void InputConvertNormal::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize)
Q_UNUSED(showSize)
if (!from) {
return;
}
bool repeat = from->isAutoRepeat();
// action
AndroidKeyeventAction action;
switch (from->type()) {
case QEvent::KeyPress:
action = AKEY_EVENT_ACTION_DOWN;
break;
case QEvent::KeyRelease:
action = AKEY_EVENT_ACTION_UP;
break;
default:
return;
}
// key code
AndroidKeycode keyCode = convertKeyCode(from->key(), from->modifiers());
if (AKEYCODE_UNKNOWN == keyCode) {
return;
}
// set data
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
if (!controlMsg) {
return;
}
if (repeat) {
m_repeat++;
} else {
m_repeat = 0;
}
controlMsg->setInjectKeycodeMsgData(action, keyCode, m_repeat, convertMetastate(from->modifiers()));
sendControlMsg(controlMsg);
}
AndroidMotioneventButtons InputConvertNormal::convertMouseButtons(Qt::MouseButtons buttonState)
{
quint32 buttons = 0;
if (buttonState & Qt::LeftButton) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (buttonState & Qt::RightButton) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (buttonState & Qt::MidButton) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (buttonState & Qt::XButton1) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (buttonState & Qt::XButton2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return static_cast<AndroidMotioneventButtons>(buttons);
}
AndroidKeycode InputConvertNormal::convertKeyCode(int key, Qt::KeyboardModifiers modifiers)
{
AndroidKeycode keyCode = AKEYCODE_UNKNOWN;
// functional keys
switch (key) {
case Qt::Key_Return:
keyCode = AKEYCODE_ENTER;
break;
case Qt::Key_Enter:
keyCode = AKEYCODE_NUMPAD_ENTER;
break;
case Qt::Key_Escape:
keyCode = AKEYCODE_ESCAPE;
break;
case Qt::Key_Backspace:
keyCode = AKEYCODE_DEL;
break;
case Qt::Key_Delete:
keyCode = AKEYCODE_FORWARD_DEL;
break;
case Qt::Key_Tab:
keyCode = AKEYCODE_TAB;
break;
case Qt::Key_Home:
keyCode = AKEYCODE_MOVE_HOME;
break;
case Qt::Key_End:
keyCode = AKEYCODE_MOVE_END;
break;
case Qt::Key_PageUp:
keyCode = AKEYCODE_PAGE_UP;
break;
case Qt::Key_PageDown:
keyCode = AKEYCODE_PAGE_DOWN;
break;
case Qt::Key_Left:
keyCode = AKEYCODE_DPAD_LEFT;
break;
case Qt::Key_Right:
keyCode = AKEYCODE_DPAD_RIGHT;
break;
case Qt::Key_Up:
keyCode = AKEYCODE_DPAD_UP;
break;
case Qt::Key_Down:
keyCode = AKEYCODE_DPAD_DOWN;
break;
}
if (AKEYCODE_UNKNOWN != keyCode) {
return keyCode;
}
// if ALT and META are pressed, dont handle letters and space
if (modifiers & (Qt::AltModifier | Qt::MetaModifier)) {
return keyCode;
}
// character keys
switch (key) {
case Qt::Key_A:
keyCode = AKEYCODE_A;
break;
case Qt::Key_B:
keyCode = AKEYCODE_B;
break;
case Qt::Key_C:
keyCode = AKEYCODE_C;
break;
case Qt::Key_D:
keyCode = AKEYCODE_D;
break;
case Qt::Key_E:
keyCode = AKEYCODE_E;
break;
case Qt::Key_F:
keyCode = AKEYCODE_F;
break;
case Qt::Key_G:
keyCode = AKEYCODE_G;
break;
case Qt::Key_H:
keyCode = AKEYCODE_H;
break;
case Qt::Key_I:
keyCode = AKEYCODE_I;
break;
case Qt::Key_J:
keyCode = AKEYCODE_J;
break;
case Qt::Key_K:
keyCode = AKEYCODE_K;
break;
case Qt::Key_L:
keyCode = AKEYCODE_L;
break;
case Qt::Key_M:
keyCode = AKEYCODE_M;
break;
case Qt::Key_N:
keyCode = AKEYCODE_N;
break;
case Qt::Key_O:
keyCode = AKEYCODE_O;
break;
case Qt::Key_P:
keyCode = AKEYCODE_P;
break;
case Qt::Key_Q:
keyCode = AKEYCODE_Q;
break;
case Qt::Key_R:
keyCode = AKEYCODE_R;
break;
case Qt::Key_S:
keyCode = AKEYCODE_S;
break;
case Qt::Key_T:
keyCode = AKEYCODE_T;
break;
case Qt::Key_U:
keyCode = AKEYCODE_U;
break;
case Qt::Key_V:
keyCode = AKEYCODE_V;
break;
case Qt::Key_W:
keyCode = AKEYCODE_W;
break;
case Qt::Key_X:
keyCode = AKEYCODE_X;
break;
case Qt::Key_Y:
keyCode = AKEYCODE_Y;
break;
case Qt::Key_Z:
keyCode = AKEYCODE_Z;
break;
case Qt::Key_0:
keyCode = AKEYCODE_0;
break;
case Qt::Key_1:
case Qt::Key_Exclam: // !
keyCode = AKEYCODE_1;
break;
case Qt::Key_2:
keyCode = AKEYCODE_2;
break;
case Qt::Key_3:
keyCode = AKEYCODE_3;
break;
case Qt::Key_4:
case Qt::Key_Dollar: //$
keyCode = AKEYCODE_4;
break;
case Qt::Key_5:
case Qt::Key_Percent: // %
keyCode = AKEYCODE_5;
break;
case Qt::Key_6:
case Qt::Key_AsciiCircum: //^
keyCode = AKEYCODE_6;
break;
case Qt::Key_7:
case Qt::Key_Ampersand: //&
keyCode = AKEYCODE_7;
break;
case Qt::Key_8:
keyCode = AKEYCODE_8;
break;
case Qt::Key_9:
keyCode = AKEYCODE_9;
break;
case Qt::Key_Space:
keyCode = AKEYCODE_SPACE;
break;
case Qt::Key_Comma: //,
case Qt::Key_Less: //<
keyCode = AKEYCODE_COMMA;
break;
case Qt::Key_Period: //.
case Qt::Key_Greater: //>
keyCode = AKEYCODE_PERIOD;
break;
case Qt::Key_Minus: //-
case Qt::Key_Underscore: //_
keyCode = AKEYCODE_MINUS;
break;
case Qt::Key_Equal: //=
keyCode = AKEYCODE_EQUALS;
break;
case Qt::Key_BracketLeft: //[
case Qt::Key_BraceLeft: //{
keyCode = AKEYCODE_LEFT_BRACKET;
break;
case Qt::Key_BracketRight: //]
case Qt::Key_BraceRight: //}
keyCode = AKEYCODE_RIGHT_BRACKET;
break;
case Qt::Key_Backslash: // \ ????
case Qt::Key_Bar: //|
keyCode = AKEYCODE_BACKSLASH;
break;
case Qt::Key_Semicolon: //;
case Qt::Key_Colon: //:
keyCode = AKEYCODE_SEMICOLON;
break;
case Qt::Key_Apostrophe: //'
case Qt::Key_QuoteDbl: //"
keyCode = AKEYCODE_APOSTROPHE;
break;
case Qt::Key_Slash: // /
case Qt::Key_Question: //?
keyCode = AKEYCODE_SLASH;
break;
case Qt::Key_At: //@
keyCode = AKEYCODE_AT;
break;
case Qt::Key_Plus: //+
keyCode = AKEYCODE_PLUS;
break;
case Qt::Key_QuoteLeft: //`
case Qt::Key_AsciiTilde: //~
keyCode = AKEYCODE_GRAVE;
break;
case Qt::Key_NumberSign: //#
keyCode = AKEYCODE_POUND;
break;
case Qt::Key_ParenLeft: //(
keyCode = AKEYCODE_NUMPAD_LEFT_PAREN;
break;
case Qt::Key_ParenRight: //)
keyCode = AKEYCODE_NUMPAD_RIGHT_PAREN;
break;
case Qt::Key_Asterisk: //*
keyCode = AKEYCODE_STAR;
break;
}
return keyCode;
}
AndroidMetastate InputConvertNormal::convertMetastate(Qt::KeyboardModifiers modifiers)
{
int metastate = AMETA_NONE;
if (modifiers & Qt::ShiftModifier) {
metastate |= AMETA_SHIFT_ON;
}
if (modifiers & Qt::ControlModifier) {
metastate |= AMETA_CTRL_ON;
}
if (modifiers & Qt::AltModifier) {
metastate |= AMETA_ALT_ON;
}
if (modifiers & Qt::MetaModifier) {
metastate |= AMETA_META_ON;
}
/*
if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
*/
return static_cast<AndroidMetastate>(metastate);
}

View file

@ -1,23 +0,0 @@
#ifndef INPUTCONVERT_H
#define INPUTCONVERT_H
#include "inputconvertbase.h"
class InputConvertNormal : public InputConvertBase
{
Q_OBJECT
public:
InputConvertNormal(Controller *controller);
virtual ~InputConvertNormal();
virtual void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
virtual void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
virtual void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
private:
AndroidMotioneventButtons convertMouseButtons(Qt::MouseButtons buttonState);
AndroidKeycode convertKeyCode(int key, Qt::KeyboardModifiers modifiers);
AndroidMetastate convertMetastate(Qt::KeyboardModifiers modifiers);
};
#endif // INPUTCONVERT_H

View file

@ -1,533 +0,0 @@
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMetaEnum>
#include "keymap.h"
QString KeyMap::s_keyMapPath = "";
KeyMap::KeyMap(QObject *parent) : QObject(parent) {}
KeyMap::~KeyMap() {}
const QString &KeyMap::getKeyMapPath()
{
if (s_keyMapPath.isEmpty()) {
s_keyMapPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_KEYMAP_PATH"));
QFileInfo fileInfo(s_keyMapPath);
if (s_keyMapPath.isEmpty() || !fileInfo.isDir()) {
s_keyMapPath = QCoreApplication::applicationDirPath() + "/keymap";
}
}
return s_keyMapPath;
}
void KeyMap::loadKeyMap(const QString &json)
{
QString errorString;
QJsonParseError jsonError;
QJsonDocument jsonDoc;
QJsonObject rootObj;
QPair<ActionType, int> switchKey;
jsonDoc = QJsonDocument::fromJson(json.toUtf8(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
errorString = QString("json error: %1").arg(jsonError.errorString());
goto parseError;
}
// switchKey
rootObj = jsonDoc.object();
if (!checkItemString(rootObj, "switchKey")) {
errorString = QString("json error: no find switchKey");
goto parseError;
}
switchKey = getItemKey(rootObj, "switchKey");
if (switchKey.first == AT_INVALID) {
errorString = QString("json error: switchKey invalid");
goto parseError;
}
m_switchKey.type = switchKey.first;
m_switchKey.key = switchKey.second;
// mouseMoveMap
if (checkItemObject(rootObj, "mouseMoveMap")) {
QJsonObject mouseMoveMap = getItemObject(rootObj, "mouseMoveMap");
KeyMapNode keyMapNode;
keyMapNode.type = KMT_MOUSE_MOVE;
bool have_speedRatio = false;
// General speedRatio (for backwards compatibility)
if (checkItemDouble(mouseMoveMap, "speedRatio")) {
float ratio = static_cast<float>(getItemDouble(mouseMoveMap, "speedRatio"));
keyMapNode.data.mouseMove.speedRatio.setX(ratio);
keyMapNode.data.mouseMove.speedRatio.setY(ratio / 2.25f); // Phone screens are often FHD+
have_speedRatio = true;
}
// Individual X Ratio
if (checkItemDouble(mouseMoveMap, "speedRatioX")) {
keyMapNode.data.mouseMove.speedRatio.setX(static_cast<float>(getItemDouble(mouseMoveMap, "speedRatioX")));
have_speedRatio = true;
}
// Individual Y Ratio
if (checkItemDouble(mouseMoveMap, "speedRatioY")) {
keyMapNode.data.mouseMove.speedRatio.setY(static_cast<float>(getItemDouble(mouseMoveMap, "speedRatioY")));
have_speedRatio = true;
}
if (!have_speedRatio) {
errorString = QString("json error: speedRatio setting is missing in mouseMoveMap!");
goto parseError;
}
// Sanity check: No ratio must be lower than 0.001
if ( ( keyMapNode.data.mouseMove.speedRatio.x() < 0.001f ) || ( keyMapNode.data.mouseMove.speedRatio.x() < 0.001f ) ) {
errorString = QString("json error: Minimum speedRatio is 0.001");
goto parseError;
}
if (!checkItemObject(mouseMoveMap, "startPos")) {
errorString = QString("json error: mouseMoveMap on find startPos");
goto parseError;
}
QJsonObject startPos = mouseMoveMap.value("startPos").toObject();
if (checkItemDouble(startPos, "x")) {
keyMapNode.data.mouseMove.startPos.setX(getItemDouble(startPos, "x"));
}
if (checkItemDouble(startPos, "y")) {
keyMapNode.data.mouseMove.startPos.setY(getItemDouble(startPos, "y"));
}
// small eyes
if (checkItemObject(mouseMoveMap, "smallEyes")) {
QJsonObject smallEyes = mouseMoveMap.value("smallEyes").toObject();
if (!smallEyes.contains("type") || !smallEyes.value("type").isString()) {
errorString = QString("json error: smallEyes no find node type");
goto parseError;
}
// type just support KMT_CLICK
KeyMap::KeyMapType type = getItemKeyMapType(smallEyes, "type");
if (KeyMap::KMT_CLICK != type) {
errorString = QString("json error: smallEyes just support KMT_CLICK");
goto parseError;
}
// safe check
if (!checkForClick(smallEyes)) {
errorString = QString("json error: smallEyes node format error");
goto parseError;
}
QPair<ActionType, int> key = getItemKey(smallEyes, "key");
if (key.first == AT_INVALID) {
errorString = QString("json error: keyMapNodes node invalid key: %1").arg(smallEyes.value("key").toString());
goto parseError;
}
keyMapNode.data.mouseMove.smallEyes.type = key.first;
keyMapNode.data.mouseMove.smallEyes.key = key.second;
keyMapNode.data.mouseMove.smallEyes.pos = getItemPos(smallEyes, "pos");
}
m_idxMouseMove = m_keyMapNodes.size();
m_keyMapNodes.push_back(keyMapNode);
}
// keyMapNodes
if (rootObj.contains("keyMapNodes") && rootObj.value("keyMapNodes").isArray()) {
QJsonArray keyMapNodes = rootObj.value("keyMapNodes").toArray();
QJsonObject node;
int size = keyMapNodes.size();
for (int i = 0; i < size; i++) {
if (!keyMapNodes.at(i).isObject()) {
errorString = QString("json error: keyMapNodes node must be json object");
goto parseError;
}
node = keyMapNodes.at(i).toObject();
if (!node.contains("type") || !node.value("type").isString()) {
errorString = QString("json error: keyMapNodes no find node type");
goto parseError;
}
KeyMap::KeyMapType type = getItemKeyMapType(node, "type");
switch (type) {
case KeyMap::KMT_CLICK: {
// safe check
if (!checkForClick(node)) {
qWarning() << "json error: keyMapNodes node format error";
break;
}
QPair<ActionType, int> key = getItemKey(node, "key");
if (key.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
break;
}
KeyMapNode keyMapNode;
keyMapNode.type = type;
keyMapNode.data.click.keyNode.type = key.first;
keyMapNode.data.click.keyNode.key = key.second;
keyMapNode.data.click.keyNode.pos = getItemPos(node, "pos");
keyMapNode.data.click.switchMap = getItemBool(node, "switchMap");
m_keyMapNodes.push_back(keyMapNode);
} break;
case KeyMap::KMT_CLICK_TWICE: {
// safe check
if (!checkForClickTwice(node)) {
qWarning() << "json error: keyMapNodes node format error";
break;
}
QPair<ActionType, int> key = getItemKey(node, "key");
if (key.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
break;
}
KeyMapNode keyMapNode;
keyMapNode.type = type;
keyMapNode.data.click.keyNode.type = key.first;
keyMapNode.data.click.keyNode.key = key.second;
keyMapNode.data.click.keyNode.pos = getItemPos(node, "pos");
keyMapNode.data.click.switchMap = getItemBool(node, "switchMap");
m_keyMapNodes.push_back(keyMapNode);
} break;
case KeyMap::KMT_CLICK_MULTI: {
// safe check
if (!checkForClickMulti(node)) {
qWarning() << "json error: keyMapNodes node format error";
break;
}
QPair<ActionType, int> key = getItemKey(node, "key");
if (key.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
break;
}
KeyMapNode keyMapNode;
keyMapNode.type = type;
keyMapNode.data.clickMulti.keyNode.type = key.first;
keyMapNode.data.clickMulti.keyNode.key = key.second;
QJsonArray clickNodes = node.value("clickNodes").toArray();
QJsonObject clickNode;
keyMapNode.data.clickMulti.keyNode.delayClickNodesCount = 0;
for (int i = 0; i < clickNodes.size(); i++) {
if (i >= MAX_DELAY_CLICK_NODES) {
qInfo() << "clickNodes too much, up to " << MAX_DELAY_CLICK_NODES;
break;
}
clickNode = clickNodes.at(i).toObject();
DelayClickNode delayClickNode;
delayClickNode.delay = getItemDouble(clickNode, "delay");
delayClickNode.pos = getItemPos(clickNode, "pos");
keyMapNode.data.clickMulti.keyNode.delayClickNodes[i] = delayClickNode;
keyMapNode.data.clickMulti.keyNode.delayClickNodesCount++;
}
m_keyMapNodes.push_back(keyMapNode);
} break;
case KeyMap::KMT_STEER_WHEEL: {
// safe check
if (!checkForSteerWhell(node)) {
qWarning() << "json error: keyMapNodes node format error";
break;
}
QPair<ActionType, int> leftKey = getItemKey(node, "leftKey");
QPair<ActionType, int> rightKey = getItemKey(node, "rightKey");
QPair<ActionType, int> upKey = getItemKey(node, "upKey");
QPair<ActionType, int> downKey = getItemKey(node, "downKey");
if (leftKey.first == AT_INVALID || rightKey.first == AT_INVALID || upKey.first == AT_INVALID || downKey.first == AT_INVALID) {
if (leftKey.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("leftKey").toString();
}
if (rightKey.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("rightKey").toString();
}
if (upKey.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("upKey").toString();
}
if (downKey.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("downKey").toString();
}
break;
}
KeyMapNode keyMapNode;
keyMapNode.type = type;
keyMapNode.data.steerWheel.left = { leftKey.first, leftKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "leftOffset") };
keyMapNode.data.steerWheel.right = { rightKey.first, rightKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "rightOffset") };
keyMapNode.data.steerWheel.up = { upKey.first, upKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "upOffset") };
keyMapNode.data.steerWheel.down = { downKey.first, downKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "downOffset") };
keyMapNode.data.steerWheel.centerPos = getItemPos(node, "centerPos");
m_idxSteerWheel = m_keyMapNodes.size();
m_keyMapNodes.push_back(keyMapNode);
} break;
case KeyMap::KMT_DRAG: {
// safe check
if (!checkForDrag(node)) {
qWarning() << "json error: keyMapNodes node format error";
break;
}
QPair<ActionType, int> key = getItemKey(node, "key");
if (key.first == AT_INVALID) {
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
break;
}
KeyMapNode keyMapNode;
keyMapNode.type = type;
keyMapNode.data.drag.keyNode.type = key.first;
keyMapNode.data.drag.keyNode.key = key.second;
keyMapNode.data.drag.keyNode.pos = getItemPos(node, "startPos");
keyMapNode.data.drag.keyNode.extendPos = getItemPos(node, "endPos");
m_keyMapNodes.push_back(keyMapNode);
break;
}
default:
qWarning() << "json error: keyMapNodes invalid node type:" << node.value("type").toString();
break;
}
}
}
// this must be called after m_keyMapNodes is stable
makeReverseMap();
qInfo() << tr("Script updated, current keymap mode:normal, Press ~ key to switch keymap mode");
parseError:
if (!errorString.isEmpty()) {
qWarning() << errorString;
}
return;
}
const KeyMap::KeyMapNode &KeyMap::getKeyMapNode(int key)
{
auto p = m_rmapKey.value(key, &m_invalidNode);
if (p == &m_invalidNode) {
return *m_rmapMouse.value(key, &m_invalidNode);
}
return *p;
}
const KeyMap::KeyMapNode &KeyMap::getKeyMapNodeKey(int key)
{
return *m_rmapKey.value(key, &m_invalidNode);
}
const KeyMap::KeyMapNode &KeyMap::getKeyMapNodeMouse(int key)
{
return *m_rmapMouse.value(key, &m_invalidNode);
}
bool KeyMap::isSwitchOnKeyboard()
{
return m_switchKey.type == AT_KEY;
}
int KeyMap::getSwitchKey()
{
return m_switchKey.key;
}
const KeyMap::KeyMapNode &KeyMap::getMouseMoveMap()
{
return m_keyMapNodes[m_idxMouseMove];
}
bool KeyMap::isValidMouseMoveMap()
{
return m_idxMouseMove != -1;
}
bool KeyMap::isValidSteerWheelMap()
{
return m_idxSteerWheel != -1;
}
void KeyMap::makeReverseMap()
{
m_rmapKey.clear();
m_rmapMouse.clear();
for (int i = 0; i < m_keyMapNodes.size(); ++i) {
auto &node = m_keyMapNodes[i];
switch (node.type) {
case KMT_CLICK: {
QMultiHash<int, KeyMapNode *> &m = node.data.click.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
m.insert(node.data.click.keyNode.key, &node);
} break;
case KMT_CLICK_TWICE: {
QMultiHash<int, KeyMapNode *> &m = node.data.clickTwice.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
m.insert(node.data.clickTwice.keyNode.key, &node);
} break;
case KMT_CLICK_MULTI: {
QMultiHash<int, KeyMapNode *> &m = node.data.clickMulti.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
m.insert(node.data.clickMulti.keyNode.key, &node);
} break;
case KMT_STEER_WHEEL: {
QMultiHash<int, KeyMapNode *> &ml = node.data.steerWheel.left.type == AT_KEY ? m_rmapKey : m_rmapMouse;
ml.insert(node.data.steerWheel.left.key, &node);
QMultiHash<int, KeyMapNode *> &mr = node.data.steerWheel.right.type == AT_KEY ? m_rmapKey : m_rmapMouse;
mr.insert(node.data.steerWheel.right.key, &node);
QMultiHash<int, KeyMapNode *> &mu = node.data.steerWheel.up.type == AT_KEY ? m_rmapKey : m_rmapMouse;
mu.insert(node.data.steerWheel.up.key, &node);
QMultiHash<int, KeyMapNode *> &md = node.data.steerWheel.down.type == AT_KEY ? m_rmapKey : m_rmapMouse;
md.insert(node.data.steerWheel.down.key, &node);
} break;
case KMT_DRAG: {
QMultiHash<int, KeyMapNode *> &m = node.data.drag.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
m.insert(node.data.drag.keyNode.key, &node);
} break;
default:
break;
}
}
}
QString KeyMap::getItemString(const QJsonObject &node, const QString &name)
{
return node.value(name).toString();
}
double KeyMap::getItemDouble(const QJsonObject &node, const QString &name)
{
return node.value(name).toDouble();
}
bool KeyMap::getItemBool(const QJsonObject &node, const QString &name)
{
return node.value(name).toBool(false);
}
QJsonObject KeyMap::getItemObject(const QJsonObject &node, const QString &name)
{
return node.value(name).toObject();
}
QPointF KeyMap::getItemPos(const QJsonObject &node, const QString &name)
{
QJsonObject pos = node.value(name).toObject();
return QPointF(pos.value("x").toDouble(), pos.value("y").toDouble());
}
QPair<KeyMap::ActionType, int> KeyMap::getItemKey(const QJsonObject &node, const QString &name)
{
QString value = getItemString(node, name);
int key = m_metaEnumKey.keyToValue(value.toStdString().c_str());
int btn = m_metaEnumMouseButtons.keyToValue(value.toStdString().c_str());
if (key == -1 && btn == -1) {
return { AT_INVALID, -1 };
} else if (key != -1) {
return { AT_KEY, key };
} else {
return { AT_MOUSE, btn };
}
}
KeyMap::KeyMapType KeyMap::getItemKeyMapType(const QJsonObject &node, const QString &name)
{
QString value = getItemString(node, name);
return static_cast<KeyMap::KeyMapType>(m_metaEnumKeyMapType.keyToValue(value.toStdString().c_str()));
}
bool KeyMap::checkItemString(const QJsonObject &node, const QString &name)
{
return node.contains(name) && node.value(name).isString();
}
bool KeyMap::checkItemDouble(const QJsonObject &node, const QString &name)
{
return node.contains(name) && node.value(name).isDouble();
}
bool KeyMap::checkItemBool(const QJsonObject &node, const QString &name)
{
return node.contains(name) && node.value(name).isBool();
}
bool KeyMap::checkItemObject(const QJsonObject &node, const QString &name)
{
return node.contains(name) && node.value(name).isObject();
}
bool KeyMap::checkItemPos(const QJsonObject &node, const QString &name)
{
if (node.contains(name) && node.value(name).isObject()) {
QJsonObject pos = node.value(name).toObject();
return pos.contains("x") && pos.value("x").isDouble() && pos.contains("y") && pos.value("y").isDouble();
}
return false;
}
bool KeyMap::checkForClick(const QJsonObject &node)
{
return checkForClickTwice(node) && checkItemBool(node, "switchMap");
}
bool KeyMap::checkForClickMulti(const QJsonObject &node)
{
bool ret = true;
if (!node.contains("clickNodes") || !node.value("clickNodes").isArray()) {
qWarning("json error: no find clickNodes");
return false;
}
QJsonArray clickNodes = node.value("clickNodes").toArray();
QJsonObject clickNode;
int size = clickNodes.size();
if (0 == size) {
qWarning("json error: clickNodes is empty");
return false;
}
for (int i = 0; i < size; i++) {
if (!clickNodes.at(i).isObject()) {
qWarning("json error: clickNodes node must be json object");
ret = false;
break;
}
clickNode = clickNodes.at(i).toObject();
if (!checkForDelayClickNode(clickNode)) {
ret = false;
break;
}
}
return ret;
}
bool KeyMap::checkForDelayClickNode(const QJsonObject &node)
{
return checkItemPos(node, "pos") && checkItemDouble(node, "delay");
}
bool KeyMap::checkForClickTwice(const QJsonObject &node)
{
return checkItemString(node, "key") && checkItemPos(node, "pos");
}
bool KeyMap::checkForSteerWhell(const QJsonObject &node)
{
return checkItemString(node, "leftKey") && checkItemString(node, "rightKey") && checkItemString(node, "upKey") && checkItemString(node, "downKey")
&& checkItemDouble(node, "leftOffset") && checkItemDouble(node, "rightOffset") && checkItemDouble(node, "upOffset")
&& checkItemDouble(node, "downOffset") && checkItemPos(node, "centerPos");
}
bool KeyMap::checkForDrag(const QJsonObject &node)
{
return checkItemString(node, "key") && checkItemPos(node, "startPos") && checkItemPos(node, "endPos");
}

View file

@ -1,174 +0,0 @@
#ifndef KEYMAP_H
#define KEYMAP_H
#include <QJsonObject>
#include <QMetaEnum>
#include <QMultiHash>
#include <QObject>
#include <QPair>
#include <QPointF>
#include <QRectF>
#include <QVector>
#define MAX_DELAY_CLICK_NODES 50
class KeyMap : public QObject
{
Q_OBJECT
public:
enum KeyMapType
{
KMT_INVALID = -1,
KMT_CLICK = 0,
KMT_CLICK_TWICE,
KMT_CLICK_MULTI,
KMT_STEER_WHEEL,
KMT_DRAG,
KMT_MOUSE_MOVE
};
Q_ENUM(KeyMapType)
enum ActionType
{
AT_INVALID = -1,
AT_KEY = 0,
AT_MOUSE = 1,
};
Q_ENUM(ActionType)
struct DelayClickNode
{
int delay = 0;
QPointF pos = QPointF(0, 0);
};
struct KeyNode
{
ActionType type = AT_INVALID;
int key = Qt::Key_unknown;
QPointF pos = QPointF(0, 0); // normal key
QPointF extendPos = QPointF(0, 0); // for drag
double extendOffset = 0.0; // for steerWheel
DelayClickNode delayClickNodes[MAX_DELAY_CLICK_NODES]; // for multi clicks
int delayClickNodesCount = 0;
KeyNode(
ActionType type = AT_INVALID,
int key = Qt::Key_unknown,
QPointF pos = QPointF(0, 0),
QPointF extendPos = QPointF(0, 0),
double extendOffset = 0.0)
: type(type), key(key), pos(pos), extendPos(extendPos), extendOffset(extendOffset)
{
}
};
struct KeyMapNode
{
KeyMapType type = KMT_INVALID;
union DATA
{
struct
{
KeyNode keyNode;
bool switchMap = false;
} click;
struct
{
KeyNode keyNode;
} clickTwice;
struct
{
KeyNode keyNode;
} clickMulti;
struct
{
QPointF centerPos = { 0.0, 0.0 };
KeyNode left, right, up, down;
} steerWheel;
struct
{
KeyNode keyNode;
} drag;
struct
{
QPointF startPos = { 0.0, 0.0 };
QPointF speedRatio = { 1.0, 1.0 };
KeyNode smallEyes;
} mouseMove;
DATA() {}
~DATA() {}
} data;
KeyMapNode() {}
~KeyMapNode() {}
};
KeyMap(QObject *parent = Q_NULLPTR);
virtual ~KeyMap();
void loadKeyMap(const QString &json);
const KeyMap::KeyMapNode &getKeyMapNode(int key);
const KeyMap::KeyMapNode &getKeyMapNodeKey(int key);
const KeyMap::KeyMapNode &getKeyMapNodeMouse(int key);
bool isSwitchOnKeyboard();
int getSwitchKey();
bool isValidMouseMoveMap();
bool isValidSteerWheelMap();
const KeyMap::KeyMapNode &getMouseMoveMap();
static const QString &getKeyMapPath();
private:
// set up the reverse map from key/event event to keyMapNode
void makeReverseMap();
// safe check for base
bool checkItemString(const QJsonObject &node, const QString &name);
bool checkItemDouble(const QJsonObject &node, const QString &name);
bool checkItemBool(const QJsonObject &node, const QString &name);
bool checkItemObject(const QJsonObject &node, const QString &name);
bool checkItemPos(const QJsonObject &node, const QString &name);
// safe check for KeyMapNode
bool checkForClick(const QJsonObject &node);
bool checkForClickMulti(const QJsonObject &node);
bool checkForDelayClickNode(const QJsonObject &node);
bool checkForClickTwice(const QJsonObject &node);
bool checkForSteerWhell(const QJsonObject &node);
bool checkForDrag(const QJsonObject &node);
// get keymap from json object
QString getItemString(const QJsonObject &node, const QString &name);
double getItemDouble(const QJsonObject &node, const QString &name);
bool getItemBool(const QJsonObject &node, const QString &name);
QJsonObject getItemObject(const QJsonObject &node, const QString &name);
QPointF getItemPos(const QJsonObject &node, const QString &name);
QPair<ActionType, int> getItemKey(const QJsonObject &node, const QString &name);
KeyMapType getItemKeyMapType(const QJsonObject &node, const QString &name);
private:
static QString s_keyMapPath;
QVector<KeyMapNode> m_keyMapNodes;
KeyNode m_switchKey = { AT_KEY, Qt::Key_QuoteLeft };
// just for return
KeyMapNode m_invalidNode;
// steer wheel index
int m_idxSteerWheel = -1;
// mouse move index
int m_idxMouseMove = -1;
// mapping of key/mouse event name to index
QMetaEnum m_metaEnumKey = QMetaEnum::fromType<Qt::Key>();
QMetaEnum m_metaEnumMouseButtons = QMetaEnum::fromType<Qt::MouseButtons>();
QMetaEnum m_metaEnumKeyMapType = QMetaEnum::fromType<KeyMap::KeyMapType>();
// reverse map of key/mouse event
QMultiHash<int, KeyMapNode *> m_rmapKey;
QMultiHash<int, KeyMapNode *> m_rmapMouse;
};
#endif // KEYMAP_H

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/keymap.h
SOURCES += \
$$PWD/keymap.cpp

View file

@ -1,66 +0,0 @@
#include <QDebug>
#include "bufferutil.h"
#include "devicemsg.h"
DeviceMsg::DeviceMsg(QObject *parent) : QObject(parent) {}
DeviceMsg::~DeviceMsg()
{
if (DMT_GET_CLIPBOARD == m_data.type && Q_NULLPTR != m_data.clipboardMsg.text) {
delete m_data.clipboardMsg.text;
m_data.clipboardMsg.text = Q_NULLPTR;
}
}
DeviceMsg::DeviceMsgType DeviceMsg::type()
{
return m_data.type;
}
void DeviceMsg::getClipboardMsgData(QString &text)
{
text = QString::fromUtf8(m_data.clipboardMsg.text);
}
qint32 DeviceMsg::deserialize(QByteArray &byteArray)
{
QBuffer buf(&byteArray);
buf.open(QBuffer::ReadOnly);
qint64 len = buf.size();
char c = 0;
qint32 ret = 0;
if (len < 5) {
// at least type + empty string length
return 0; // not available
}
buf.getChar(&c);
m_data.type = (DeviceMsgType)c;
switch (m_data.type) {
case DMT_GET_CLIPBOARD: {
m_data.clipboardMsg.text = Q_NULLPTR;
quint16 clipboardLen = BufferUtil::read32(buf);
if (clipboardLen > len - 5) {
ret = 0; // not available
break;
}
QByteArray text = buf.readAll();
m_data.clipboardMsg.text = new char[text.length() + 1];
memcpy(m_data.clipboardMsg.text, text.data(), text.length());
m_data.clipboardMsg.text[text.length()] = '\0';
ret = 5 + clipboardLen;
break;
}
default:
qWarning("Unsupported device msg type: %d", (int)m_data.type);
ret = -1; // error, we cannot recover
}
buf.close();
return ret;
}

View file

@ -1,46 +0,0 @@
#ifndef DEVICEMSG_H
#define DEVICEMSG_H
#include <QBuffer>
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
// type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
class DeviceMsg : public QObject
{
Q_OBJECT
public:
enum DeviceMsgType
{
DMT_NULL = -1,
// 和服务端对应
DMT_GET_CLIPBOARD = 0,
};
explicit DeviceMsg(QObject *parent = nullptr);
virtual ~DeviceMsg();
DeviceMsg::DeviceMsgType type();
void getClipboardMsgData(QString &text);
qint32 deserialize(QByteArray &byteArray);
private:
struct DeviceMsgData
{
DeviceMsgType type = DMT_NULL;
union
{
struct
{
char *text = Q_NULLPTR;
} clipboardMsg;
};
DeviceMsgData() {}
~DeviceMsgData() {}
};
DeviceMsgData m_data;
};
#endif // DEVICEMSG_H

View file

@ -1,58 +0,0 @@
#include <QApplication>
#include <QClipboard>
#include <QTcpSocket>
#include "devicemsg.h"
#include "receiver.h"
Receiver::Receiver(QObject *parent) : QObject(parent) {}
Receiver::~Receiver() {}
void Receiver::setControlSocket(QTcpSocket *controlSocket)
{
if (m_controlSocket || !controlSocket) {
return;
}
m_controlSocket = controlSocket;
connect(controlSocket, &QTcpSocket::readyRead, this, &Receiver::onReadyRead);
}
void Receiver::onReadyRead()
{
if (!m_controlSocket) {
return;
}
while (m_controlSocket->bytesAvailable()) {
QByteArray byteArray = m_controlSocket->peek(m_controlSocket->bytesAvailable());
DeviceMsg deviceMsg;
qint32 consume = deviceMsg.deserialize(byteArray);
if (0 >= consume) {
break;
}
m_controlSocket->read(consume);
processMsg(&deviceMsg);
}
}
void Receiver::processMsg(DeviceMsg *deviceMsg)
{
switch (deviceMsg->type()) {
case DeviceMsg::DMT_GET_CLIPBOARD: {
qInfo("Device clipboard copied");
QClipboard *board = QApplication::clipboard();
QString text;
deviceMsg->getClipboardMsgData(text);
if (board->text() == text) {
qDebug("Computer clipboard unchanged");
break;
}
board->setText(text);
break;
}
default:
break;
}
}

View file

@ -1,27 +0,0 @@
#ifndef RECEIVER_H
#define RECEIVER_H
#include <QPointer>
class QTcpSocket;
class DeviceMsg;
class Receiver : public QObject
{
Q_OBJECT
public:
explicit Receiver(QObject *parent = Q_NULLPTR);
virtual ~Receiver();
void setControlSocket(QTcpSocket *controlSocket);
public slots:
void onReadyRead();
protected:
void processMsg(DeviceMsg *deviceMsg);
private:
QPointer<QTcpSocket> m_controlSocket;
};
#endif // RECEIVER_H

View file

@ -1,7 +0,0 @@
HEADERS += \
$$PWD/devicemsg.h \
$$PWD/receiver.h
SOURCES += \
$$PWD/devicemsg.cpp \
$$PWD/receiver.cpp

View file

@ -1,74 +0,0 @@
#include <QDebug>
#include "avframeconvert.h"
AVFrameConvert::AVFrameConvert() {}
AVFrameConvert::~AVFrameConvert() {}
void AVFrameConvert::setSrcFrameInfo(int srcWidth, int srcHeight, AVPixelFormat srcFormat)
{
m_srcWidth = srcWidth;
m_srcHeight = srcHeight;
m_srcFormat = srcFormat;
qDebug() << "Convert::src frame info " << srcWidth << "x" << srcHeight;
}
void AVFrameConvert::getSrcFrameInfo(int &srcWidth, int &srcHeight, AVPixelFormat &srcFormat)
{
srcWidth = m_srcWidth;
srcHeight = m_srcHeight;
srcFormat = m_srcFormat;
}
void AVFrameConvert::setDstFrameInfo(int dstWidth, int dstHeight, AVPixelFormat dstFormat)
{
m_dstWidth = dstWidth;
m_dstHeight = dstHeight;
m_dstFormat = dstFormat;
}
void AVFrameConvert::getDstFrameInfo(int &dstWidth, int &dstHeight, AVPixelFormat &dstFormat)
{
dstWidth = m_dstWidth;
dstHeight = m_dstHeight;
dstFormat = m_dstFormat;
}
bool AVFrameConvert::init()
{
if (m_convertCtx) {
return true;
}
m_convertCtx = sws_getContext(m_srcWidth, m_srcHeight, m_srcFormat, m_dstWidth, m_dstHeight, m_dstFormat, SWS_BICUBIC, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR);
if (!m_convertCtx) {
return false;
}
return true;
}
bool AVFrameConvert::isInit()
{
return m_convertCtx ? true : false;
}
void AVFrameConvert::deInit()
{
if (m_convertCtx) {
sws_freeContext(m_convertCtx);
m_convertCtx = Q_NULLPTR;
}
}
bool AVFrameConvert::convert(const AVFrame *srcFrame, AVFrame *dstFrame)
{
if (!m_convertCtx || !srcFrame || !dstFrame) {
return false;
}
qint32 ret
= sws_scale(m_convertCtx, static_cast<const uint8_t *const *>(srcFrame->data), srcFrame->linesize, 0, m_srcHeight, dstFrame->data, dstFrame->linesize);
if (0 == ret) {
return false;
}
return true;
}

View file

@ -1,40 +0,0 @@
#ifndef AVFRAMECONVERT_H
#define AVFRAMECONVERT_H
#include <QtGlobal>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavutil/frame.h"
#include "libswscale/swscale.h"
}
class AVFrameConvert
{
public:
AVFrameConvert();
virtual ~AVFrameConvert();
public:
void setSrcFrameInfo(int srcWidth, int srcHeight, AVPixelFormat srcFormat);
void getSrcFrameInfo(int &srcWidth, int &srcHeight, AVPixelFormat &srcFormat);
void setDstFrameInfo(int dstWidth, int dstHeight, AVPixelFormat dstFormat);
void getDstFrameInfo(int &dstWidth, int &dstHeight, AVPixelFormat &dstFormat);
bool init();
bool isInit();
void deInit();
bool convert(const AVFrame *srcFrame, AVFrame *dstFrame);
private:
int m_srcWidth = 0;
int m_srcHeight = 0;
AVPixelFormat m_srcFormat = AV_PIX_FMT_NONE;
int m_dstWidth = 0;
int m_dstHeight = 0;
AVPixelFormat m_dstFormat = AV_PIX_FMT_NONE;
struct SwsContext *m_convertCtx = Q_NULLPTR;
};
#endif // AVFRAMECONVERT_H

View file

@ -1,120 +0,0 @@
#include <QDebug>
#include "compat.h"
#include "decoder.h"
#include "videobuffer.h"
Decoder::Decoder(VideoBuffer *vb, QObject *parent) : QObject(parent), m_vb(vb) {}
Decoder::~Decoder() {}
bool Decoder::open(const AVCodec *codec)
{
// codec context
m_codecCtx = avcodec_alloc_context3(codec);
if (!m_codecCtx) {
qCritical("Could not allocate decoder context");
return false;
}
if (avcodec_open2(m_codecCtx, codec, NULL) < 0) {
qCritical("Could not open H.264 codec");
return false;
}
m_isCodecCtxOpen = true;
return true;
}
void Decoder::close()
{
if (!m_codecCtx) {
return;
}
if (m_isCodecCtxOpen) {
avcodec_close(m_codecCtx);
}
avcodec_free_context(&m_codecCtx);
}
bool Decoder::push(const AVPacket *packet)
{
if (!m_codecCtx || !m_vb) {
return false;
}
AVFrame *decodingFrame = m_vb->decodingFrame();
#ifdef QTSCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
int ret = -1;
if ((ret = avcodec_send_packet(m_codecCtx, packet)) < 0) {
char errorbuf[255] = { 0 };
av_strerror(ret, errorbuf, 254);
qCritical("Could not send video packet: %s", errorbuf);
return false;
}
if (decodingFrame) {
ret = avcodec_receive_frame(m_codecCtx, decodingFrame);
}
if (!ret) {
// a frame was received
pushFrame();
//emit getOneFrame(yuvDecoderFrame->data[0], yuvDecoderFrame->data[1], yuvDecoderFrame->data[2],
// yuvDecoderFrame->linesize[0], yuvDecoderFrame->linesize[1], yuvDecoderFrame->linesize[2]);
/*
// m_conver转换yuv为rgb是使用cpu转的占用cpu太高改用opengl渲染yuv
// QImage的copy也非常占用内存此方案不考虑
if (!m_conver.isInit()) {
qDebug() << "decoder frame format" << decodingFrame->format;
m_conver.setSrcFrameInfo(codecCtx->width, codecCtx->height, AV_PIX_FMT_YUV420P);
m_conver.setDstFrameInfo(codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB32);
m_conver.init();
}
if (!outBuffer) {
outBuffer=new quint8[avpicture_get_size(AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height)];
avpicture_fill((AVPicture *)rgbDecoderFrame, outBuffer, AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height);
}
m_conver.convert(decodingFrame, rgbDecoderFrame);
//QImage tmpImg((uchar *)outBuffer, codecCtx->width, codecCtx->height, QImage::Format_RGB32);
//QImage image = tmpImg.copy();
//emit getOneImage(image);
*/
} else if (ret != AVERROR(EAGAIN)) {
qCritical("Could not receive video frame: %d", ret);
return false;
}
#else
int gotPicture = 0;
int len = -1;
if (decodingFrame) {
len = avcodec_decode_video2(m_codecCtx, decodingFrame, &gotPicture, packet);
}
if (len < 0) {
qCritical("Could not decode video packet: %d", len);
return false;
}
if (gotPicture) {
pushFrame();
}
#endif
return true;
}
void Decoder::interrupt()
{
if (m_vb) {
m_vb->interrupt();
}
}
void Decoder::pushFrame()
{
if (!m_vb) {
return;
}
bool previousFrameSkipped = true;
m_vb->offerDecodedFrame(previousFrameSkipped);
if (previousFrameSkipped) {
// the previous newFrame will consume this frame
return;
}
emit onNewFrame();
}

View file

@ -1,35 +0,0 @@
#ifndef DECODER_H
#define DECODER_H
#include <QObject>
extern "C"
{
#include "libavcodec/avcodec.h"
}
class VideoBuffer;
class Decoder : public QObject
{
Q_OBJECT
public:
Decoder(VideoBuffer *vb, QObject *parent = Q_NULLPTR);
virtual ~Decoder();
bool open(const AVCodec *codec);
void close();
bool push(const AVPacket *packet);
void interrupt();
signals:
void onNewFrame();
protected:
void pushFrame();
private:
VideoBuffer *m_vb = Q_NULLPTR;
AVCodecContext *m_codecCtx = Q_NULLPTR;
bool m_isCodecCtxOpen = false;
};
#endif // DECODER_H

View file

@ -1,11 +0,0 @@
HEADERS += \
$$PWD/decoder.h \
$$PWD/fpscounter.h \
$$PWD/avframeconvert.h \
$$PWD/videobuffer.h
SOURCES += \
$$PWD/decoder.cpp \
$$PWD/fpscounter.cpp \
$$PWD/avframeconvert.cpp \
$$PWD/videobuffer.cpp

View file

@ -1,66 +0,0 @@
#include <QDebug>
#include <QTimerEvent>
#include "fpscounter.h"
FpsCounter::FpsCounter(QObject *parent) : QObject(parent) {}
FpsCounter::~FpsCounter() {}
void FpsCounter::start()
{
resetCounter();
startCounterTimer();
}
void FpsCounter::stop()
{
stopCounterTimer();
resetCounter();
}
bool FpsCounter::isStarted()
{
return m_counterTimer;
}
void FpsCounter::addRenderedFrame()
{
m_rendered++;
}
void FpsCounter::addSkippedFrame()
{
m_skipped++;
}
void FpsCounter::timerEvent(QTimerEvent *event)
{
if (event && m_counterTimer == event->timerId()) {
m_curRendered = m_rendered;
m_curSkipped = m_skipped;
resetCounter();
emit updateFPS(m_curRendered);
//qInfo("FPS:%d Discard:%d", m_curRendered, m_skipped);
}
}
void FpsCounter::startCounterTimer()
{
stopCounterTimer();
m_counterTimer = startTimer(1000);
}
void FpsCounter::stopCounterTimer()
{
if (m_counterTimer) {
killTimer(m_counterTimer);
m_counterTimer = 0;
}
}
void FpsCounter::resetCounter()
{
m_rendered = 0;
m_skipped = 0;
}

View file

@ -1,38 +0,0 @@
#ifndef FPSCOUNTER_H
#define FPSCOUNTER_H
#include <QObject>
class FpsCounter : public QObject
{
Q_OBJECT
public:
FpsCounter(QObject *parent = Q_NULLPTR);
virtual ~FpsCounter();
void start();
void stop();
bool isStarted();
void addRenderedFrame();
void addSkippedFrame();
signals:
void updateFPS(quint32 fps);
protected:
virtual void timerEvent(QTimerEvent *event);
private:
void startCounterTimer();
void stopCounterTimer();
void resetCounter();
private:
qint32 m_counterTimer = 0;
quint32 m_curRendered = 0;
quint32 m_curSkipped = 0;
quint32 m_rendered = 0;
quint32 m_skipped = 0;
};
#endif // FPSCOUNTER_H

View file

@ -1,128 +0,0 @@
#include "videobuffer.h"
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
}
VideoBuffer::VideoBuffer() {}
VideoBuffer::~VideoBuffer() {}
bool VideoBuffer::init(bool renderExpiredFrames)
{
m_renderExpiredFrames = renderExpiredFrames;
m_decodingFrame = av_frame_alloc();
if (!m_decodingFrame) {
goto error;
}
m_renderingframe = av_frame_alloc();
if (!m_renderingframe) {
goto error;
}
// there is initially no rendering frame, so consider it has already been
// consumed
m_renderingFrameConsumed = true;
m_fpsCounter.start();
return true;
error:
deInit();
return false;
}
void VideoBuffer::deInit()
{
if (m_decodingFrame) {
av_frame_free(&m_decodingFrame);
m_decodingFrame = Q_NULLPTR;
}
if (m_renderingframe) {
av_frame_free(&m_renderingframe);
m_renderingframe = Q_NULLPTR;
}
m_fpsCounter.stop();
}
void VideoBuffer::lock()
{
m_mutex.lock();
}
void VideoBuffer::unLock()
{
m_mutex.unlock();
}
AVFrame *VideoBuffer::decodingFrame()
{
return m_decodingFrame;
}
void VideoBuffer::offerDecodedFrame(bool &previousFrameSkipped)
{
m_mutex.lock();
if (m_renderExpiredFrames) {
// if m_renderExpiredFrames is enable, then the decoder must wait for the current
// frame to be consumed
while (!m_renderingFrameConsumed && !m_interrupted) {
m_renderingFrameConsumedCond.wait(&m_mutex);
}
} else {
if (m_fpsCounter.isStarted() && !m_renderingFrameConsumed) {
m_fpsCounter.addSkippedFrame();
}
}
swap();
previousFrameSkipped = !m_renderingFrameConsumed;
m_renderingFrameConsumed = false;
m_mutex.unlock();
}
const AVFrame *VideoBuffer::consumeRenderedFrame()
{
Q_ASSERT(!m_renderingFrameConsumed);
m_renderingFrameConsumed = true;
if (m_fpsCounter.isStarted()) {
m_fpsCounter.addRenderedFrame();
}
if (m_renderExpiredFrames) {
// if m_renderExpiredFrames is enable, then notify the decoder the current frame is
// consumed, so that it may push a new one
m_renderingFrameConsumedCond.wakeOne();
}
return m_renderingframe;
}
const AVFrame *VideoBuffer::peekRenderedFrame()
{
return m_renderingframe;
}
void VideoBuffer::interrupt()
{
if (m_renderExpiredFrames) {
m_mutex.lock();
m_interrupted = true;
m_mutex.unlock();
// wake up blocking wait
m_renderingFrameConsumedCond.wakeOne();
}
}
FpsCounter *VideoBuffer::getFPSCounter()
{
return &m_fpsCounter;
}
void VideoBuffer::swap()
{
AVFrame *tmp = m_decodingFrame;
m_decodingFrame = m_renderingframe;
m_renderingframe = tmp;
}

View file

@ -1,60 +0,0 @@
#ifndef VIDEO_BUFFER_H
#define VIDEO_BUFFER_H
#include <QMutex>
#include <QWaitCondition>
#include "fpscounter.h"
// forward declarations
typedef struct AVFrame AVFrame;
class VideoBuffer
{
public:
VideoBuffer();
virtual ~VideoBuffer();
bool init(bool renderExpiredFrames = false);
void deInit();
void lock();
void unLock();
AVFrame *decodingFrame();
// set the decoder frame as ready for rendering
// this function locks m_mutex during its execution
// returns true if the previous frame had been consumed
void offerDecodedFrame(bool &previousFrameSkipped);
// mark the rendering frame as consumed and return it
// MUST be called with m_mutex locked!!!
// the caller is expected to render the returned frame to some texture before
// unlocking m_mutex
const AVFrame *consumeRenderedFrame();
const AVFrame *peekRenderedFrame();
// wake up and avoid any blocking call
void interrupt();
FpsCounter *getFPSCounter();
private:
void swap();
private:
AVFrame *m_decodingFrame = Q_NULLPTR;
AVFrame *m_renderingframe = Q_NULLPTR;
QMutex m_mutex;
bool m_renderingFrameConsumed = true;
FpsCounter m_fpsCounter;
bool m_renderExpiredFrames = false;
QWaitCondition m_renderingFrameConsumedCond;
// interrupted is not used if expired frames are not rendered
// since offering a frame will never block
bool m_interrupted = false;
};
#endif // VIDEO_BUFFER_H

View file

@ -1,402 +0,0 @@
#include <QDir>
#include <QMessageBox>
#include <QTimer>
#include "avframeconvert.h"
#include "config.h"
#include "controller.h"
#include "decoder.h"
#include "device.h"
#include "filehandler.h"
#include "mousetap/mousetap.h"
#include "recorder.h"
#include "server.h"
#include "stream.h"
#include "videobuffer.h"
#include "videoform.h"
extern "C"
{
#include "libavutil/imgutils.h"
}
Device::Device(DeviceParams params, QObject *parent) : QObject(parent), m_params(params)
{
if (!params.display && m_params.recordFileName.trimmed().isEmpty()) {
qCritical("not display must be recorded");
deleteLater();
return;
}
if (params.display) {
m_vb = new VideoBuffer();
m_vb->init(params.renderExpiredFrames);
m_decoder = new Decoder(m_vb, this);
m_fileHandler = new FileHandler(this);
m_controller = new Controller(params.gameScript, this);
m_videoForm = new VideoForm(params.framelessWindow, Config::getInstance().getSkin());
m_videoForm->setDevice(this);
}
m_stream = new Stream(this);
if (m_decoder) {
m_stream->setDecoder(m_decoder);
}
m_server = new Server(this);
if (!m_params.recordFileName.trimmed().isEmpty()) {
m_recorder = new Recorder(m_params.recordFileName);
m_stream->setRecoder(m_recorder);
}
initSignals();
startServer();
}
Device::~Device()
{
if (m_server) {
m_server->stop();
}
// server must stop before decoder, because decoder block main thread
if (m_stream) {
m_stream->stopDecode();
}
if (m_recorder) {
delete m_recorder;
}
if (m_vb) {
m_vb->deInit();
delete m_vb;
}
if (m_videoForm) {
m_videoForm->close();
delete m_videoForm;
}
emit deviceDisconnect(m_params.serial);
}
VideoForm *Device::getVideoForm()
{
return m_videoForm;
}
Server *Device::getServer()
{
return m_server;
}
const QString &Device::getSerial()
{
return m_params.serial;
}
const QSize Device::frameSize()
{
QSize size;
if (!m_videoForm) {
return size;
}
return m_videoForm->frameSize();
}
void Device::updateScript(QString script)
{
if (m_controller) {
m_controller->updateScript(script);
}
}
void Device::onScreenshot()
{
if (!m_vb) {
return;
}
m_vb->lock();
// screenshot
saveFrame(m_vb->peekRenderedFrame());
m_vb->unLock();
}
void Device::onShowTouch(bool show)
{
AdbProcess *adb = new AdbProcess();
if (!adb) {
return;
}
connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
if (AdbProcess::AER_SUCCESS_START != processResult) {
sender()->deleteLater();
}
});
adb->setShowTouchesEnabled(getSerial(), show);
qInfo() << getSerial() << " show touch " << (show ? "enable" : "disable");
}
void Device::initSignals()
{
connect(this, &Device::screenshot, this, &Device::onScreenshot);
connect(this, &Device::showTouch, this, &Device::onShowTouch);
connect(this, &Device::setControlState, this, &Device::onSetControlState);
connect(this, &Device::grabCursor, this, &Device::onGrabCursor);
if (m_controller) {
connect(m_controller, &Controller::grabCursor, this, &Device::grabCursor);
}
if (m_controller) {
connect(this, &Device::postGoBack, m_controller, &Controller::onPostGoBack);
connect(this, &Device::postGoHome, m_controller, &Controller::onPostGoHome);
connect(this, &Device::postGoMenu, m_controller, &Controller::onPostGoMenu);
connect(this, &Device::postAppSwitch, m_controller, &Controller::onPostAppSwitch);
connect(this, &Device::postPower, m_controller, &Controller::onPostPower);
connect(this, &Device::postVolumeUp, m_controller, &Controller::onPostVolumeUp);
connect(this, &Device::postVolumeDown, m_controller, &Controller::onPostVolumeDown);
connect(this, &Device::postCopy, m_controller, &Controller::onCopy);
connect(this, &Device::postCut, m_controller, &Controller::onCut);
connect(this, &Device::setScreenPowerMode, m_controller, &Controller::onSetScreenPowerMode);
connect(this, &Device::expandNotificationPanel, m_controller, &Controller::onExpandNotificationPanel);
connect(this, &Device::collapseNotificationPanel, m_controller, &Controller::onCollapseNotificationPanel);
connect(this, &Device::mouseEvent, m_controller, &Controller::onMouseEvent);
connect(this, &Device::wheelEvent, m_controller, &Controller::onWheelEvent);
connect(this, &Device::keyEvent, m_controller, &Controller::onKeyEvent);
connect(this, &Device::postBackOrScreenOn, m_controller, &Controller::onPostBackOrScreenOn);
connect(this, &Device::setDeviceClipboard, m_controller, &Controller::onSetDeviceClipboard);
connect(this, &Device::clipboardPaste, m_controller, &Controller::onClipboardPaste);
connect(this, &Device::postTextInput, m_controller, &Controller::onPostTextInput);
}
if (m_videoForm) {
connect(m_videoForm, &VideoForm::destroyed, this, [this](QObject *obj) {
Q_UNUSED(obj)
deleteLater();
});
connect(this, &Device::switchFullScreen, m_videoForm, &VideoForm::onSwitchFullScreen);
}
if (m_fileHandler) {
connect(this, &Device::pushFileRequest, this, [this](const QString &file, const QString &devicePath) {
m_fileHandler->onPushFileRequest(getSerial(), file, devicePath);
});
connect(this, &Device::installApkRequest, this, [this](const QString &apkFile) { m_fileHandler->onInstallApkRequest(getSerial(), apkFile); });
connect(m_fileHandler, &FileHandler::fileHandlerResult, this, [this](FileHandler::FILE_HANDLER_RESULT processResult, bool isApk) {
QString tipsType = "";
if (isApk) {
tipsType = tr("install apk");
} else {
tipsType = tr("file transfer");
}
QString tips;
if (FileHandler::FAR_IS_RUNNING == processResult && m_videoForm) {
tips = tr("wait current %1 to complete").arg(tipsType);
}
if (FileHandler::FAR_SUCCESS_EXEC == processResult && m_videoForm) {
tips = tr("%1 complete, save in %2").arg(tipsType).arg(Config::getInstance().getPushFilePath());
}
if (FileHandler::FAR_ERROR_EXEC == processResult && m_videoForm) {
tips = tr("%1 failed").arg(tipsType);
}
qInfo() << tips;
if (m_controlState == GCS_CLIENT) {
return;
}
//QMessageBox::information(m_videoForm, "QtScrcpy", tips, QMessageBox::Ok);
});
}
if (m_server) {
connect(m_server, &Server::serverStartResult, this, [this](bool success) {
if (success) {
m_server->connectTo();
} else {
deleteLater();
}
});
connect(m_server, &Server::connectToResult, this, [this](bool success, const QString &deviceName, const QSize &size) {
if (success) {
double diff = m_startTimeCount.elapsed() / 1000.0;
qInfo() << QString("server start finish in %1s").arg(diff).toStdString().c_str();
// update ui
if (m_videoForm) {
// must be show before updateShowSize
m_videoForm->show();
m_videoForm->setWindowTitle(deviceName);
m_videoForm->updateShowSize(size);
bool deviceVer = size.height() > size.width();
QRect rc = Config::getInstance().getRect(getSerial());
bool rcVer = rc.height() > rc.width();
// same width/height rate
if (rc.isValid() && (deviceVer == rcVer)) {
// mark: resize is for fix setGeometry magneticwidget bug
m_videoForm->resize(rc.size());
m_videoForm->setGeometry(rc);
}
}
// init recorder
if (m_recorder) {
m_recorder->setFrameSize(size);
}
// init decoder
m_stream->setVideoSocket(m_server->getVideoSocket());
m_stream->startDecode();
// init controller
if (m_controller) {
m_controller->setControlSocket(m_server->getControlSocket());
}
// 显示界面时才自动息屏m_params.display
if (m_params.closeScreen && m_params.display && m_controller) {
emit m_controller->onSetScreenPowerMode(ControlMsg::SPM_OFF);
}
}
});
connect(m_server, &Server::onServerStop, this, [this]() {
deleteLater();
qDebug() << "server process stop";
});
}
if (m_stream) {
connect(m_stream, &Stream::onStreamStop, this, [this]() {
deleteLater();
qDebug() << "stream thread stop";
});
}
if (m_decoder && m_vb) {
// must be Qt::QueuedConnection, ui update must be main thread
connect(
m_decoder,
&Decoder::onNewFrame,
this,
[this]() {
m_vb->lock();
const AVFrame *frame = m_vb->consumeRenderedFrame();
if (m_videoForm) {
m_videoForm->updateRender(frame);
}
m_vb->unLock();
},
Qt::QueuedConnection);
connect(m_vb->getFPSCounter(), &::FpsCounter::updateFPS, m_videoForm, &VideoForm::updateFPS);
}
}
void Device::startServer()
{
// fix: macos cant recv finished signel, timer is ok
QTimer::singleShot(0, this, [this]() {
m_startTimeCount.start();
// max size support 480p 720p 1080p 设备原生分辨率
// support wireless connect, example:
//m_server->start("192.168.0.174:5555", 27183, m_maxSize, m_bitRate, "");
// only one devices, serial can be null
// mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0"
Server::ServerParams params;
params.serial = m_params.serial;
params.localPort = m_params.localPort;
params.maxSize = m_params.maxSize;
params.bitRate = m_params.bitRate;
params.maxFps = m_params.maxFps;
params.crop = "-";
params.control = true;
params.useReverse = m_params.useReverse;
params.lockVideoOrientation = m_params.lockVideoOrientation;
params.stayAwake = m_params.stayAwake;
m_server->start(params);
});
}
void Device::onSetControlState(Device *device, Device::GroupControlState state)
{
Q_UNUSED(device)
if (m_controlState == state) {
return;
}
GroupControlState oldState = m_controlState;
m_controlState = state;
emit controlStateChange(this, oldState, m_controlState);
}
void Device::onGrabCursor(bool grab)
{
if (!m_videoForm) {
return;
}
if (m_controlState == GCS_CLIENT) {
return;
}
QRect rc = m_videoForm->getGrabCursorRect();
MouseTap::getInstance()->enableMouseEventTap(rc, grab);
}
Device::GroupControlState Device::controlState()
{
return m_controlState;
}
bool Device::isCurrentCustomKeymap()
{
if (!m_controller) {
return false;
}
return m_controller->isCurrentCustomKeymap();
}
bool Device::saveFrame(const AVFrame *frame)
{
if (!frame) {
return false;
}
// create buffer
QImage rgbImage(frame->width, frame->height, QImage::Format_RGB32);
AVFrame *rgbFrame = av_frame_alloc();
if (!rgbFrame) {
return false;
}
// bind buffer to AVFrame
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbImage.bits(), AV_PIX_FMT_RGB32, frame->width, frame->height, 4);
// convert
AVFrameConvert convert;
convert.setSrcFrameInfo(frame->width, frame->height, AV_PIX_FMT_YUV420P);
convert.setDstFrameInfo(frame->width, frame->height, AV_PIX_FMT_RGB32);
bool ret = false;
ret = convert.init();
if (!ret) {
return false;
}
ret = convert.convert(frame, rgbFrame);
if (!ret) {
return false;
}
convert.deInit();
av_free(rgbFrame);
// save
QString absFilePath;
QString fileDir(m_params.recordPath);
if (fileDir.isEmpty()) {
qWarning() << "please select record save path!!!";
return false;
}
QDateTime dateTime = QDateTime::currentDateTime();
QString fileName = dateTime.toString("_yyyyMMdd_hhmmss_zzz");
fileName = Config::getInstance().getTitle() + fileName + ".png";
QDir dir(fileDir);
absFilePath = dir.absoluteFilePath(fileName);
ret = rgbImage.save(absFilePath, "PNG", 100);
if (!ret) {
return false;
}
qInfo() << "screenshot save to " << absFilePath;
return true;
}

View file

@ -1,132 +0,0 @@
#ifndef DEVICE_H
#define DEVICE_H
#include <QElapsedTimer>
#include <QPointer>
#include <QTime>
#include "controlmsg.h"
class QMouseEvent;
class QWheelEvent;
class QKeyEvent;
class Recorder;
class Server;
class VideoBuffer;
class Decoder;
class FileHandler;
class Stream;
class VideoForm;
class Controller;
struct AVFrame;
class Device : public QObject
{
Q_OBJECT
public:
struct DeviceParams
{
QString recordFileName = ""; // 视频录制文件名
QString recordPath = ""; // 视频保存路径
QString serial = ""; // 设备序列号
quint16 localPort = 27183; // reverse时本地监听端口
quint16 maxSize = 720; // 视频分辨率
quint32 bitRate = 8000000; // 视频比特率
quint32 maxFps = 60; // 视频最大帧率
bool closeScreen = false; // 启动时自动息屏
bool useReverse = true; // true:先使用adb reverse失败后自动使用adb forwardfalse:直接使用adb forward
bool display = true; // 是否显示画面(或者仅仅后台录制)
QString gameScript = ""; // 游戏映射脚本
bool renderExpiredFrames = false; // 是否渲染延迟视频帧
int lockVideoOrientation = -1; // 是否锁定视频方向
bool stayAwake = false; // 是否保持唤醒
bool framelessWindow = false; // 是否无边框窗口
};
enum GroupControlState
{
GCS_FREE = 0,
GCS_HOST,
GCS_CLIENT,
};
explicit Device(DeviceParams params, QObject *parent = nullptr);
virtual ~Device();
VideoForm *getVideoForm();
Server *getServer();
const QString &getSerial();
const QSize frameSize();
void updateScript(QString script);
Device::GroupControlState controlState();
bool isCurrentCustomKeymap();
signals:
void deviceDisconnect(QString serial);
// tool bar
void switchFullScreen();
void postGoBack();
void postGoHome();
void postGoMenu();
void postAppSwitch();
void postPower();
void postVolumeUp();
void postVolumeDown();
void postCopy();
void postCut();
void setScreenPowerMode(ControlMsg::ScreenPowerMode mode);
void expandNotificationPanel();
void collapseNotificationPanel();
void postBackOrScreenOn();
void postTextInput(QString &text);
void requestDeviceClipboard();
void setDeviceClipboard(bool pause = true);
void clipboardPaste();
void pushFileRequest(const QString &file, const QString &devicePath = "");
void installApkRequest(const QString &apkFile);
// key map
void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
// self connect signal and slots
void screenshot();
void showTouch(bool show);
void setControlState(Device *device, Device::GroupControlState state);
void grabCursor(bool grab);
// for notify
void controlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
public slots:
void onScreenshot();
void onShowTouch(bool show);
void onSetControlState(Device *device, Device::GroupControlState state);
void onGrabCursor(bool grab);
private:
void initSignals();
void startServer();
bool saveFrame(const AVFrame *frame);
private:
// server relevant
QPointer<Server> m_server;
QPointer<Decoder> m_decoder;
QPointer<Controller> m_controller;
QPointer<FileHandler> m_fileHandler;
QPointer<Stream> m_stream;
VideoBuffer *m_vb = Q_NULLPTR;
Recorder *m_recorder = Q_NULLPTR;
// ui
QPointer<VideoForm> m_videoForm;
QElapsedTimer m_startTimeCount;
DeviceParams m_params;
GroupControlState m_controlState = GCS_FREE;
};
#endif // DEVICE_H

View file

@ -1,27 +0,0 @@
HEADERS += \
$$PWD/device.h
SOURCES += \
$$PWD/device.cpp
include ($$PWD/server/server.pri)
include ($$PWD/decoder/decoder.pri)
include ($$PWD/render/render.pri)
include ($$PWD/stream/stream.pri)
include ($$PWD/android/android.pri)
include ($$PWD/controller/controller.pri)
include ($$PWD/filehandler/filehandler.pri)
include ($$PWD/recorder/recorder.pri)
include ($$PWD/ui/ui.pri)
INCLUDEPATH += \
$$PWD/../../third_party/ffmpeg/include \
$$PWD/server \
$$PWD/decoder \
$$PWD/render \
$$PWD/stream \
$$PWD/android \
$$PWD/controller \
$$PWD/filehandler \
$$PWD/recorder \
$$PWD/ui

View file

@ -1,47 +0,0 @@
#include "filehandler.h"
FileHandler::FileHandler(QObject *parent) : QObject(parent)
{
}
FileHandler::~FileHandler() {}
void FileHandler::onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath)
{
AdbProcess* adb = new AdbProcess;
bool isApk = false;
connect(adb, &AdbProcess::adbProcessResult, this, [this, adb, isApk](AdbProcess::ADB_EXEC_RESULT processResult) {
onAdbProcessResult(adb, isApk, processResult);
});
adb->push(serial, file, devicePath);
}
void FileHandler::onInstallApkRequest(const QString &serial, const QString &apkFile)
{
AdbProcess* adb = new AdbProcess;
bool isApk = true;
connect(adb, &AdbProcess::adbProcessResult, this, [this, adb, isApk](AdbProcess::ADB_EXEC_RESULT processResult) {
onAdbProcessResult(adb, isApk, processResult);
});
adb->install(serial, apkFile);
}
void FileHandler::onAdbProcessResult(AdbProcess *adb, bool isApk, AdbProcess::ADB_EXEC_RESULT processResult)
{
switch (processResult) {
case AdbProcess::AER_ERROR_START:
case AdbProcess::AER_ERROR_EXEC:
case AdbProcess::AER_ERROR_MISSING_BINARY:
emit fileHandlerResult(FAR_ERROR_EXEC, isApk);
adb->deleteLater();
break;
case AdbProcess::AER_SUCCESS_EXEC:
emit fileHandlerResult(FAR_SUCCESS_EXEC, isApk);
adb->deleteLater();
break;
default:
break;
}
}

View file

@ -1,34 +0,0 @@
#ifndef FILEHANDLER_H
#define FILEHANDLER_H
#include <QObject>
#include "adbprocess.h"
class FileHandler : public QObject
{
Q_OBJECT
public:
enum FILE_HANDLER_RESULT
{
FAR_IS_RUNNING, // 正在执行
FAR_SUCCESS_EXEC, // 执行成功
FAR_ERROR_EXEC, // 执行失败
};
FileHandler(QObject *parent = nullptr);
virtual ~FileHandler();
const QString &getDevicePath();
public slots:
void onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath = "");
void onInstallApkRequest(const QString &serial, const QString &apkFile);
protected:
void onAdbProcessResult(AdbProcess* adb, bool isApk, AdbProcess::ADB_EXEC_RESULT processResult);
signals:
void fileHandlerResult(FILE_HANDLER_RESULT processResult, bool isApk = false);
};
#endif // FILEHANDLER_H

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/filehandler.h
SOURCES += \
$$PWD/filehandler.cpp

View file

@ -1,330 +0,0 @@
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include "compat.h"
#include "recorder.h"
static const AVRational SCRCPY_TIME_BASE = { 1, 1000000 }; // timestamps in us
Recorder::Recorder(const QString &fileName, QObject *parent) : QThread(parent), m_fileName(fileName), m_format(guessRecordFormat(fileName)) {}
Recorder::~Recorder() {}
AVPacket *Recorder::packetNew(const AVPacket *packet)
{
AVPacket *rec = new AVPacket;
if (!rec) {
return Q_NULLPTR;
}
// av_packet_ref() does not initialize all fields in old FFmpeg versions
av_init_packet(rec);
if (av_packet_ref(rec, packet)) {
delete rec;
return Q_NULLPTR;
}
return rec;
}
void Recorder::packetDelete(AVPacket *packet)
{
av_packet_unref(packet);
delete packet;
}
void Recorder::queueClear()
{
while (!m_queue.isEmpty()) {
packetDelete(m_queue.dequeue());
}
}
void Recorder::setFrameSize(const QSize &declaredFrameSize)
{
m_declaredFrameSize = declaredFrameSize;
}
void Recorder::setFormat(Recorder::RecorderFormat format)
{
m_format = format;
}
bool Recorder::open(const AVCodec *inputCodec)
{
QString formatName = recorderGetFormatName(m_format);
Q_ASSERT(!formatName.isEmpty());
const AVOutputFormat *format = findMuxer(formatName.toUtf8());
if (!format) {
qCritical("Could not find muxer");
return false;
}
m_formatCtx = avformat_alloc_context();
if (!m_formatCtx) {
qCritical("Could not allocate output context");
return false;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
m_formatCtx->oformat = (AVOutputFormat *)format;
QString comment = "Recorded by QtScrcpy " + QCoreApplication::applicationVersion();
av_dict_set(&m_formatCtx->metadata, "comment", comment.toUtf8(), 0);
AVStream *outStream = avformat_new_stream(m_formatCtx, inputCodec);
if (!outStream) {
avformat_free_context(m_formatCtx);
m_formatCtx = Q_NULLPTR;
return false;
}
#ifdef QTSCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
outStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
outStream->codecpar->codec_id = inputCodec->id;
outStream->codecpar->format = AV_PIX_FMT_YUV420P;
outStream->codecpar->width = m_declaredFrameSize.width();
outStream->codecpar->height = m_declaredFrameSize.height();
#else
outStream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
outStream->codec->codec_id = inputCodec->id;
outStream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
outStream->codec->width = m_declaredFrameSize.width();
outStream->codec->height = m_declaredFrameSize.height();
#endif
int ret = avio_open(&m_formatCtx->pb, m_fileName.toUtf8().toStdString().c_str(), AVIO_FLAG_WRITE);
if (ret < 0) {
char errorbuf[255] = { 0 };
av_strerror(ret, errorbuf, 254);
qCritical() << QString("Failed to open output file: %1 %2").arg(errorbuf).arg(m_fileName).toUtf8().toStdString().c_str();
// ostream will be cleaned up during context cleaning
avformat_free_context(m_formatCtx);
m_formatCtx = Q_NULLPTR;
return false;
}
return true;
}
void Recorder::close()
{
if (Q_NULLPTR != m_formatCtx) {
if (m_headerWritten) {
int ret = av_write_trailer(m_formatCtx);
if (ret < 0) {
qCritical() << QString("Failed to write trailer to %1").arg(m_fileName).toUtf8().toStdString().c_str();
m_failed = true;
} else {
qInfo() << QString("success record %1").arg(m_fileName).toStdString().c_str();
}
} else {
// the recorded file is empty
m_failed = true;
}
avio_close(m_formatCtx->pb);
avformat_free_context(m_formatCtx);
m_formatCtx = Q_NULLPTR;
}
}
bool Recorder::write(AVPacket *packet)
{
if (!m_headerWritten) {
if (packet->pts != AV_NOPTS_VALUE) {
qCritical("The first packet is not a config packet");
return false;
}
bool ok = recorderWriteHeader(packet);
if (!ok) {
return false;
}
m_headerWritten = true;
return true;
}
if (packet->pts == AV_NOPTS_VALUE) {
// ignore config packets
return true;
}
recorderRescalePacket(packet);
return av_write_frame(m_formatCtx, packet) >= 0;
}
const AVOutputFormat *Recorder::findMuxer(const char *name)
{
#ifdef QTSCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
void *opaque = Q_NULLPTR;
#endif
const AVOutputFormat *outFormat = Q_NULLPTR;
do {
#ifdef QTSCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
outFormat = av_muxer_iterate(&opaque);
#else
outFormat = av_oformat_next(outFormat);
#endif
// until null or with name "name"
} while (outFormat && strcmp(outFormat->name, name));
return outFormat;
}
bool Recorder::recorderWriteHeader(const AVPacket *packet)
{
AVStream *ostream = m_formatCtx->streams[0];
quint8 *extradata = (quint8 *)av_malloc(packet->size * sizeof(quint8));
if (!extradata) {
qCritical("Cannot allocate extradata");
return false;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
#ifdef QTSCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
#else
ostream->codec->extradata = extradata;
ostream->codec->extradata_size = packet->size;
#endif
int ret = avformat_write_header(m_formatCtx, NULL);
if (ret < 0) {
qCritical("Failed to write header recorder file");
return false;
}
return true;
}
void Recorder::recorderRescalePacket(AVPacket *packet)
{
AVStream *ostream = m_formatCtx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
QString Recorder::recorderGetFormatName(Recorder::RecorderFormat format)
{
switch (format) {
case RECORDER_FORMAT_MP4:
return "mp4";
case RECORDER_FORMAT_MKV:
return "matroska";
default:
return "";
}
}
Recorder::RecorderFormat Recorder::guessRecordFormat(const QString &fileName)
{
if (4 > fileName.length()) {
return Recorder::RECORDER_FORMAT_NULL;
}
QFileInfo fileInfo = QFileInfo(fileName);
QString ext = fileInfo.suffix();
if (0 == ext.compare("mp4")) {
return Recorder::RECORDER_FORMAT_MP4;
}
if (0 == ext.compare("mkv")) {
return Recorder::RECORDER_FORMAT_MKV;
}
return Recorder::RECORDER_FORMAT_NULL;
}
void Recorder::run()
{
for (;;) {
AVPacket *rec = Q_NULLPTR;
{
QMutexLocker locker(&m_mutex);
while (!m_stopped && m_queue.isEmpty()) {
m_recvDataCond.wait(&m_mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping
if (m_stopped && m_queue.isEmpty()) {
AVPacket *last = m_previous;
if (last) {
// assign an arbitrary duration to the last packet
last->duration = 100000;
bool ok = write(last);
if (!ok) {
// failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file
// will still be valid
qWarning("Could not record last packet");
}
packetDelete(last);
}
break;
}
rec = m_queue.dequeue();
}
// recorder->previous is only written from this thread, no need to lock
AVPacket *previous = m_previous;
m_previous = rec;
if (!previous) {
// we just received the first packet
continue;
}
// config packets have no PTS, we must ignore them
if (rec->pts != AV_NOPTS_VALUE && previous->pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet
previous->duration = rec->pts - previous->pts;
}
bool ok = write(previous);
packetDelete(previous);
if (!ok) {
qCritical("Could not record packet");
QMutexLocker locker(&m_mutex);
m_failed = true;
// discard pending packets
queueClear();
break;
}
}
qDebug("Recorder thread ended");
}
bool Recorder::startRecorder()
{
start();
return true;
}
void Recorder::stopRecorder()
{
QMutexLocker locker(&m_mutex);
m_stopped = true;
m_recvDataCond.wakeOne();
}
bool Recorder::push(const AVPacket *packet)
{
QMutexLocker locker(&m_mutex);
Q_ASSERT(!m_stopped);
if (m_failed) {
// reject any new packet (this will stop the stream)
return false;
}
AVPacket *rec = packetNew(packet);
if (rec) {
m_queue.enqueue(rec);
m_recvDataCond.wakeOne();
}
return rec != Q_NULLPTR;
}

View file

@ -1,71 +0,0 @@
#ifndef RECORDER_H
#define RECORDER_H
#include <QMutex>
#include <QQueue>
#include <QSize>
#include <QString>
#include <QThread>
#include <QWaitCondition>
extern "C"
{
#include "libavformat/avformat.h"
}
class Recorder : public QThread
{
Q_OBJECT
public:
enum RecorderFormat
{
RECORDER_FORMAT_NULL = 0,
RECORDER_FORMAT_MP4,
RECORDER_FORMAT_MKV,
};
Recorder(const QString &fileName, QObject *parent = Q_NULLPTR);
virtual ~Recorder();
void setFrameSize(const QSize &declaredFrameSize);
void setFormat(Recorder::RecorderFormat format);
bool open(const AVCodec *inputCodec);
void close();
bool write(AVPacket *packet);
bool startRecorder();
void stopRecorder();
bool push(const AVPacket *packet);
private:
const AVOutputFormat *findMuxer(const char *name);
bool recorderWriteHeader(const AVPacket *packet);
void recorderRescalePacket(AVPacket *packet);
QString recorderGetFormatName(Recorder::RecorderFormat format);
RecorderFormat guessRecordFormat(const QString &fileName);
private:
AVPacket *packetNew(const AVPacket *packet);
void packetDelete(AVPacket *packet);
void queueClear();
protected:
void run();
private:
QString m_fileName = "";
AVFormatContext *m_formatCtx = Q_NULLPTR;
QSize m_declaredFrameSize;
bool m_headerWritten = false;
RecorderFormat m_format = RECORDER_FORMAT_NULL;
QMutex m_mutex;
QWaitCondition m_recvDataCond;
bool m_stopped = false; // set on recorder_stop() by the stream reader
bool m_failed = false; // set on packet write failure
QQueue<AVPacket *> m_queue;
// we can write a packet only once we received the next one so that we can
// set its duration (next_pts - current_pts)
// "previous" is only accessed from the recorder thread, so it does not
// need to be protected by the mutex
AVPacket *m_previous = Q_NULLPTR;
};
#endif // RECORDER_H

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/recorder.h
SOURCES += \
$$PWD/recorder.cpp

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/qyuvopenglwidget.h
SOURCES += \
$$PWD/qyuvopenglwidget.cpp

View file

@ -1,512 +0,0 @@
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QThread>
#include <QTimer>
#include <QTimerEvent>
#include "config.h"
#include "server.h"
#define DEVICE_NAME_FIELD_LENGTH 64
#define SOCKET_NAME "scrcpy"
#define MAX_CONNECT_COUNT 30
#define MAX_RESTART_COUNT 1
Server::Server(QObject *parent) : QObject(parent)
{
connect(&m_workProcess, &AdbProcess::adbProcessResult, this, &Server::onWorkProcessResult);
connect(&m_serverProcess, &AdbProcess::adbProcessResult, this, &Server::onWorkProcessResult);
connect(&m_serverSocket, &QTcpServer::newConnection, this, [this]() {
QTcpSocket *tmp = m_serverSocket.nextPendingConnection();
if (dynamic_cast<VideoSocket *>(tmp)) {
m_videoSocket = dynamic_cast<VideoSocket *>(tmp);
if (!m_videoSocket->isValid() || !readInfo(m_videoSocket, m_deviceName, m_deviceSize)) {
stop();
emit connectToResult(false);
}
} else {
m_controlSocket = tmp;
if (m_controlSocket && m_controlSocket->isValid()) {
// we don't need the server socket anymore
// just m_videoSocket is ok
m_serverSocket.close();
// we don't need the adb tunnel anymore
disableTunnelReverse();
m_tunnelEnabled = false;
emit connectToResult(true, m_deviceName, m_deviceSize);
} else {
stop();
emit connectToResult(false);
}
stopAcceptTimeoutTimer();
}
});
}
Server::~Server() {}
const QString &Server::getServerPath()
{
if (m_serverPath.isEmpty()) {
m_serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH"));
QFileInfo fileInfo(m_serverPath);
if (m_serverPath.isEmpty() || !fileInfo.isFile()) {
m_serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server";
}
}
return m_serverPath;
}
bool Server::pushServer()
{
if (m_workProcess.isRuning()) {
m_workProcess.kill();
}
m_workProcess.push(m_params.serial, getServerPath(), Config::getInstance().getServerPath());
return true;
}
bool Server::enableTunnelReverse()
{
if (m_workProcess.isRuning()) {
m_workProcess.kill();
}
m_workProcess.reverse(m_params.serial, SOCKET_NAME, m_params.localPort);
return true;
}
bool Server::disableTunnelReverse()
{
AdbProcess *adb = new AdbProcess();
if (!adb) {
return false;
}
connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
if (AdbProcess::AER_SUCCESS_START != processResult) {
sender()->deleteLater();
}
});
adb->reverseRemove(m_params.serial, SOCKET_NAME);
return true;
}
bool Server::enableTunnelForward()
{
if (m_workProcess.isRuning()) {
m_workProcess.kill();
}
m_workProcess.forward(m_params.serial, m_params.localPort, SOCKET_NAME);
return true;
}
bool Server::disableTunnelForward()
{
AdbProcess *adb = new AdbProcess();
if (!adb) {
return false;
}
connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
if (AdbProcess::AER_SUCCESS_START != processResult) {
sender()->deleteLater();
}
});
adb->forwardRemove(m_params.serial, m_params.localPort);
return true;
}
bool Server::execute()
{
if (m_serverProcess.isRuning()) {
m_serverProcess.kill();
}
QStringList args;
args << "shell";
args << QString("CLASSPATH=%1").arg(Config::getInstance().getServerPath());
args << "app_process";
#ifdef SERVER_DEBUGGER
#define SERVER_DEBUGGER_PORT "5005"
args <<
#ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
#else
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
#endif
SERVER_DEBUGGER_PORT,
#endif
args << "/"; // unused;
args << "com.genymobile.scrcpy.Server";
args << Config::getInstance().getServerVersion();
args << Config::getInstance().getLogLevel();
args << QString::number(m_params.maxSize);
args << QString::number(m_params.bitRate);
args << QString::number(m_params.maxFps);
args << QString::number(m_params.lockVideoOrientation);
args << (m_tunnelForward ? "true" : "false");
if (m_params.crop.isEmpty()) {
args << "-";
} else {
args << m_params.crop;
}
args << "true"; // always send frame meta (packet boundaries + timestamp)
args << (m_params.control ? "true" : "false");
args << "0"; // display id
args << "false"; // show touch
args << (m_params.stayAwake ? "true" : "false"); // stay awake
// code option
// https://github.com/Genymobile/scrcpy/commit/080a4ee3654a9b7e96c8ffe37474b5c21c02852a
// <https://d.android.com/reference/android/media/MediaFormat>
args << Config::getInstance().getCodecOptions();
args << Config::getInstance().getCodecName();
#ifdef SERVER_DEBUGGER
qInfo("Server debugger waiting for a client on device port " SERVER_DEBUGGER_PORT "...");
// From the computer, run
// adb forward tcp:5005 tcp:5005
// Then, from Android Studio: Run > Debug > Edit configurations...
// On the left, click on '+', "Remote", with:
// Host: localhost
// Port: 5005
// Then click on "Debug"
#endif
// adb -s P7C0218510000537 shell CLASSPATH=/data/local/tmp/scrcpy-server app_process / com.genymobile.scrcpy.Server 0 8000000 false
// mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0"
// 这条adb命令是阻塞运行的m_serverProcess进程不会退出了
m_serverProcess.execute(m_params.serial, args);
return true;
}
bool Server::start(Server::ServerParams params)
{
m_params = params;
m_serverStartStep = SSS_PUSH;
return startServerByStep();
}
bool Server::connectTo()
{
if (SSS_RUNNING != m_serverStartStep) {
qWarning("server not run");
return false;
}
if (!m_tunnelForward && !m_videoSocket) {
startAcceptTimeoutTimer();
return true;
}
startConnectTimeoutTimer();
return true;
}
bool Server::isReverse()
{
return !m_tunnelForward;
}
Server::ServerParams Server::getParams()
{
return m_params;
}
void Server::timerEvent(QTimerEvent *event)
{
if (event && m_acceptTimeoutTimer == event->timerId()) {
stopAcceptTimeoutTimer();
emit connectToResult(false, "", QSize());
} else if (event && m_connectTimeoutTimer == event->timerId()) {
onConnectTimer();
}
}
VideoSocket *Server::getVideoSocket()
{
return m_videoSocket;
}
QTcpSocket *Server::getControlSocket()
{
return m_controlSocket;
}
void Server::stop()
{
if (m_tunnelForward) {
stopConnectTimeoutTimer();
} else {
stopAcceptTimeoutTimer();
}
if (m_videoSocket) {
m_videoSocket->close();
m_videoSocket->deleteLater();
}
if (m_controlSocket) {
m_controlSocket->close();
m_controlSocket->deleteLater();
}
// ignore failure
m_serverProcess.kill();
if (m_tunnelEnabled) {
if (m_tunnelForward) {
disableTunnelForward();
} else {
disableTunnelReverse();
}
m_tunnelForward = false;
m_tunnelEnabled = false;
}
m_serverSocket.close();
}
bool Server::startServerByStep()
{
bool stepSuccess = false;
// push, enable tunnel et start the server
if (SSS_NULL != m_serverStartStep) {
switch (m_serverStartStep) {
case SSS_PUSH:
stepSuccess = pushServer();
break;
case SSS_ENABLE_TUNNEL_REVERSE:
stepSuccess = enableTunnelReverse();
break;
case SSS_ENABLE_TUNNEL_FORWARD:
stepSuccess = enableTunnelForward();
break;
case SSS_EXECUTE_SERVER:
// server will connect to our server socket
stepSuccess = execute();
break;
default:
break;
}
}
if (!stepSuccess) {
emit serverStartResult(false);
}
return stepSuccess;
}
bool Server::readInfo(VideoSocket *videoSocket, QString &deviceName, QSize &size)
{
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
if (videoSocket->bytesAvailable() <= (DEVICE_NAME_FIELD_LENGTH + 4)) {
videoSocket->waitForReadyRead(300);
}
qint64 len = videoSocket->read((char *)buf, sizeof(buf));
if (len < DEVICE_NAME_FIELD_LENGTH + 4) {
qInfo("Could not retrieve device information");
return false;
}
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
// strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
// and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
deviceName = (char *)buf;
size.setWidth((buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1]);
size.setHeight((buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3]);
return true;
}
void Server::startAcceptTimeoutTimer()
{
stopAcceptTimeoutTimer();
m_acceptTimeoutTimer = startTimer(1000);
}
void Server::stopAcceptTimeoutTimer()
{
if (m_acceptTimeoutTimer) {
killTimer(m_acceptTimeoutTimer);
m_acceptTimeoutTimer = 0;
}
}
void Server::startConnectTimeoutTimer()
{
stopConnectTimeoutTimer();
m_connectTimeoutTimer = startTimer(100);
}
void Server::stopConnectTimeoutTimer()
{
if (m_connectTimeoutTimer) {
killTimer(m_connectTimeoutTimer);
m_connectTimeoutTimer = 0;
}
m_connectCount = 0;
}
void Server::onConnectTimer()
{
// device server need time to start
// 这里连接太早时间不够导致安卓监听socket还没有建立readInfo会失败所以采取定时重试策略
// 每隔100ms尝试一次最多尝试MAX_CONNECT_COUNT次
QString deviceName;
QSize deviceSize;
bool success = false;
VideoSocket *videoSocket = new VideoSocket();
QTcpSocket *controlSocket = new QTcpSocket();
videoSocket->connectToHost(QHostAddress::LocalHost, m_params.localPort);
if (!videoSocket->waitForConnected(1000)) {
// 连接到adb很快的这里失败不重试
m_connectCount = MAX_CONNECT_COUNT;
qWarning("video socket connect to server failed");
goto result;
}
controlSocket->connectToHost(QHostAddress::LocalHost, m_params.localPort);
if (!controlSocket->waitForConnected(1000)) {
// 连接到adb很快的这里失败不重试
m_connectCount = MAX_CONNECT_COUNT;
qWarning("control socket connect to server failed");
goto result;
}
if (QTcpSocket::ConnectedState == videoSocket->state()) {
// connect will success even if devices offline, recv data is real connect success
// because connect is to pc adb server
videoSocket->waitForReadyRead(1000);
// devices will send 1 byte first on tunnel forward mode
QByteArray data = videoSocket->read(1);
if (!data.isEmpty() && readInfo(videoSocket, deviceName, deviceSize)) {
success = true;
goto result;
} else {
qWarning("video socket connect to server read device info failed, try again");
goto result;
}
} else {
qWarning("connect to server failed");
m_connectCount = MAX_CONNECT_COUNT;
goto result;
}
result:
if (success) {
stopConnectTimeoutTimer();
m_videoSocket = videoSocket;
m_controlSocket = controlSocket;
// we don't need the adb tunnel anymore
disableTunnelForward();
m_tunnelEnabled = false;
m_restartCount = 0;
emit connectToResult(success, deviceName, deviceSize);
return;
}
if (videoSocket) {
videoSocket->deleteLater();
}
if (controlSocket) {
controlSocket->deleteLater();
}
if (MAX_CONNECT_COUNT <= m_connectCount++) {
stopConnectTimeoutTimer();
stop();
if (MAX_RESTART_COUNT > m_restartCount++) {
qWarning("restart server auto");
start(m_params);
} else {
m_restartCount = 0;
emit connectToResult(false);
}
}
}
void Server::onWorkProcessResult(AdbProcess::ADB_EXEC_RESULT processResult)
{
if (sender() == &m_workProcess) {
if (SSS_NULL != m_serverStartStep) {
switch (m_serverStartStep) {
case SSS_PUSH:
if (AdbProcess::AER_SUCCESS_EXEC == processResult) {
if (m_params.useReverse) {
m_serverStartStep = SSS_ENABLE_TUNNEL_REVERSE;
} else {
m_tunnelForward = true;
m_serverStartStep = SSS_ENABLE_TUNNEL_FORWARD;
}
startServerByStep();
} else if (AdbProcess::AER_SUCCESS_START != processResult) {
qCritical("adb push failed");
m_serverStartStep = SSS_NULL;
emit serverStartResult(false);
}
break;
case SSS_ENABLE_TUNNEL_REVERSE:
if (AdbProcess::AER_SUCCESS_EXEC == processResult) {
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no need to
// try to connect until the server socket is listening on the device.
m_serverSocket.setMaxPendingConnections(2);
if (!m_serverSocket.listen(QHostAddress::LocalHost, m_params.localPort)) {
qCritical() << QString("Could not listen on port %1").arg(m_params.localPort).toStdString().c_str();
m_serverStartStep = SSS_NULL;
disableTunnelReverse();
emit serverStartResult(false);
break;
}
m_serverStartStep = SSS_EXECUTE_SERVER;
startServerByStep();
} else if (AdbProcess::AER_SUCCESS_START != processResult) {
// 有一些设备reverse会报错more than o'ne deviceadb的bug
// https://github.com/Genymobile/scrcpy/issues/5
qCritical("adb reverse failed");
m_tunnelForward = true;
m_serverStartStep = SSS_ENABLE_TUNNEL_FORWARD;
startServerByStep();
}
break;
case SSS_ENABLE_TUNNEL_FORWARD:
if (AdbProcess::AER_SUCCESS_EXEC == processResult) {
m_serverStartStep = SSS_EXECUTE_SERVER;
startServerByStep();
} else if (AdbProcess::AER_SUCCESS_START != processResult) {
qCritical("adb forward failed");
m_serverStartStep = SSS_NULL;
emit serverStartResult(false);
}
break;
default:
break;
}
}
}
if (sender() == &m_serverProcess) {
if (SSS_EXECUTE_SERVER == m_serverStartStep) {
if (AdbProcess::AER_SUCCESS_START == processResult) {
m_serverStartStep = SSS_RUNNING;
m_tunnelEnabled = true;
emit serverStartResult(true);
} else if (AdbProcess::AER_ERROR_START == processResult) {
if (!m_tunnelForward) {
m_serverSocket.close();
disableTunnelReverse();
} else {
disableTunnelForward();
}
qCritical("adb shell start server failed");
m_serverStartStep = SSS_NULL;
emit serverStartResult(false);
}
} else if (SSS_RUNNING == m_serverStartStep) {
m_serverStartStep = SSS_NULL;
emit onServerStop();
}
}
}

View file

@ -1,101 +0,0 @@
#ifndef SERVER_H
#define SERVER_H
#include <QObject>
#include <QPointer>
#include <QSize>
#include "adbprocess.h"
#include "tcpserver.h"
#include "videosocket.h"
class Server : public QObject
{
Q_OBJECT
enum SERVER_START_STEP
{
SSS_NULL,
SSS_PUSH,
SSS_ENABLE_TUNNEL_REVERSE,
SSS_ENABLE_TUNNEL_FORWARD,
SSS_EXECUTE_SERVER,
SSS_RUNNING,
};
public:
struct ServerParams
{
QString serial = ""; // 设备序列号
quint16 localPort = 27183; // reverse时本地监听端口
quint16 maxSize = 720; // 视频分辨率
quint32 bitRate = 8000000; // 视频比特率
quint32 maxFps = 60; // 视频最大帧率
QString crop = "-"; // 视频裁剪
bool control = true; // 安卓端是否接收键鼠控制
bool useReverse = true; // true:先使用adb reverse失败后自动使用adb forwardfalse:直接使用adb forward
int lockVideoOrientation = -1; // 是否锁定视频方向
int stayAwake = false; // 是否保持唤醒
};
explicit Server(QObject *parent = nullptr);
virtual ~Server();
bool start(Server::ServerParams params);
bool connectTo();
bool isReverse();
Server::ServerParams getParams();
VideoSocket *getVideoSocket();
QTcpSocket *getControlSocket();
void stop();
signals:
void serverStartResult(bool success);
void connectToResult(bool success, const QString &deviceName = "", const QSize &size = QSize());
void onServerStop();
private slots:
void onWorkProcessResult(AdbProcess::ADB_EXEC_RESULT processResult);
protected:
void timerEvent(QTimerEvent *event);
private:
const QString &getServerPath();
bool pushServer();
bool enableTunnelReverse();
bool disableTunnelReverse();
bool enableTunnelForward();
bool disableTunnelForward();
bool execute();
bool startServerByStep();
bool readInfo(VideoSocket *videoSocket, QString &deviceName, QSize &size);
void startAcceptTimeoutTimer();
void stopAcceptTimeoutTimer();
void startConnectTimeoutTimer();
void stopConnectTimeoutTimer();
void onConnectTimer();
private:
QString m_serverPath = "";
AdbProcess m_workProcess;
AdbProcess m_serverProcess;
TcpServer m_serverSocket; // only used if !tunnel_forward
QPointer<VideoSocket> m_videoSocket = Q_NULLPTR;
QPointer<QTcpSocket> m_controlSocket = Q_NULLPTR;
bool m_tunnelEnabled = false;
bool m_tunnelForward = false; // use "adb forward" instead of "adb reverse"
int m_acceptTimeoutTimer = 0;
int m_connectTimeoutTimer = 0;
quint32 m_connectCount = 0;
quint32 m_restartCount = 0;
QString m_deviceName = "";
QSize m_deviceSize = QSize();
ServerParams m_params;
SERVER_START_STEP m_serverStartStep = SSS_NULL;
};
#endif // SERVER_H

View file

@ -1,9 +0,0 @@
HEADERS += \
$$PWD/server.h \
$$PWD/tcpserver.h \
$$PWD/videosocket.h
SOURCES += \
$$PWD/server.cpp \
$$PWD/tcpserver.cpp \
$$PWD/videosocket.cpp

View file

@ -1,22 +0,0 @@
#include "tcpserver.h"
#include "videosocket.h"
TcpServer::TcpServer(QObject *parent) : QTcpServer(parent) {}
TcpServer::~TcpServer() {}
void TcpServer::incomingConnection(qintptr handle)
{
if (m_isVideoSocket) {
VideoSocket *socket = new VideoSocket();
socket->setSocketDescriptor(handle);
addPendingConnection(socket);
// next is control socket
m_isVideoSocket = false;
} else {
QTcpSocket *socket = new QTcpSocket();
socket->setSocketDescriptor(handle);
addPendingConnection(socket);
}
}

View file

@ -1,20 +0,0 @@
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QTcpServer>
class TcpServer : public QTcpServer
{
Q_OBJECT
public:
explicit TcpServer(QObject *parent = nullptr);
virtual ~TcpServer();
protected:
virtual void incomingConnection(qintptr handle);
private:
bool m_isVideoSocket = true;
};
#endif // TCPSERVER_H

View file

@ -1,81 +0,0 @@
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include "qscrcpyevent.h"
#include "videosocket.h"
VideoSocket::VideoSocket(QObject *parent) : QTcpSocket(parent)
{
connect(this, &VideoSocket::readyRead, this, &VideoSocket::onReadyRead);
connect(this, &VideoSocket::aboutToClose, this, &VideoSocket::quitNotify);
connect(this, &VideoSocket::disconnected, this, &VideoSocket::quitNotify);
}
VideoSocket::~VideoSocket()
{
quitNotify();
}
qint32 VideoSocket::subThreadRecvData(quint8 *buf, qint32 bufSize)
{
// this function cant call in main thread
Q_ASSERT(QCoreApplication::instance()->thread() != QThread::currentThread());
if (m_quit) {
return 0;
}
QMutexLocker locker(&m_mutex);
m_buffer = buf;
m_bufferSize = bufSize;
m_dataSize = 0;
// post event
VideoSocketEvent *getDataEvent = new VideoSocketEvent();
QCoreApplication::postEvent(this, getDataEvent);
// wait
while (!m_recvData) {
m_recvDataCond.wait(&m_mutex);
}
m_recvData = false;
return m_dataSize;
}
bool VideoSocket::event(QEvent *event)
{
if (static_cast<QScrcpyEvent::Type>(event->type()) == QScrcpyEvent::VideoSocket) {
onReadyRead();
return true;
}
return QTcpSocket::event(event);
}
void VideoSocket::onReadyRead()
{
QMutexLocker locker(&m_mutex);
if (m_buffer && m_bufferSize <= bytesAvailable()) {
// recv data
qint64 readSize = qMin(bytesAvailable(), (qint64)m_bufferSize);
m_dataSize = read((char *)m_buffer, readSize);
m_buffer = Q_NULLPTR;
m_bufferSize = 0;
m_recvData = true;
m_recvDataCond.wakeOne();
}
}
void VideoSocket::quitNotify()
{
m_quit = true;
QMutexLocker locker(&m_mutex);
if (m_buffer) {
m_buffer = Q_NULLPTR;
m_bufferSize = 0;
m_recvData = true;
m_dataSize = 0;
m_recvDataCond.wakeOne();
}
}

View file

@ -1,35 +0,0 @@
#ifndef VIDEOSOCKET_H
#define VIDEOSOCKET_H
#include <QEvent>
#include <QMutex>
#include <QTcpSocket>
#include <QWaitCondition>
class VideoSocket : public QTcpSocket
{
Q_OBJECT
public:
explicit VideoSocket(QObject *parent = nullptr);
virtual ~VideoSocket();
qint32 subThreadRecvData(quint8 *buf, qint32 bufSize);
protected:
bool event(QEvent *event);
protected slots:
void onReadyRead();
void quitNotify();
private:
QMutex m_mutex;
QWaitCondition m_recvDataCond;
bool m_recvData = false;
quint8 *m_buffer = Q_NULLPTR;
qint32 m_bufferSize = 0;
qint32 m_dataSize = 0;
bool m_quit = false;
};
#endif // VIDEOSOCKET_H

View file

@ -1,361 +0,0 @@
#include <QDebug>
#include <QTime>
#include "compat.h"
#include "decoder.h"
#include "recorder.h"
#include "stream.h"
#include "videosocket.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_MAX
typedef qint32 (*ReadPacketFunc)(void *, quint8 *, qint32);
Stream::Stream(QObject *parent) : QThread(parent) {}
Stream::~Stream() {}
static void avLogCallback(void *avcl, int level, const char *fmt, va_list vl)
{
Q_UNUSED(avcl)
Q_UNUSED(vl)
QString localFmt = QString::fromUtf8(fmt);
localFmt.prepend("[FFmpeg] ");
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
qFatal("%s", localFmt.toUtf8().data());
break;
case AV_LOG_ERROR:
qCritical() << localFmt.toUtf8();
break;
case AV_LOG_WARNING:
qWarning() << localFmt.toUtf8();
break;
case AV_LOG_INFO:
qInfo() << localFmt.toUtf8();
break;
case AV_LOG_DEBUG:
// qDebug() << localFmt.toUtf8();
break;
}
// do not forward others, which are too verbose
return;
}
bool Stream::init()
{
#ifdef QTSCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all();
#endif
if (avformat_network_init()) {
return false;
}
av_log_set_callback(avLogCallback);
return true;
}
void Stream::deInit()
{
avformat_network_deinit(); // ignore failure
}
void Stream::setDecoder(Decoder *decoder)
{
m_decoder = decoder;
}
static quint32 bufferRead32be(quint8 *buf)
{
return static_cast<quint32>((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
}
static quint64 bufferRead64be(quint8 *buf)
{
quint32 msb = bufferRead32be(buf);
quint32 lsb = bufferRead32be(&buf[4]);
return (static_cast<quint64>(msb) << 32) | lsb;
}
void Stream::setVideoSocket(VideoSocket *videoSocket)
{
m_videoSocket = videoSocket;
}
void Stream::setRecoder(Recorder *recorder)
{
m_recorder = recorder;
}
qint32 Stream::recvData(quint8 *buf, qint32 bufSize)
{
if (!buf) {
return 0;
}
if (m_videoSocket) {
qint32 len = m_videoSocket->subThreadRecvData(buf, bufSize);
return len;
}
return 0;
}
bool Stream::startDecode()
{
if (!m_videoSocket) {
return false;
}
start();
return true;
}
void Stream::stopDecode()
{
if (m_decoder) {
m_decoder->interrupt();
}
wait();
}
void Stream::run()
{
AVCodec *codec = Q_NULLPTR;
m_codecCtx = Q_NULLPTR;
m_parser = Q_NULLPTR;
// codec
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
qCritical("H.264 decoder not found");
goto runQuit;
}
// codeCtx
m_codecCtx = avcodec_alloc_context3(codec);
if (!m_codecCtx) {
qCritical("Could not allocate codec context");
goto runQuit;
}
if (m_decoder && !m_decoder->open(codec)) {
qCritical("Could not open m_decoder");
goto runQuit;
}
if (m_recorder) {
if (!m_recorder->open(codec)) {
qCritical("Could not open recorder");
goto runQuit;
}
if (!m_recorder->startRecorder()) {
qCritical("Could not start recorder");
goto runQuit;
}
}
m_parser = av_parser_init(AV_CODEC_ID_H264);
if (!m_parser) {
qCritical("Could not initialize parser");
goto runQuit;
}
// We must only pass complete frames to av_parser_parse2()!
// It's more complicated, but this allows to reduce the latency by 1 frame!
m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
for (;;) {
AVPacket packet;
bool ok = recvPacket(&packet);
if (!ok) {
// end of stream
break;
}
ok = pushPacket(&packet);
av_packet_unref(&packet);
if (!ok) {
// cannot process packet (error already logged)
break;
}
}
qDebug("End of frames");
if (m_hasPending) {
av_packet_unref(&m_pending);
}
av_parser_close(m_parser);
runQuit:
if (m_recorder) {
if (m_recorder->isRunning()) {
m_recorder->stopRecorder();
m_recorder->wait();
}
m_recorder->close();
}
if (m_decoder) {
m_decoder->close();
}
if (m_codecCtx) {
avcodec_free_context(&m_codecCtx);
}
emit onStreamStop();
}
bool Stream::recvPacket(AVPacket *packet)
{
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
quint8 header[HEADER_SIZE];
qint32 r = recvData(header, HEADER_SIZE);
if (r < HEADER_SIZE) {
return false;
}
quint64 pts = bufferRead64be(header);
quint32 len = bufferRead32be(&header[8]);
Q_ASSERT(pts == NO_PTS || (pts & 0x8000000000000000) == 0);
Q_ASSERT(len);
if (av_new_packet(packet, static_cast<int>(len))) {
qCritical("Could not allocate packet");
return false;
}
r = recvData(packet->data, static_cast<qint32>(len));
if (r < 0 || static_cast<quint32>(r) < len) {
av_packet_unref(packet);
return false;
}
packet->pts = pts != NO_PTS ? static_cast<int64_t>(pts) : static_cast<int64_t>(AV_NOPTS_VALUE);
return true;
}
bool Stream::pushPacket(AVPacket *packet)
{
bool isConfig = packet->pts == AV_NOPTS_VALUE;
// A config packet must not be decoded immetiately (it contains no
// frame); instead, it must be concatenated with the future data packet.
if (m_hasPending || isConfig) {
qint32 offset;
if (m_hasPending) {
offset = m_pending.size;
if (av_grow_packet(&m_pending, packet->size)) {
qCritical("Could not grow packet");
return false;
}
} else {
offset = 0;
if (av_new_packet(&m_pending, packet->size)) {
qCritical("Could not create packet");
return false;
}
m_hasPending = true;
}
memcpy(m_pending.data + offset, packet->data, static_cast<unsigned int>(packet->size));
if (!isConfig) {
// prepare the concat packet to send to the decoder
m_pending.pts = packet->pts;
m_pending.dts = packet->dts;
m_pending.flags = packet->flags;
packet = &m_pending;
}
}
if (isConfig) {
// config packet
bool ok = processConfigPacket(packet);
if (!ok) {
return false;
}
} else {
// data packet
bool ok = parse(packet);
if (m_hasPending) {
// the pending packet must be discarded (consumed or error)
m_hasPending = false;
av_packet_unref(&m_pending);
}
if (!ok) {
return false;
}
}
return true;
}
bool Stream::processConfigPacket(AVPacket *packet)
{
if (m_recorder && !m_recorder->push(packet)) {
qCritical("Could not send config packet to recorder");
return false;
}
return true;
}
bool Stream::parse(AVPacket *packet)
{
quint8 *inData = packet->data;
int inLen = packet->size;
quint8 *outData = Q_NULLPTR;
int outLen = 0;
int r = av_parser_parse2(m_parser, m_codecCtx, &outData, &outLen, inData, inLen, AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
// PARSER_FLAG_COMPLETE_FRAMES is set
Q_ASSERT(r == inLen);
(void)r;
Q_ASSERT(outLen == inLen);
if (m_parser->key_frame == 1) {
packet->flags |= AV_PKT_FLAG_KEY;
}
bool ok = processFrame(packet);
if (!ok) {
qCritical("Could not process frame");
return false;
}
return true;
}
bool Stream::processFrame(AVPacket *packet)
{
if (m_decoder && !m_decoder->push(packet)) {
return false;
}
if (m_recorder) {
packet->dts = packet->pts;
if (!m_recorder->push(packet)) {
qCritical("Could not send packet to recorder");
return false;
}
}
return true;
}

View file

@ -1,59 +0,0 @@
#ifndef STREAM_H
#define STREAM_H
#include <QPointer>
#include <QThread>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
class VideoSocket;
class Recorder;
class Decoder;
class Stream : public QThread
{
Q_OBJECT
public:
Stream(QObject *parent = Q_NULLPTR);
virtual ~Stream();
public:
static bool init();
static void deInit();
void setDecoder(Decoder *decoder);
void setRecoder(Recorder *recorder);
void setVideoSocket(VideoSocket *deviceSocket);
qint32 recvData(quint8 *buf, qint32 bufSize);
bool startDecode();
void stopDecode();
signals:
void onStreamStop();
protected:
void run();
bool recvPacket(AVPacket *packet);
bool pushPacket(AVPacket *packet);
bool processConfigPacket(AVPacket *packet);
bool parse(AVPacket *packet);
bool processFrame(AVPacket *packet);
private:
QPointer<VideoSocket> m_videoSocket;
// for recorder
Recorder *m_recorder = Q_NULLPTR;
Decoder *m_decoder = Q_NULLPTR;
AVCodecContext *m_codecCtx = Q_NULLPTR;
AVCodecParserContext *m_parser = Q_NULLPTR;
// successive packets may need to be concatenated, until a non-config
// packet is available
bool m_hasPending = false;
AVPacket m_pending;
};
#endif // STREAM_H

View file

@ -1,6 +0,0 @@
HEADERS += \
$$PWD/stream.h
SOURCES += \
$$PWD/stream.cpp

View file

@ -1,11 +0,0 @@
SOURCES += \
$$PWD/videoform.cpp \
$$PWD/toolform.cpp
HEADERS += \
$$PWD/videoform.h \
$$PWD/toolform.h
FORMS += \
$$PWD/videoform.ui \
$$PWD/toolform.ui

View file

@ -1,88 +0,0 @@
#ifndef VIDEOFORM_H
#define VIDEOFORM_H
#include <QPointer>
#include <QWidget>
namespace Ui
{
class videoForm;
}
struct AVFrame;
class ToolForm;
class Device;
class FileHandler;
class QYUVOpenGLWidget;
class QLabel;
class VideoForm : public QWidget
{
Q_OBJECT
public:
explicit VideoForm(bool framelessWindow = false, bool skin = true, QWidget *parent = 0);
~VideoForm();
void staysOnTop(bool top = true);
void updateShowSize(const QSize &newSize);
void updateRender(const AVFrame *frame);
void setDevice(Device *device);
QRect getGrabCursorRect();
const QSize &frameSize();
void resizeSquare();
void removeBlackRect();
void showFPS(bool show);
public slots:
void onSwitchFullScreen();
void updateFPS(quint32 fps);
private:
void updateStyleSheet(bool vertical);
QMargins getMargins(bool vertical);
void initUI();
void showToolForm(bool show = true);
void moveCenter();
void installShortcut();
QRect getScreenRect();
bool checkTrialExpire();
protected:
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
void paintEvent(QPaintEvent *);
void showEvent(QShowEvent *event);
void resizeEvent(QResizeEvent *event);
void closeEvent(QCloseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dropEvent(QDropEvent *event);
private:
// ui
Ui::videoForm *ui;
QPointer<ToolForm> m_toolForm;
QPointer<QWidget> m_loadingWidget;
QPointer<QYUVOpenGLWidget> m_videoWidget;
QPointer<QLabel> m_fpsLabel;
//inside member
QSize m_frameSize;
QPoint m_dragPosition;
float m_widthHeightRatio = 0.5f;
bool m_skin = true;
QPoint m_fullScreenBeforePos;
//outside member
QPointer<Device> m_device;
};
#endif // VIDEOFORM_H

View file

@ -1,297 +0,0 @@
#include <QDebug>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include "devicemanage.h"
#include "server.h"
#include "videoform.h"
#define DM_MAX_DEVICES_NUM 1000
DeviceManage::DeviceManage(QObject *parent) : QObject(parent) {}
DeviceManage::~DeviceManage() {}
bool DeviceManage::connectDevice(Device::DeviceParams params)
{
if (params.serial.trimmed().isEmpty()) {
return false;
}
if (m_devices.contains(params.serial)) {
return false;
}
if (DM_MAX_DEVICES_NUM < m_devices.size()) {
qInfo("over the maximum number of connections");
return false;
}
/*
// 没有必要分配端口都用27183即可连接建立以后server会释放监听的
quint16 port = 0;
if (params.useReverse) {
port = getFreePort();
if (0 == port) {
qInfo("no port available, automatically switch to forward");
params.useReverse = false;
} else {
params.localPort = port;
qInfo("free port %d", port);
}
}
*/
Device *device = new Device(params);
connect(device, &Device::deviceDisconnect, this, &DeviceManage::onDeviceDisconnect);
connect(device, &Device::controlStateChange, this, &DeviceManage::onControlStateChange);
m_devices[params.serial] = device;
if (!m_script.isEmpty()) {
device->updateScript(m_script);
}
return true;
}
void DeviceManage::updateScript(QString script)
{
m_script = script;
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
if (i.value()) {
i.value()->updateScript(script);
}
}
}
bool DeviceManage::staysOnTop(const QString &serial)
{
if (!serial.isEmpty() && m_devices.contains(serial)) {
auto it = m_devices.find(serial);
if (!it->data()) {
return false;
}
if (!it->data()->getVideoForm()) {
return false;
}
it->data()->getVideoForm()->staysOnTop();
}
return true;
}
void DeviceManage::showFPS(const QString &serial, bool show)
{
if (!serial.isEmpty() && m_devices.contains(serial)) {
auto it = m_devices.find(serial);
if (!it->data()) {
return;
}
if (!it->data()->getVideoForm()) {
return;
}
it->data()->getVideoForm()->showFPS(show);
}
return;
}
bool DeviceManage::disconnectDevice(const QString &serial)
{
bool ret = false;
if (!serial.isEmpty() && m_devices.contains(serial)) {
auto it = m_devices.find(serial);
if (it->data()) {
delete it->data();
ret = true;
}
}
return ret;
}
void DeviceManage::disconnectAllDevice()
{
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
if (i.value()) {
delete i.value();
}
}
}
void DeviceManage::setGroupControlSignals(Device *host, Device *client, bool install)
{
if (!host || !client) {
return;
}
if (install) {
connect(host, &Device::postGoBack, client, &Device::postGoBack);
connect(host, &Device::postGoHome, client, &Device::postGoHome);
connect(host, &Device::postGoMenu, client, &Device::postGoMenu);
connect(host, &Device::postAppSwitch, client, &Device::postAppSwitch);
connect(host, &Device::postPower, client, &Device::postPower);
connect(host, &Device::postVolumeUp, client, &Device::postVolumeUp);
connect(host, &Device::postVolumeDown, client, &Device::postVolumeDown);
connect(host, &Device::setScreenPowerMode, client, &Device::setScreenPowerMode);
connect(host, &Device::expandNotificationPanel, client, &Device::expandNotificationPanel);
connect(host, &Device::collapseNotificationPanel, client, &Device::collapseNotificationPanel);
connect(host, &Device::postBackOrScreenOn, client, &Device::postBackOrScreenOn);
connect(host, &Device::postTextInput, client, &Device::postTextInput);
connect(host, &Device::setDeviceClipboard, client, &Device::setDeviceClipboard);
connect(host, &Device::clipboardPaste, client, &Device::clipboardPaste);
connect(host, &Device::pushFileRequest, client, &Device::pushFileRequest);
connect(host, &Device::installApkRequest, client, &Device::installApkRequest);
connect(host, &Device::screenshot, client, &Device::screenshot);
connect(host, &Device::showTouch, client, &Device::showTouch);
} else {
disconnect(host, &Device::postGoBack, client, &Device::postGoBack);
disconnect(host, &Device::postGoHome, client, &Device::postGoHome);
disconnect(host, &Device::postGoMenu, client, &Device::postGoMenu);
disconnect(host, &Device::postAppSwitch, client, &Device::postAppSwitch);
disconnect(host, &Device::postPower, client, &Device::postPower);
disconnect(host, &Device::postVolumeUp, client, &Device::postVolumeUp);
disconnect(host, &Device::postVolumeDown, client, &Device::postVolumeDown);
disconnect(host, &Device::setScreenPowerMode, client, &Device::setScreenPowerMode);
disconnect(host, &Device::expandNotificationPanel, client, &Device::expandNotificationPanel);
disconnect(host, &Device::collapseNotificationPanel, client, &Device::collapseNotificationPanel);
disconnect(host, &Device::postBackOrScreenOn, client, &Device::postBackOrScreenOn);
disconnect(host, &Device::postTextInput, client, &Device::postTextInput);
disconnect(host, &Device::setDeviceClipboard, client, &Device::setDeviceClipboard);
disconnect(host, &Device::clipboardPaste, client, &Device::clipboardPaste);
disconnect(host, &Device::pushFileRequest, client, &Device::pushFileRequest);
disconnect(host, &Device::installApkRequest, client, &Device::installApkRequest);
disconnect(host, &Device::screenshot, client, &Device::screenshot);
disconnect(host, &Device::showTouch, client, &Device::showTouch);
}
}
void DeviceManage::setGroupControlHost(Device *host, bool install)
{
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
if (!i.value()) {
continue;
}
if (i.value() == host) {
continue;
}
if (install) {
if (host) {
setGroupControlSignals(host, i.value(), true);
}
emit i.value()->setControlState(i.value(), Device::GroupControlState::GCS_CLIENT);
} else {
if (host) {
setGroupControlSignals(host, i.value(), false);
}
emit i.value()->setControlState(i.value(), Device::GroupControlState::GCS_FREE);
}
}
}
void DeviceManage::onDeviceDisconnect(QString serial)
{
if (!serial.isEmpty() && m_devices.contains(serial)) {
if (m_devices[serial]->controlState() == Device::GroupControlState::GCS_HOST) {
setGroupControlHost(nullptr, false);
}
m_devices.remove(serial);
}
}
void DeviceManage::onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState)
{
if (!device) {
return;
}
// free to host
if (oldState == Device::GroupControlState::GCS_FREE && newState == Device::GroupControlState::GCS_HOST) {
// install direct control signals
setGroupControlHost(device, true);
// install convert control signals(frameSize need convert)
connect(device, &Device::mouseEvent, this, &DeviceManage::onMouseEvent, Qt::UniqueConnection);
connect(device, &Device::wheelEvent, this, &DeviceManage::onWheelEvent, Qt::UniqueConnection);
connect(device, &Device::keyEvent, this, &DeviceManage::onKeyEvent, Qt::UniqueConnection);
return;
}
// host to free
if (oldState == Device::GroupControlState::GCS_HOST && newState == Device::GroupControlState::GCS_FREE) {
// uninstall direct control signals
setGroupControlHost(device, false);
// uninstall convert control signals(frameSize need convert)
disconnect(device, &Device::mouseEvent, this, &DeviceManage::onMouseEvent);
disconnect(device, &Device::wheelEvent, this, &DeviceManage::onWheelEvent);
disconnect(device, &Device::keyEvent, this, &DeviceManage::onKeyEvent);
return;
}
}
void DeviceManage::onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize)
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
if (!i.value()) {
continue;
}
if (i.value() == sender()) {
continue;
}
// neend convert frameSize to its frameSize
emit i.value()->mouseEvent(from, i.value()->frameSize(), showSize);
}
}
void DeviceManage::onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize)
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
if (!i.value()) {
continue;
}
if (i.value() == sender()) {
continue;
}
// neend convert frameSize to its frameSize
emit i.value()->wheelEvent(from, i.value()->frameSize(), showSize);
}
}
void DeviceManage::onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize)
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
if (!i.value()) {
continue;
}
if (i.value() == sender()) {
continue;
}
// neend convert frameSize to its frameSize
emit i.value()->keyEvent(from, i.value()->frameSize(), showSize);
}
}
quint16 DeviceManage::getFreePort()
{
quint16 port = m_localPortStart;
while (port < m_localPortStart + DM_MAX_DEVICES_NUM) {
bool used = false;
QMapIterator<QString, QPointer<Device>> i(m_devices);
while (i.hasNext()) {
i.next();
auto device = i.value();
if (device && device->getServer() && device->getServer()->isReverse() && port == device->getServer()->getParams().localPort) {
used = true;
break;
}
}
if (!used) {
return port;
}
port++;
}
return 0;
}

View file

@ -1,46 +0,0 @@
#ifndef DEVICEMANAGE_H
#define DEVICEMANAGE_H
#include <QMap>
#include <QPointer>
#include "device.h"
class DeviceManage : public QObject
{
Q_OBJECT
public:
explicit DeviceManage(QObject *parent = nullptr);
virtual ~DeviceManage();
bool connectDevice(Device::DeviceParams params);
void updateScript(QString script);
bool staysOnTop(const QString &serial);
void showFPS(const QString &serial, bool show);
bool disconnectDevice(const QString &serial);
void disconnectAllDevice();
protected:
void setGroupControlSignals(Device *host, Device *client, bool install);
void setGroupControlHost(Device *host, bool install);
protected slots:
void onDeviceDisconnect(QString serial);
void onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
// neend convert frameSize to its frameSize
void onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
void onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
void onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
private:
quint16 getFreePort();
private:
QMap<QString, QPointer<Device>> m_devices;
quint16 m_localPortStart = 27183;
QString m_script;
};
#endif // DEVICEMANAGE_H

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/devicemanage.h
SOURCES += \
$$PWD/devicemanage.cpp

View file

@ -1,786 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>745</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string notr="true">QtScrcpy</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="useSingleModeCheck">
<property name="text">
<string>Use Simple Mode</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="simpleGroupBox">
<property name="title">
<string>Simple Mode</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QPushButton" name="wifiConnectBtn">
<property name="text">
<string>WIFI Connect</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="usbConnectBtn">
<property name="text">
<string>USB Connect</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>Double click to connect:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="connectedPhoneList">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="configGroupBox">
<property name="title">
<string>Start Config</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QWidget" name="configWidget1" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>bit rate:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="bitRateBox">
<property name="toolTip">
<string/>
</property>
<property name="currentText">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>max size:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="maxSizeBox">
<property name="toolTip">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>record format</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="formatBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget5" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>lock orientation:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="lockOrientationBox"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>record save path:</string>
</property>
<property name="buddy">
<cstring>recordPathEdt</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="recordPathEdt">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectRecordPathBtn">
<property name="text">
<string>select path</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget4" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="gameBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="refreshGameScriptBtn">
<property name="text">
<string>refresh script</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="applyScriptBtn">
<property name="text">
<string>apply</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget3" native="true">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="1">
<widget class="QCheckBox" name="closeScreenCheck">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>screen-off</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="framelessCheck">
<property name="text">
<string>frameless</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="alwaysTopCheck">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>always on top</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="notDisplayCheck">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>background record</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="useReverseCheck">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>reverse connection</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="recordScreenCheck">
<property name="text">
<string>record screen</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QCheckBox" name="fpsCheck">
<property name="text">
<string>show fps</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QCheckBox" name="stayAwakeCheck">
<property name="text">
<string>stay awake</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="usbGroupBox">
<property name="title">
<string>USB line</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_9">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>device name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="userNameEdt">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="updateNameBtn">
<property name="text">
<string>update name</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="usbWidget1" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>device serial:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="serialBox"/>
</item>
<item>
<widget class="QPushButton" name="startServerBtn">
<property name="text">
<string>start server</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopServerBtn">
<property name="text">
<string>stop server</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="usbWidget2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="stopAllServerBtn">
<property name="text">
<string>stop all server</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="updateDevice">
<property name="text">
<string>refresh devices</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="getIPBtn">
<property name="text">
<string>get device IP</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="startAdbdBtn">
<property name="text">
<string>start adbd</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="wirelessGroupBox">
<property name="title">
<string>Wireless</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLineEdit" name="deviceIpEdt">
<property name="text">
<string/>
</property>
<property name="maxLength">
<number>128</number>
</property>
<property name="placeholderText">
<string notr="true">192.168.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>5</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>5</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="devicePortEdt">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="maxLength">
<number>6</number>
</property>
<property name="placeholderText">
<string notr="true">5555</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="wirelessConnectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>wireless connect</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="wirelessDisConnectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>wireless disconnect</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="adbGroupBox">
<property name="title">
<string notr="true">adb</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>adb command:</string>
</property>
<property name="buddy">
<cstring>adbCommandEdt</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="adbCommandEdt">
<property name="text">
<string notr="true">devices</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="adbCommandBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>execute</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopAdbBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>terminate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearOut">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>clear</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTextEdit" name="outEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>140</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="documentTitle">
<string/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>deviceIpEdt</tabstop>
<tabstop>devicePortEdt</tabstop>
<tabstop>wirelessConnectBtn</tabstop>
<tabstop>wirelessDisConnectBtn</tabstop>
<tabstop>adbCommandEdt</tabstop>
<tabstop>adbCommandBtn</tabstop>
<tabstop>stopAdbBtn</tabstop>
<tabstop>clearOut</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -1,5 +0,0 @@
HEADERS += \
$$PWD/iconhelper.h
SOURCES += \
$$PWD/iconhelper.cpp

View file

@ -0,0 +1,443 @@
#include <QPointer>
#include "groupcontroller.h"
#include "videoform.h"
GroupController::GroupController(QObject *parent) : QObject(parent)
{
}
bool GroupController::isHost(const QString &serial)
{
auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
if (!data) {
return true;
}
return static_cast<VideoForm*>(data)->isHost();
}
QSize GroupController::getFrameSize(const QString &serial)
{
auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
if (!data) {
return QSize();
}
return static_cast<VideoForm*>(data)->frameSize();
}
GroupController &GroupController::instance()
{
static GroupController gc;
return gc;
}
void GroupController::updateDeviceState(const QString &serial)
{
if (!m_devices.contains(serial)) {
return;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
if (isHost(serial)) {
device->registerDeviceObserver(this);
} else {
device->deRegisterDeviceObserver(this);
}
}
void GroupController::addDevice(const QString &serial)
{
if (m_devices.contains(serial)) {
return;
}
m_devices.append(serial);
}
void GroupController::removeDevice(const QString &serial)
{
if (!m_devices.contains(serial)) {
return;
}
m_devices.removeOne(serial);
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
if (isHost(serial)) {
device->deRegisterDeviceObserver(this);
}
}
void GroupController::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize);
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->mouseEvent(from, getFrameSize(serial), showSize);
}
}
void GroupController::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize);
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->wheelEvent(from, getFrameSize(serial), showSize);
}
}
void GroupController::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize);
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->keyEvent(from, getFrameSize(serial), showSize);
}
}
void GroupController::postGoBack()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postGoBack();
}
}
void GroupController::postGoHome()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postGoHome();
}
}
void GroupController::postGoMenu()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postGoMenu();
}
}
void GroupController::postAppSwitch()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postAppSwitch();
}
}
void GroupController::postPower()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postPower();
}
}
void GroupController::postVolumeUp()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postVolumeUp();
}
}
void GroupController::postVolumeDown()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postVolumeDown();
}
}
void GroupController::postCopy()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postCopy();
}
}
void GroupController::postCut()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postCut();
}
}
void GroupController::setDisplayPower(bool on)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->setDisplayPower(on);
}
}
void GroupController::expandNotificationPanel()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->expandNotificationPanel();
}
}
void GroupController::collapsePanel()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->collapsePanel();
}
}
void GroupController::postBackOrScreenOn(bool down)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postBackOrScreenOn(down);
}
}
void GroupController::postTextInput(QString &text)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postTextInput(text);
}
}
void GroupController::requestDeviceClipboard()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->requestDeviceClipboard();
}
}
void GroupController::setDeviceClipboard(bool pause)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->setDeviceClipboard(pause);
}
}
void GroupController::clipboardPaste()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->clipboardPaste();
}
}
void GroupController::pushFileRequest(const QString &file, const QString &devicePath)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->pushFileRequest(file, devicePath);
}
}
void GroupController::installApkRequest(const QString &apkFile)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->installApkRequest(apkFile);
}
}
void GroupController::screenshot()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->screenshot();
}
}
void GroupController::showTouch(bool show)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->showTouch(show);
}
}

View file

@ -0,0 +1,56 @@
#ifndef GROUPCONTROLLER_H
#define GROUPCONTROLLER_H
#include <QObject>
#include <QVector>
#include "QtScrcpyCore.h"
class GroupController : public QObject, public qsc::DeviceObserver
{
Q_OBJECT
public:
static GroupController& instance();
void updateDeviceState(const QString& serial);
void addDevice(const QString& serial);
void removeDevice(const QString& serial);
private:
// DeviceObserver
void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) override;
void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) override;
void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) override;
void postGoBack() override;
void postGoHome() override;
void postGoMenu() override;
void postAppSwitch() override;
void postPower() override;
void postVolumeUp() override;
void postVolumeDown() override;
void postCopy() override;
void postCut() override;
void setDisplayPower(bool on) override;
void expandNotificationPanel() override;
void collapsePanel() override;
void postBackOrScreenOn(bool down) override;
void postTextInput(QString &text) override;
void requestDeviceClipboard() override;
void setDeviceClipboard(bool pause = true) override;
void clipboardPaste() override;
void pushFileRequest(const QString &file, const QString &devicePath = "") override;
void installApkRequest(const QString &apkFile) override;
void screenshot() override;
void showTouch(bool show) override;
private:
explicit GroupController(QObject *parent = nullptr);
bool isHost(const QString& serial);
QSize getFrameSize(const QString& serial);
private:
QVector<QString> m_devices;
};
#endif // GROUPCONTROLLER_H

View file

@ -9,10 +9,8 @@
#include "config.h"
#include "dialog.h"
#include "mousetap/mousetap.h"
#include "stream.h"
static Dialog *g_mainDlg = Q_NULLPTR;
static QtMessageHandler g_oldMessageHandler = Q_NULLPTR;
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void installTranslator();
@ -24,21 +22,24 @@ int main(int argc, char *argv[])
{
// set env
#ifdef Q_OS_WIN32
qputenv("QTSCRCPY_ADB_PATH", "../../../../third_party/adb/win/adb.exe");
qputenv("QTSCRCPY_SERVER_PATH", "../../../../third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../../config");
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe");
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
#endif
#ifdef Q_OS_OSX
qputenv("QTSCRCPY_ADB_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../../../../config");
#endif
#ifdef Q_OS_LINUX
qputenv("QTSCRCPY_ADB_PATH", "../../../third_party/adb/linux/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../../third_party/scrcpy-server");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
#endif
g_msgType = covertLogLevel(Config::getInstance().getLogLevel());
@ -54,8 +55,14 @@ int main(int argc, char *argv[])
QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
#endif
QSurfaceFormat varFormat = QSurfaceFormat::defaultFormat();
varFormat.setVersion(2, 0);
varFormat.setProfile(QSurfaceFormat::NoProfile);
@ -70,7 +77,6 @@ int main(int argc, char *argv[])
QSurfaceFormat::setDefaultFormat(varFormat);
g_oldMessageHandler = qInstallMessageHandler(myMessageOutput);
Stream::init();
QApplication a(argc, argv);
// windows下通过qmake VERSION变量或者rc设置版本号和应用名称后这里可以直接拿到
@ -100,25 +106,29 @@ int main(int argc, char *argv[])
file.close();
}
g_mainDlg = new Dialog;
g_mainDlg->setWindowTitle(Config::getInstance().getTitle());
qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath());
g_mainDlg = new Dialog {};
g_mainDlg->show();
qInfo(
"%s",
QObject::tr("This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the "
"following address:")
.toUtf8()
.data());
qInfo() << QString("QtScrcpy %1 <https://github.com/barry-ran/QtScrcpy>").arg(QCoreApplication::applicationVersion()).toUtf8();
qInfo() << QObject::tr("This software is completely open source and free. Use it at your own risk. You can download it at the "
"following address:");
qInfo() << QString("QtScrcpy %1 <https://github.com/barry-ran/QtScrcpy>").arg(QCoreApplication::applicationVersion());
qInfo() << QObject::tr("If you need more professional batch control mirror software, you can try the following software:");
qInfo() << QString(QObject::tr("QuickMirror") + " <https://lrbnfell4p.feishu.cn/drive/folder/KviYfz5uFlpUT8dXgdjccmfUnse>");
qInfo() << QObject::tr("If you need more professional game keymap mirror software, you can try the following software:");
qInfo() << QString(QObject::tr("QuickAssistant") + " <https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh>");
qInfo() << QObject::tr("You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1>");
int ret = a.exec();
delete g_mainDlg;
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
MouseTap::getInstance()->quitMouseEventTap();
#endif
Stream::deInit();
return ret;
}
@ -127,18 +137,28 @@ void installTranslator()
static QTranslator translator;
QLocale locale;
QLocale::Language language = locale.language();
//language = QLocale::English;
if (Config::getInstance().getLanguage() == "zh_CN") {
language = QLocale::Chinese;
} else if (Config::getInstance().getLanguage() == "en_US") {
language = QLocale::English;
}
QString languagePath = ":/i18n/";
switch (language) {
case QLocale::Chinese:
languagePath += "QtScrcpy_zh.qm";
languagePath += "zh_CN.qm";
break;
case QLocale::English:
default:
languagePath += "QtScrcpy_en.qm";
languagePath += "en_US.qm";
break;
}
translator.load(languagePath);
auto loaded = translator.load(languagePath);
if (!loaded) {
qWarning() << "Failed to load translation file:" << languagePath;
}
qApp->installTranslator(&translator);
}
@ -173,12 +193,12 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
g_oldMessageHandler(type, context, msg);
}
// qt log info big than warning?
float fLogLevel = 1.0f * g_msgType;
// Is Qt log level higher than warning?
float fLogLevel = g_msgType;
if (QtInfoMsg == g_msgType) {
fLogLevel = QtDebugMsg + 0.5f;
}
float fLogLevel2 = 1.0f * type;
float fLogLevel2 = type;
if (QtInfoMsg == type) {
fLogLevel2 = QtDebugMsg + 0.5f;
}

View file

@ -157,6 +157,8 @@ void QYUVOpenGLWidget::initializeGL()
void QYUVOpenGLWidget::paintGL()
{
m_shaderProgram.bind();
if (m_needUpdate) {
deInitTextures();
initTextures();
@ -175,6 +177,8 @@ void QYUVOpenGLWidget::paintGL()
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
m_shaderProgram.release();
}
void QYUVOpenGLWidget::resizeGL(int width, int height)

View file

@ -5,11 +5,11 @@
<key>CFBundleDevelopmentRegion</key>
<string>zh-Hans</string>
<key>CFBundleExecutable</key>
<string>@EXECUTABLE@</string>
<string>QtScrcpy</string>
<key>CFBundleGetInfoString</key>
<string>Created by rankun</string>
<key>CFBundleIconFile</key>
<string>@ICON@</string>
<string>QtScrcpy</string>
<key>CFBundleIdentifier</key>
<string>rankun.QtScrcpy</string>
<key>CFBundleInfoDictionaryVersion</key>
@ -19,13 +19,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>${BUNDLE_VERSION}</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<string>${BUNDLE_VERSION}</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSAppleEventsUsageDescription</key>

View file

@ -0,0 +1,51 @@
# 声明ts文件
set(QC_TS_FILES
${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts
${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts
)
# 设置qm文件生成目录
set_source_files_properties(${QC_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
# 引入LinguistTools
find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools REQUIRED)
# qt5_create_translation会依次执行 lupdate更新ts、lrelease更新qm
qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${QC_TS_FILES})
# 自定义目标依赖QM_FILES否则不会生成qm文件
add_custom_target(QC_QM_GENERATOR DEPENDS ${QM_FILES})
# qt5_create_translation的bugcmake clean的时候会删除翻译好的ts文件导致翻译丢失
# qt官方说qt6没问题只用qt6的可以考虑qt5_create_translation
# 网上查到的CLEAN_NO_CUSTOM办法只能在makefile生成器下生效解决不了问题
# https://cmake.org/cmake/help/latest/prop_dir/CLEAN_NO_CUSTOM.html
# set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true)
# 目前唯一的解决办法是每次clean后都手动在git中恢复一下ts文件
#[[
总结:
cmake qt项目下利用cmake脚本有三种方式处理翻译
1. 完全使用qt自带的cmake LinguistTools脚本qt5_create_translation&qt5_add_translation
这两个脚本都满足不了需求:
qt5_add_translation只能根据已有ts文件生成qm文件lrelease不能更新ts文件(lupdate)
qt5_create_translation在cmake clean的时候会删除翻译好的ts文件导致翻译丢失
2. cmake add_custom_command + cmake LinguistTools脚本其实qt5_create_translation内部使用的也是add_custom_command
例如add_custom_command执行lupdate配合qt5_add_translation更新qm
参考https://github.com/maratnek/QtFirstProgrammCMake/blob/2c93b59e2ba85ff6ee0e727487e14003381687d3/CMakeLists.txt
3. 完全使用cmake命令来执行lupdate和lrelease
例如add_custom_command/add_custom_target/execute_process都可以实现执行lupdate和lrelease命令
上面3个方案都有一个共同问题就是翻译文件处理都是和编译绑定在一起的每次编译都会检测执行实际的翻译工作是所有
编程工作都完成以后统一执行一次lupdate、翻译、lrelease就可以了不应该和编译绑定在一起
所以写两个shell脚本lupdate.sh和lrelease.sh来处理比较合适其实非常简单
1. 更新tslupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
2. 手动翻译ts
3. 发布lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
参考文档
1. qt知道qt5_create_translation的bug但是不肯解决只确定了qt6没问题 https://bugreports.qt.io/browse/QTBUG-96549
2. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-add-translation.html
3. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-create-translation.html
4. execute_process 参考https://blog.csdn.net/u010255072/article/details/120326833
5. add_custom_target 参考https://www.cnblogs.com/apocelipes/p/14355460.html

Binary file not shown.

View file

@ -1,473 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>Device</name>
<message>
<source>wait current file transfer to complete</source>
<translation type="vanished">wait current file transfer to complete</translation>
</message>
<message>
<source>file transfer complete</source>
<translation type="vanished">file transfer complete</translation>
</message>
<message>
<source>file transfer failed</source>
<translation type="vanished">file transfer failed</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="184"/>
<source>install apk</source>
<translation>install apk</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="186"/>
<source>file transfer</source>
<translation>file transfer</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="190"/>
<source>wait current %1 to complete</source>
<translation>wait current %1 to complete</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="193"/>
<source>%1 complete, save in %2</source>
<translation>%1 complete, save in %2</translation>
</message>
<message>
<source>%1 complete
save in %2</source>
<translation type="vanished">%1 complete\n save in %2</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="196"/>
<source>%1 failed</source>
<translation>%1 failed</translation>
</message>
</context>
<context>
<name>Dialog</name>
<message>
<location filename="../../dialog.ui" line="562"/>
<source>Wireless</source>
<translation>Wireless</translation>
</message>
<message>
<location filename="../../dialog.ui" line="646"/>
<source>wireless connect</source>
<translation>wireless connect</translation>
</message>
<message>
<location filename="../../dialog.ui" line="662"/>
<source>wireless disconnect</source>
<translation>wireless disconnect</translation>
</message>
<message>
<location filename="../../dialog.ui" line="89"/>
<source>Start Config</source>
<translation>Start Config</translation>
</message>
<message>
<location filename="../../dialog.ui" line="225"/>
<source>record save path:</source>
<translation>record save path:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="242"/>
<location filename="../../dialog.cpp" line="449"/>
<source>select path</source>
<translation>select path</translation>
</message>
<message>
<location filename="../../dialog.ui" line="156"/>
<source>record format</source>
<translation>record format:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="380"/>
<source>record screen</source>
<translation>record screen</translation>
</message>
<message>
<location filename="../../dialog.ui" line="325"/>
<source>frameless</source>
<translation>frameless</translation>
</message>
<message>
<location filename="../../dialog.ui" line="32"/>
<source>Use Simple Mode</source>
<translatorcomment>Use Simple Mode</translatorcomment>
<translation>Use Simple Mode</translation>
</message>
<message>
<location filename="../../dialog.ui" line="42"/>
<source>Simple Mode</source>
<translatorcomment>Simple Mode</translatorcomment>
<translation>Simple Mode</translation>
</message>
<message>
<location filename="../../dialog.ui" line="53"/>
<source>WIFI Connect</source>
<translatorcomment>WIFI Connect</translatorcomment>
<translation>WIFI Connect</translation>
</message>
<message>
<location filename="../../dialog.ui" line="60"/>
<source>USB Connect</source>
<translatorcomment>USB Connect</translatorcomment>
<translation>USB Connect</translation>
</message>
<message>
<location filename="../../dialog.ui" line="69"/>
<source>Double click to connect:</source>
<translation>Double click to connect:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="184"/>
<source>lock orientation:</source>
<translation>lock orientation:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="387"/>
<source>show fps</source>
<translation>show fps</translation>
</message>
<message>
<location filename="../../dialog.ui" line="394"/>
<source>stay awake</source>
<translation>stay awake</translation>
</message>
<message>
<location filename="../../dialog.ui" line="421"/>
<source>device name:</source>
<translatorcomment>device name:</translatorcomment>
<translation>device name:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="438"/>
<source>update name</source>
<translatorcomment>update name</translatorcomment>
<translation>update name</translation>
</message>
<message>
<location filename="../../dialog.ui" line="519"/>
<source>stop all server</source>
<translation>stop all server</translation>
</message>
<message>
<location filename="../../dialog.ui" line="696"/>
<source>adb command:</source>
<translation>adb command:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="732"/>
<source>terminate</source>
<translation>terminate</translation>
</message>
<message>
<location filename="../../dialog.ui" line="719"/>
<source>execute</source>
<translation>execute</translation>
</message>
<message>
<location filename="../../dialog.ui" line="745"/>
<source>clear</source>
<translation>clear</translation>
</message>
<message>
<location filename="../../dialog.ui" line="370"/>
<source>reverse connection</source>
<translation>reverse connection</translation>
</message>
<message>
<source>auto enable</source>
<translation type="vanished">auto enable</translation>
</message>
<message>
<location filename="../../dialog.ui" line="354"/>
<source>background record</source>
<translation>background record</translation>
</message>
<message>
<location filename="../../dialog.ui" line="318"/>
<source>screen-off</source>
<translation>screen-off</translation>
</message>
<message>
<location filename="../../dialog.ui" line="287"/>
<source>apply</source>
<translation>apply</translation>
</message>
<message>
<location filename="../../dialog.ui" line="142"/>
<source>max size:</source>
<translation>max size:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="338"/>
<source>always on top</source>
<translation>always on top</translation>
</message>
<message>
<location filename="../../dialog.ui" line="280"/>
<source>refresh script</source>
<translation>refresh script</translation>
</message>
<message>
<location filename="../../dialog.ui" line="536"/>
<source>get device IP</source>
<translation>get device IP</translation>
</message>
<message>
<location filename="../../dialog.ui" line="407"/>
<source>USB line</source>
<translation>USB line</translation>
</message>
<message>
<location filename="../../dialog.ui" line="491"/>
<source>stop server</source>
<translation>stop server</translation>
</message>
<message>
<location filename="../../dialog.ui" line="481"/>
<source>start server</source>
<translation>start server</translation>
</message>
<message>
<location filename="../../dialog.ui" line="471"/>
<source>device serial:</source>
<translation>device serial:</translation>
</message>
<message>
<source>Config</source>
<translation type="vanished">Config</translation>
</message>
<message>
<location filename="../../dialog.ui" line="125"/>
<source>bit rate:</source>
<translation>bit rate:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="546"/>
<source>start adbd</source>
<translation>start adbd</translation>
</message>
<message>
<location filename="../../dialog.ui" line="526"/>
<source>refresh devices</source>
<translation>refresh devices</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="85"/>
<source>show</source>
<translatorcomment>show</translatorcomment>
<translation>show</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="86"/>
<source>quit</source>
<translatorcomment>quit</translatorcomment>
<translation>quit</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="121"/>
<source>original</source>
<translation>original</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="126"/>
<source>no lock</source>
<translation>no lock</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>warning</source>
<translatorcomment>Warning</translatorcomment>
<translation>Warning</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Quit or set tray?</source>
<translatorcomment>Quit or set tray?</translatorcomment>
<translation>Quit or set tray?</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Quit</source>
<translatorcomment>Quit</translatorcomment>
<translation>Quit</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Set tray</source>
<translatorcomment>Set tray</translatorcomment>
<translation>Set tray</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Cancel</source>
<translatorcomment>Cancel</translatorcomment>
<translation>Cancel</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="253"/>
<source>Notice</source>
<translatorcomment>Notice</translatorcomment>
<translation>Notice</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="254"/>
<source>Hidden here!</source>
<translatorcomment>Hidden here!</translatorcomment>
<translation>Hidden here!</translation>
</message>
</context>
<context>
<name>InputConvertGame</name>
<message>
<location filename="../../device/controller/inputconvert/inputconvertgame.cpp" line="507"/>
<source>current keymap mode: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../device/controller/inputconvert/inputconvertgame.cpp" line="507"/>
<source>custom</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../device/controller/inputconvert/inputconvertgame.cpp" line="507"/>
<source>normal</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>KeyMap</name>
<message>
<location filename="../../device/controller/inputconvert/keymap/keymap.cpp" line="307"/>
<source>Script updated, current keymap mode:normal, Press ~ key to switch keymap mode</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free, you can download it at the following address:</source>
<translation type="vanished">This software is completely open source and free, you can download it at the following address:</translation>
</message>
<message>
<source>This software is completely open source and free.
Strictly used for illegal purposes, or at your own risk.
You can download it at the following address:</source>
<translation type="vanished">This software is completely open source and free.\nStrictly used for illegal purposes, or at your own risk.\nYou can download it at the following address:</translation>
</message>
<message>
<location filename="../../main.cpp" line="109"/>
<source>This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</source>
<translation>This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<location filename="../../device/ui/toolform.ui" line="14"/>
<source>Tool</source>
<translation>Tool</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="33"/>
<source>full screen</source>
<translation>full screen</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="56"/>
<source>expand notify</source>
<translation>expand notify</translation>
</message>
<message>
<source>turn off</source>
<translation type="vanished">turn off</translation>
</message>
<message>
<source>turn on</source>
<translation type="vanished">turn on</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="66"/>
<source>touch switch</source>
<translation>touch switch</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="76"/>
<source>close screen</source>
<translation>close screen</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="86"/>
<source>power</source>
<translation>power</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="96"/>
<source>volume up</source>
<translation>volume up</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="106"/>
<source>volume down</source>
<translation>volume down</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="116"/>
<source>app switch</source>
<translation>app switch</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="126"/>
<source>menu</source>
<translation>menu</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="136"/>
<source>home</source>
<translation>home</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="146"/>
<source>return</source>
<translation>return</translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="156"/>
<source>screen shot</source>
<translation>screen shot</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>wait current file transfer to complete</source>
<translation type="vanished">wait current file transfer to complete</translation>
</message>
<message>
<source>file transfer complete</source>
<translation type="vanished">file transfer complete</translation>
</message>
<message>
<source>file transfer failed</source>
<translation type="vanished">file transfer failed</translation>
</message>
<message>
<location filename="../../device/ui/videoform.cpp" line="749"/>
<source>file does not exist</source>
<translation>file does not exist</translation>
</message>
</context>
<context>
<name>videoForm</name>
<message>
<source>qrc:/qml/pinwheel.qml</source>
<translation type="vanished">qrc:/qml/pinwheel.qml</translation>
</message>
</context>
</TS>

Binary file not shown.

View file

@ -1,473 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>Device</name>
<message>
<source>wait current file transfer to complete</source>
<translation type="vanished"></translation>
</message>
<message>
<source>file transfer complete</source>
<translation type="vanished"></translation>
</message>
<message>
<source>file transfer failed</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../../device/device.cpp" line="184"/>
<source>install apk</source>
<translation>apk</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="186"/>
<source>file transfer</source>
<translation></translation>
</message>
<message>
<location filename="../../device/device.cpp" line="190"/>
<source>wait current %1 to complete</source>
<translation>%1</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="193"/>
<source>%1 complete, save in %2</source>
<translation>%1,%2</translation>
</message>
<message>
<source>%1 complete
save in %2</source>
<translation type="vanished">%1\n %2</translation>
</message>
<message>
<location filename="../../device/device.cpp" line="196"/>
<source>%1 failed</source>
<translation>%1 </translation>
</message>
</context>
<context>
<name>Dialog</name>
<message>
<location filename="../../dialog.ui" line="562"/>
<source>Wireless</source>
<translation>线</translation>
</message>
<message>
<location filename="../../dialog.ui" line="646"/>
<source>wireless connect</source>
<translation>线</translation>
</message>
<message>
<location filename="../../dialog.ui" line="662"/>
<source>wireless disconnect</source>
<translation>线</translation>
</message>
<message>
<location filename="../../dialog.ui" line="89"/>
<source>Start Config</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="225"/>
<source>record save path:</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="242"/>
<location filename="../../dialog.cpp" line="449"/>
<source>select path</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="156"/>
<source>record format</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="380"/>
<source>record screen</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="325"/>
<source>frameless</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="32"/>
<source>Use Simple Mode</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="42"/>
<source>Simple Mode</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="53"/>
<source>WIFI Connect</source>
<translatorcomment>WIFI连接</translatorcomment>
<translation>WIFI连接</translation>
</message>
<message>
<location filename="../../dialog.ui" line="60"/>
<source>USB Connect</source>
<translatorcomment>USB连接</translatorcomment>
<translation>USB连接</translation>
</message>
<message>
<location filename="../../dialog.ui" line="69"/>
<source>Double click to connect:</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="184"/>
<source>lock orientation:</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="387"/>
<source>show fps</source>
<translation>fps</translation>
</message>
<message>
<location filename="../../dialog.ui" line="394"/>
<source>stay awake</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="421"/>
<source>device name:</source>
<translatorcomment>:</translatorcomment>
<translation>:</translation>
</message>
<message>
<location filename="../../dialog.ui" line="438"/>
<source>update name</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="519"/>
<source>stop all server</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="696"/>
<source>adb command:</source>
<translation>adb命令</translation>
</message>
<message>
<location filename="../../dialog.ui" line="732"/>
<source>terminate</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="719"/>
<source>execute</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="745"/>
<source>clear</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="370"/>
<source>reverse connection</source>
<translation></translation>
</message>
<message>
<source>auto enable</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../../dialog.ui" line="354"/>
<source>background record</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="318"/>
<source>screen-off</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="287"/>
<source>apply</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="142"/>
<source>max size:</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="338"/>
<source>always on top</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="280"/>
<source>refresh script</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="536"/>
<source>get device IP</source>
<translation>IP</translation>
</message>
<message>
<location filename="../../dialog.ui" line="407"/>
<source>USB line</source>
<translation>USB线</translation>
</message>
<message>
<location filename="../../dialog.ui" line="491"/>
<source>stop server</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="481"/>
<source>start server</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="471"/>
<source>device serial:</source>
<translation></translation>
</message>
<message>
<source>Config</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../../dialog.ui" line="125"/>
<source>bit rate:</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.ui" line="546"/>
<source>start adbd</source>
<translation>adbd</translation>
</message>
<message>
<location filename="../../dialog.ui" line="526"/>
<source>refresh devices</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="85"/>
<source>show</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="86"/>
<source>quit</source>
<translatorcomment>退</translatorcomment>
<translation>退</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="121"/>
<source>original</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="126"/>
<source>no lock</source>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>warning</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Quit or set tray?</source>
<translatorcomment>退</translatorcomment>
<translation>退</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Quit</source>
<translatorcomment>退</translatorcomment>
<translation>退</translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Set tray</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="243"/>
<source>Cancel</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="253"/>
<source>Notice</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<location filename="../../dialog.cpp" line="254"/>
<source>Hidden here!</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
</context>
<context>
<name>InputConvertGame</name>
<message>
<location filename="../../device/controller/inputconvert/inputconvertgame.cpp" line="507"/>
<source>current keymap mode: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../device/controller/inputconvert/inputconvertgame.cpp" line="507"/>
<source>custom</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../device/controller/inputconvert/inputconvertgame.cpp" line="507"/>
<source>normal</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>KeyMap</name>
<message>
<location filename="../../device/controller/inputconvert/keymap/keymap.cpp" line="307"/>
<source>Script updated, current keymap mode:normal, Press ~ key to switch keymap mode</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free, you can download it at the following address:</source>
<translation type="vanished"></translation>
</message>
<message>
<source>This software is completely open source and free.
Strictly used for illegal purposes, or at your own risk.
You can download it at the following address:</source>
<translation type="vanished">.\n严禁用于非法用途.\n你可以在下面地址下载:</translation>
</message>
<message>
<location filename="../../main.cpp" line="109"/>
<source>This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</source>
<translation>:</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<location filename="../../device/ui/toolform.ui" line="14"/>
<source>Tool</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="33"/>
<source>full screen</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="56"/>
<source>expand notify</source>
<translation></translation>
</message>
<message>
<source>turn off</source>
<translation type="vanished"></translation>
</message>
<message>
<source>turn on</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="66"/>
<source>touch switch</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="76"/>
<source>close screen</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="86"/>
<source>power</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="96"/>
<source>volume up</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="106"/>
<source>volume down</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="116"/>
<source>app switch</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="126"/>
<source>menu</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="136"/>
<source>home</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="146"/>
<source>return</source>
<translation></translation>
</message>
<message>
<location filename="../../device/ui/toolform.ui" line="156"/>
<source>screen shot</source>
<translation></translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>wait current file transfer to complete</source>
<translation type="vanished"></translation>
</message>
<message>
<source>file transfer complete</source>
<translation type="vanished"></translation>
</message>
<message>
<source>file transfer failed</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../../device/ui/videoform.cpp" line="749"/>
<source>file does not exist</source>
<translation></translation>
</message>
</context>
<context>
<name>videoForm</name>
<message>
<source>qrc:/qml/pinwheel.qml</source>
<translation type="vanished">qrc:/qml/pinwheel.qml</translation>
</message>
</context>
</TS>

BIN
QtScrcpy/res/i18n/en_US.qm Normal file

Binary file not shown.

321
QtScrcpy/res/i18n/en_US.ts Normal file
View file

@ -0,0 +1,321 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation>show</translation>
</message>
<message>
<source>quit</source>
<translation>quit</translation>
</message>
<message>
<source>original</source>
<translation>original</translation>
</message>
<message>
<source>no lock</source>
<translation>no lock</translation>
</message>
<message>
<source>Notice</source>
<translation>Notice</translation>
</message>
<message>
<source>Hidden here!</source>
<translation>Hidden here!</translation>
</message>
<message>
<source>select path</source>
<translation>select path</translation>
</message>
<message>
<source>Clear History</source>
<translation>Clear History</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>
</message>
<message>
<source>QuickMirror</source>
<translation>QuickMirror</translation>
</message>
<message>
<source>If you need more professional batch control mirror software, you can try the following software:</source>
<translation>If you need more professional batch control mirror software, you can try the following software:</translation>
</message>
<message>
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
<translation>If you need more professional game keymap mirror software, you can try the following software:</translation>
</message>
<message>
<source>QuickAssistant</source>
<translation>QuickAssistant</translation>
</message>
<message>
<source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>
<translation>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>Tool</translation>
</message>
<message>
<source>full screen</source>
<translation>full screen</translation>
</message>
<message>
<source>expand notify</source>
<translation>expand notify</translation>
</message>
<message>
<source>touch switch</source>
<translation>touch switch</translation>
</message>
<message>
<source>close screen</source>
<translation>close screen</translation>
</message>
<message>
<source>power</source>
<translation>power</translation>
</message>
<message>
<source>volume up</source>
<translation>volume up</translation>
</message>
<message>
<source>volume down</source>
<translation>volume down</translation>
</message>
<message>
<source>app switch</source>
<translation>app switch</translation>
</message>
<message>
<source>menu</source>
<translation>menu</translation>
</message>
<message>
<source>home</source>
<translation>home</translation>
</message>
<message>
<source>return</source>
<translation>return</translation>
</message>
<message>
<source>screen shot</source>
<translation>screen shot</translation>
</message>
<message>
<source>open screen</source>
<translation>open screen</translation>
</message>
<message>
<source>group control</source>
<translation>group control</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>file does not exist</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>Wireless</translation>
</message>
<message>
<source>wireless connect</source>
<translation>wireless connect</translation>
</message>
<message>
<source>wireless disconnect</source>
<translation>wireless disconnect</translation>
</message>
<message>
<source>Start Config</source>
<translation>Start Config</translation>
</message>
<message>
<source>select path</source>
<translation>select path</translation>
</message>
<message>
<source>record format</source>
<translation>record format:</translation>
</message>
<message>
<source>record screen</source>
<translation>record screen</translation>
</message>
<message>
<source>frameless</source>
<translation>frameless</translation>
</message>
<message>
<source>Use Simple Mode</source>
<translatorcomment>Use Simple Mode</translatorcomment>
<translation>Use Simple Mode</translation>
</message>
<message>
<source>Simple Mode</source>
<translatorcomment>Simple Mode</translatorcomment>
<translation>Simple Mode</translation>
</message>
<message>
<source>WIFI Connect</source>
<translatorcomment>WIFI Connect</translatorcomment>
<translation>WIFI Connect</translation>
</message>
<message>
<source>USB Connect</source>
<translatorcomment>USB Connect</translatorcomment>
<translation>USB Connect</translation>
</message>
<message>
<source>Double click to connect:</source>
<translation>Double click to connect:</translation>
</message>
<message>
<source>lock orientation:</source>
<translation>lock orientation:</translation>
</message>
<message>
<source>show fps</source>
<translation>show fps</translation>
</message>
<message>
<source>stay awake</source>
<translation>stay awake</translation>
</message>
<message>
<source>device name:</source>
<translatorcomment>device name:</translatorcomment>
<translation>device name:</translation>
</message>
<message>
<source>update name</source>
<translatorcomment>update name</translatorcomment>
<translation>update name</translation>
</message>
<message>
<source>stop all server</source>
<translation>stop all server</translation>
</message>
<message>
<source>adb command:</source>
<translation>adb command:</translation>
</message>
<message>
<source>terminate</source>
<translation>terminate</translation>
</message>
<message>
<source>execute</source>
<translation>execute</translation>
</message>
<message>
<source>clear</source>
<translation>clear</translation>
</message>
<message>
<source>reverse connection</source>
<translation>reverse connection</translation>
</message>
<message>
<source>background record</source>
<translation>background record</translation>
</message>
<message>
<source>screen-off</source>
<translation>screen-off</translation>
</message>
<message>
<source>apply</source>
<translation>apply</translation>
</message>
<message>
<source>max size:</source>
<translation>max size:</translation>
</message>
<message>
<source>always on top</source>
<translation>always on top</translation>
</message>
<message>
<source>refresh script</source>
<translation>refresh script</translation>
</message>
<message>
<source>get device IP</source>
<translation>get device IP</translation>
</message>
<message>
<source>USB line</source>
<translation>USB line</translation>
</message>
<message>
<source>stop server</source>
<translation>stop server</translation>
</message>
<message>
<source>start server</source>
<translation>start server</translation>
</message>
<message>
<source>device serial:</source>
<translation>device serial:</translation>
</message>
<message>
<source>bit rate:</source>
<translation>bit rate:</translation>
</message>
<message>
<source>start adbd</source>
<translation>start adbd</translation>
</message>
<message>
<source>refresh devices</source>
<translation>refresh devices</translation>
</message>
<message>
<source>install sndcpy</source>
<translation>install sndcpy</translation>
</message>
<message>
<source>start audio</source>
<translation>start audio</translation>
</message>
<message>
<source>stop audio</source>
<translation>stop audio</translation>
</message>
<message>
<source>auto update</source>
<translation>auto update</translation>
</message>
<message>
<source>show toolbar</source>
<translation>show toolbar</translation>
</message>
<message>
<source>record save path:</source>
<translation>record save path:</translation>
</message>
</context>
</TS>

BIN
QtScrcpy/res/i18n/zh_CN.qm Normal file

Binary file not shown.

321
QtScrcpy/res/i18n/zh_CN.ts Normal file
View file

@ -0,0 +1,321 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation></translation>
</message>
<message>
<source>quit</source>
<translation>退</translation>
</message>
<message>
<source>original</source>
<translation></translation>
</message>
<message>
<source>no lock</source>
<translation></translation>
</message>
<message>
<source>Notice</source>
<translation></translation>
</message>
<message>
<source>Hidden here!</source>
<translation></translation>
</message>
<message>
<source>select path</source>
<translation></translation>
</message>
<message>
<source>Clear History</source>
<translation></translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>使</translation>
</message>
<message>
<source>QuickMirror</source>
<translation></translation>
</message>
<message>
<source>If you need more professional batch control mirror software, you can try the following software:</source>
<translation></translation>
</message>
<message>
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
<translation></translation>
</message>
<message>
<source>QuickAssistant</source>
<translation></translation>
</message>
<message>
<source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>
<translation>QQ群联系我 &lt;901736468&gt;</translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation></translation>
</message>
<message>
<source>full screen</source>
<translation></translation>
</message>
<message>
<source>expand notify</source>
<translation></translation>
</message>
<message>
<source>touch switch</source>
<translation></translation>
</message>
<message>
<source>close screen</source>
<translation></translation>
</message>
<message>
<source>power</source>
<translation></translation>
</message>
<message>
<source>volume up</source>
<translation></translation>
</message>
<message>
<source>volume down</source>
<translation></translation>
</message>
<message>
<source>app switch</source>
<translation></translation>
</message>
<message>
<source>menu</source>
<translation></translation>
</message>
<message>
<source>home</source>
<translation></translation>
</message>
<message>
<source>return</source>
<translation></translation>
</message>
<message>
<source>screen shot</source>
<translation></translation>
</message>
<message>
<source>open screen</source>
<translation></translation>
</message>
<message>
<source>group control</source>
<translation></translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation></translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>线</translation>
</message>
<message>
<source>wireless connect</source>
<translation>线</translation>
</message>
<message>
<source>wireless disconnect</source>
<translation>线</translation>
</message>
<message>
<source>Start Config</source>
<translation></translation>
</message>
<message>
<source>select path</source>
<translation></translation>
</message>
<message>
<source>record format</source>
<translation></translation>
</message>
<message>
<source>record screen</source>
<translation></translation>
</message>
<message>
<source>frameless</source>
<translation></translation>
</message>
<message>
<source>Use Simple Mode</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<source>Simple Mode</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<source>WIFI Connect</source>
<translatorcomment>WIFI连接</translatorcomment>
<translation>WIFI连接</translation>
</message>
<message>
<source>USB Connect</source>
<translatorcomment>USB连接</translatorcomment>
<translation>USB连接</translation>
</message>
<message>
<source>Double click to connect:</source>
<translation></translation>
</message>
<message>
<source>lock orientation:</source>
<translation></translation>
</message>
<message>
<source>show fps</source>
<translation>fps</translation>
</message>
<message>
<source>stay awake</source>
<translation></translation>
</message>
<message>
<source>device name:</source>
<translatorcomment>:</translatorcomment>
<translation>:</translation>
</message>
<message>
<source>update name</source>
<translatorcomment></translatorcomment>
<translation></translation>
</message>
<message>
<source>stop all server</source>
<translation></translation>
</message>
<message>
<source>adb command:</source>
<translation>adb命令</translation>
</message>
<message>
<source>terminate</source>
<translation></translation>
</message>
<message>
<source>execute</source>
<translation></translation>
</message>
<message>
<source>clear</source>
<translation></translation>
</message>
<message>
<source>reverse connection</source>
<translation></translation>
</message>
<message>
<source>background record</source>
<translation></translation>
</message>
<message>
<source>screen-off</source>
<translation></translation>
</message>
<message>
<source>apply</source>
<translation></translation>
</message>
<message>
<source>max size:</source>
<translation></translation>
</message>
<message>
<source>always on top</source>
<translation></translation>
</message>
<message>
<source>refresh script</source>
<translation></translation>
</message>
<message>
<source>get device IP</source>
<translation>IP</translation>
</message>
<message>
<source>USB line</source>
<translation>USB线</translation>
</message>
<message>
<source>stop server</source>
<translation></translation>
</message>
<message>
<source>start server</source>
<translation></translation>
</message>
<message>
<source>device serial:</source>
<translation></translation>
</message>
<message>
<source>bit rate:</source>
<translation></translation>
</message>
<message>
<source>start adbd</source>
<translation>adbd</translation>
</message>
<message>
<source>refresh devices</source>
<translation></translation>
</message>
<message>
<source>install sndcpy</source>
<translation>sndcpy</translation>
</message>
<message>
<source>start audio</source>
<translation></translation>
</message>
<message>
<source>stop audio</source>
<translation></translation>
</message>
<message>
<source>auto update</source>
<translation></translation>
</message>
<message>
<source>show toolbar</source>
<translation></translation>
</message>
<message>
<source>record save path:</source>
<translation></translation>
</message>
</context>
</TS>

View file

@ -22,8 +22,8 @@
<file>qss/psblack/radiobutton_checked_disable.png</file>
<file>qss/psblack/radiobutton_unchecked.png</file>
<file>qss/psblack/radiobutton_unchecked_disable.png</file>
<file>i18n/QtScrcpy_en.qm</file>
<file>i18n/QtScrcpy_zh.qm</file>
<file>i18n/en_US.qm</file>
<file>i18n/zh_CN.qm</file>
<file>image/tray/logo.png</file>
</qresource>
</RCC>

BIN
QtScrcpy/sndcpy/sndcpy.apk Normal file

Binary file not shown.

View file

@ -0,0 +1,53 @@
@echo off
echo Begin Runing...
set SNDCPY_PORT=28200
set SNDCPY_APK=sndcpy.apk
set ADB=adb.exe
if not "%1"=="" (
set serial=-s %1
)
if not "%2"=="" (
set SNDCPY_PORT=%2
)
echo Waiting for device %1...
%ADB% %serial% wait-for-device || goto :error
echo Find device %1
for /f "delims=" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i
if "%sndcpy_installed%"=="" (
echo Install %SNDCPY_APK%...
%ADB% %serial% uninstall com.rom1v.sndcpy || echo uninstall failed
%ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error
echo Install %SNDCPY_APK% success
)
echo Request PROJECT_MEDIA permission...
%ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow
echo Forward port %SNDCPY_PORT%...
%ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error
echo Start %SNDCPY_APK%...
%ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error
:check_start
echo Waiting %SNDCPY_APK% start...
::timeout /T 1 /NOBREAK > nul
%ADB% %serial% shell sleep 0.1
for /f "delims=" %%i in ("%ADB% shell 'ps | grep com.rom1v.sndcpy'") do set sndcpy_started=%%i
if "%sndcpy_started%"=="" (
goto :check_start
)
echo %SNDCPY_APK% started...
echo Ready playing...
::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT%
::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT%
goto :EOF
:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%

46
QtScrcpy/sndcpy/sndcpy.sh Executable file
View file

@ -0,0 +1,46 @@
#!/bin/bash
echo Begin Runing...
SNDCPY_PORT=28200
SNDCPY_APK=sndcpy.apk
ADB=./adb
serial=
if [[ $# -ge 2 ]]
then
serial="-s $1"
SNDCPY_PORT=$2
fi
echo "Waiting for device $1..."
$ADB $serial wait-for-device
echo "Find device $1"
sndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy)
if [[ $sndcpy_installed == "" ]]; then
echo Install $SNDCPY_APK...
$ADB $serial uninstall com.rom1v.sndcpy || echo uninstall failed
$ADB $serial install -t -r -g $SNDCPY_APK
echo Install $SNDCPY_APK success
fi
echo Request PROJECT_MEDIA permission...
$ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow
echo Forward port $SNDCPY_PORT...
$ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy
echo Start $SNDCPY_APK...
$ADB $serial shell am start com.rom1v.sndcpy/.MainActivity
while ((1))
do
echo Waiting $SNDCPY_APK start...
sleep 0.1
sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy')
if [[ $sndcpy_started != "" ]]; then
break
fi
done
echo Ready playing...

View file

@ -2,43 +2,71 @@
#include <QFile>
#include <QFileDialog>
#include <QKeyEvent>
#include <QRandomGenerator>
#include <QTime>
#include <QTimer>
#include "config.h"
#include "device.h"
#include "dialog.h"
#include "keymap.h"
#include "ui_dialog.h"
#include "videoform.h"
#include "../groupcontroller/groupcontroller.h"
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
#ifdef Q_OS_WIN32
#include "../util/winutils.h"
#endif
QString s_keyMapPath = "";
const QString &getKeyMapPath()
{
if (s_keyMapPath.isEmpty()) {
s_keyMapPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_KEYMAP_PATH"));
QFileInfo fileInfo(s_keyMapPath);
if (s_keyMapPath.isEmpty() || !fileInfo.isDir()) {
s_keyMapPath = QCoreApplication::applicationDirPath() + "/keymap";
}
}
return s_keyMapPath;
}
Dialog::Dialog(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
initUI();
connect(&m_adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
updateBootConfig(true);
on_useSingleModeCheck_clicked();
on_updateDevice_clicked();
connect(&m_autoUpdatetimer, &QTimer::timeout, this, &Dialog::on_updateDevice_clicked);
if (ui->autoUpdatecheckBox->isChecked()) {
m_autoUpdatetimer.start(5000);
}
connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) {
QString log = "";
bool newLine = true;
QStringList args = m_adb.arguments();
switch (processResult) {
case AdbProcess::AER_ERROR_START:
case qsc::AdbProcess::AER_ERROR_START:
break;
case AdbProcess::AER_SUCCESS_START:
case qsc::AdbProcess::AER_SUCCESS_START:
log = "adb run";
newLine = false;
break;
case AdbProcess::AER_ERROR_EXEC:
case qsc::AdbProcess::AER_ERROR_EXEC:
//log = m_adb.getErrorOut();
if (args.contains("ifconfig") && args.contains("wlan0")) {
getIPbyIp();
}
break;
case AdbProcess::AER_ERROR_MISSING_BINARY:
log = "adb not find";
case qsc::AdbProcess::AER_ERROR_MISSING_BINARY:
log = "adb not found";
break;
case AdbProcess::AER_SUCCESS_EXEC:
case qsc::AdbProcess::AER_SUCCESS_EXEC:
//log = m_adb.getStdOut();
if (args.contains("devices")) {
QStringList devices = m_adb.getDevicesSerialFromStdOut();
@ -46,7 +74,7 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
ui->connectedPhoneList->clear();
for (auto &item : devices) {
ui->serialBox->addItem(item);
ui->connectedPhoneList->addItem(item+"-"+Config::getInstance().getNickName(item));
ui->connectedPhoneList->addItem(Config::getInstance().getNickName(item) + "-" + item);
}
} else if (args.contains("show") && args.contains("wlan0")) {
QString ip = m_adb.getDeviceIPFromStdOut();
@ -54,21 +82,21 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
log = "ip not find, connect to wifi?";
break;
}
ui->deviceIpEdt->setText(ip);
ui->deviceIpEdt->setEditText(ip);
} else if (args.contains("ifconfig") && args.contains("wlan0")) {
QString ip = m_adb.getDeviceIPFromStdOut();
if (ip.isEmpty()) {
log = "ip not find, connect to wifi?";
break;
}
ui->deviceIpEdt->setText(ip);
ui->deviceIpEdt->setEditText(ip);
} else if (args.contains("ip -o a")) {
QString ip = m_adb.getDeviceIPByIpFromStdOut();
if (ip.isEmpty()) {
log = "ip not find, connect to wifi?";
break;
}
ui->deviceIpEdt->setText(ip);
ui->deviceIpEdt->setEditText(ip);
}
break;
}
@ -77,41 +105,48 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
}
});
m_hideIcon = new QSystemTrayIcon();
m_hideIcon = new QSystemTrayIcon(this);
m_hideIcon->setIcon(QIcon(":/image/tray/logo.png"));
m_menu = new QMenu();
m_quit = new QAction();
m_showWindow = new QAction();;
m_menu = new QMenu(this);
m_quit = new QAction(this);
m_showWindow = new QAction(this);
m_showWindow->setText(tr("show"));
m_quit->setText(tr("quit"));
m_menu->addAction(m_showWindow);
m_menu->addAction(m_quit);
m_hideIcon->setContextMenu(m_menu);
connect(m_showWindow, &QAction::triggered, this, &Dialog::slotShow);
connect(m_quit, SIGNAL(triggered()), this, SLOT(close()));
connect(m_hideIcon, &QSystemTrayIcon::activated,this,&Dialog::slotActivated);
m_hideIcon->show();
connect(m_showWindow, &QAction::triggered, this, &Dialog::show);
connect(m_quit, &QAction::triggered, this, [this]() {
m_hideIcon->hide();
qApp->quit();
});
connect(m_hideIcon, &QSystemTrayIcon::activated, this, &Dialog::slotActivated);
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceConnected, this, &Dialog::onDeviceConnected);
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceDisconnected, this, &Dialog::onDeviceDisconnected);
}
Dialog::~Dialog()
{
qDebug() << "~Dialog()";
updateBootConfig(false);
m_deviceManage.disconnectAllDevice();
qsc::IDeviceManage::getInstance().disconnectAllDevice();
delete ui;
}
void Dialog::initUI()
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
//setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
ui->bitRateBox->addItem("2000000");
ui->bitRateBox->addItem("6000000");
ui->bitRateBox->addItem("8000000");
ui->bitRateBox->addItem("10000000");
ui->bitRateBox->addItem("20000000");
ui->bitRateBox->addItem("50000000");
ui->bitRateBox->addItem("100000000");
ui->bitRateBox->addItem("200000000");
setWindowTitle(Config::getInstance().getTitle());
#ifdef Q_OS_WIN32
WinUtils::setDarkBorderToWindow((HWND)this->winId(), true);
#endif
ui->bitRateEdit->setValidator(new QIntValidator(1, 99999, this));
ui->maxSizeBox->addItem("640");
ui->maxSizeBox->addItem("720");
@ -130,21 +165,15 @@ void Dialog::initUI()
ui->lockOrientationBox->addItem("270");
ui->lockOrientationBox->setCurrentIndex(0);
updateBootConfig(true);
// 加载IP历史记录
loadIpHistory();
on_useSingleModeCheck_clicked();
on_updateDevice_clicked();
#ifdef Q_OS_OSX
// mac need more width
setFixedWidth(550);
#endif
#ifdef Q_OS_LINUX
// linux need more width
setFixedWidth(520);
#endif
// 为deviceIpEdt添加右键菜单
if (ui->deviceIpEdt->lineEdit()) {
ui->deviceIpEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->deviceIpEdt->lineEdit(), &QWidget::customContextMenuRequested,
this, &Dialog::showIpEditMenu);
}
}
void Dialog::updateBootConfig(bool toView)
@ -152,7 +181,16 @@ void Dialog::updateBootConfig(bool toView)
if (toView) {
UserBootConfig config = Config::getInstance().getUserBootConfig();
ui->bitRateBox->setCurrentIndex(config.bitRateIndex);
if (config.bitRate == 0) {
ui->bitRateBox->setCurrentText("Mbps");
} else if (config.bitRate % 1000000 == 0) {
ui->bitRateEdit->setText(QString::number(config.bitRate / 1000000));
ui->bitRateBox->setCurrentText("Mbps");
} else {
ui->bitRateEdit->setText(QString::number(config.bitRate / 1000));
ui->bitRateBox->setCurrentText("Kbps");
}
ui->maxSizeBox->setCurrentIndex(config.maxSizeIndex);
ui->formatBox->setCurrentIndex(config.recordFormatIndex);
ui->recordPathEdt->setText(config.recordPath);
@ -166,10 +204,12 @@ void Dialog::updateBootConfig(bool toView)
ui->closeScreenCheck->setChecked(config.autoOffScreen);
ui->stayAwakeCheck->setChecked(config.keepAlive);
ui->useSingleModeCheck->setChecked(config.simpleMode);
ui->autoUpdatecheckBox->setChecked(config.autoUpdateDevice);
ui->showToolbar->setChecked(config.showToolbar);
} else {
UserBootConfig config;
config.bitRateIndex = ui->bitRateBox->currentIndex();
config.bitRate = getBitRate();
config.maxSizeIndex = ui->maxSizeBox->currentIndex();
config.recordFormatIndex = ui->formatBox->currentIndex();
config.recordPath = ui->recordPathEdt->text();
@ -183,6 +223,14 @@ void Dialog::updateBootConfig(bool toView)
config.framelessWindow = ui->framelessCheck->isChecked();
config.keepAlive = ui->stayAwakeCheck->isChecked();
config.simpleMode = ui->useSingleModeCheck->isChecked();
config.autoUpdateDevice = ui->autoUpdatecheckBox->isChecked();
config.showToolbar = ui->showToolbar->isChecked();
// 保存当前IP到历史记录
QString currentIp = ui->deviceIpEdt->currentText().trimmed();
if (!currentIp.isEmpty()) {
saveIpHistory(currentIp);
}
Config::getInstance().setUserBootConfig(config);
}
@ -195,7 +243,11 @@ void Dialog::execAdbCmd()
}
QString cmd = ui->adbCommandEdt->text().trimmed();
outLog("adb " + cmd, false);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", Qt::SkipEmptyParts));
#else
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", QString::SkipEmptyParts));
#endif
}
void Dialog::delayMs(int ms)
@ -209,7 +261,11 @@ void Dialog::delayMs(int ms)
QString Dialog::getGameScript(const QString &fileName)
{
QFile loadFile(KeyMap::getKeyMapPath() + "/" + fileName);
if (fileName.isEmpty()) {
return "";
}
QFile loadFile(getKeyMapPath() + "/" + fileName);
if (!loadFile.open(QIODevice::ReadOnly)) {
outLog("open file failed:" + fileName, true);
return "";
@ -220,18 +276,13 @@ QString Dialog::getGameScript(const QString &fileName)
return ret;
}
void Dialog::slotShow()
{
this->show();
m_hideIcon->hide();
}
void Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason) {
case QSystemTrayIcon::Trigger:
#ifdef Q_OS_WIN32
this->show();
m_hideIcon->hide();
#endif
break;
default:
break;
@ -240,26 +291,15 @@ void Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason)
void Dialog::closeEvent(QCloseEvent *event)
{
int res = QMessageBox::question(this,tr("warning"),tr("Quit or set tray?"),tr("Quit"),tr("Set tray"),tr("Cancel"));
if(res == 0)
{
event->accept();
}
else if(res == 1)
{
this->hide();
m_hideIcon->show();
this->hide();
if (!Config::getInstance().getTrayMessageShown()) {
Config::getInstance().setTrayMessageShown(true);
m_hideIcon->showMessage(tr("Notice"),
tr("Hidden here!"),
QSystemTrayIcon::Information,
3000);
event->ignore();
}
else
{
event->ignore();
tr("Hidden here!"),
QSystemTrayIcon::Information,
3000);
}
event->ignore();
}
void Dialog::on_updateDevice_clicked()
@ -275,49 +315,42 @@ void Dialog::on_startServerBtn_clicked()
{
outLog("start server...", false);
QString absFilePath;
if (ui->recordScreenCheck->isChecked()) {
QString fileDir(ui->recordPathEdt->text().trimmed());
if (!fileDir.isEmpty()) {
QDateTime dateTime = QDateTime::currentDateTime();
QString fileName = dateTime.toString("_yyyyMMdd_hhmmss_zzz");
QString ext = ui->formatBox->currentText().trimmed();
fileName = windowTitle() + fileName + "." + ext;
QDir dir(fileDir);
absFilePath = dir.absoluteFilePath(fileName);
}
}
quint32 bitRate = ui->bitRateBox->currentText().trimmed().toUInt();
// this is ok that "native" toUshort is 0
// this is ok that "original" toUshort is 0
quint16 videoSize = ui->maxSizeBox->currentText().trimmed().toUShort();
Device::DeviceParams params;
qsc::DeviceParams params;
params.serial = ui->serialBox->currentText().trimmed();
params.maxSize = videoSize;
params.bitRate = bitRate;
params.bitRate = getBitRate();
// on devices with Android >= 10, the capture frame rate can be limited
params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());
params.recordFileName = absFilePath;
params.closeScreen = ui->closeScreenCheck->isChecked();
params.useReverse = ui->useReverseCheck->isChecked();
params.display = !ui->notDisplayCheck->isChecked();
params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames();
params.lockVideoOrientation = ui->lockOrientationBox->currentIndex() - 1;
params.stayAwake = ui->stayAwakeCheck->isChecked();
params.framelessWindow = ui->framelessCheck->isChecked();
params.recordPath = ui->recordPathEdt->text().trimmed();
m_deviceManage.connectDevice(params);
if (ui->alwaysTopCheck->isChecked()) {
m_deviceManage.staysOnTop(params.serial);
if (ui->lockOrientationBox->currentIndex() > 0) {
params.captureOrientationLock = 1;
params.captureOrientation = (ui->lockOrientationBox->currentIndex() - 1) * 90;
}
m_deviceManage.showFPS(params.serial, ui->fpsCheck->isChecked());
params.stayAwake = ui->stayAwakeCheck->isChecked();
params.recordFile = ui->recordScreenCheck->isChecked();
params.recordPath = ui->recordPathEdt->text().trimmed();
params.recordFileFormat = ui->formatBox->currentText().trimmed();
params.serverLocalPath = getServerPath();
params.serverRemotePath = Config::getInstance().getServerPath();
params.pushFilePath = Config::getInstance().getPushFilePath();
params.gameScript = getGameScript(ui->gameBox->currentText());
params.serverVersion = Config::getInstance().getServerVersion();
params.logLevel = Config::getInstance().getLogLevel();
params.codecOptions = Config::getInstance().getCodecOptions();
params.codecName = Config::getInstance().getCodecName();
params.scid = QRandomGenerator::global()->bounded(1, 10000) & 0x7FFFFFFF;
qsc::IDeviceManage::getInstance().connectDevice(params);
}
void Dialog::on_stopServerBtn_clicked()
{
if (m_deviceManage.disconnectDevice(ui->serialBox->currentText().trimmed())) {
if (qsc::IDeviceManage::getInstance().disconnectDevice(ui->serialBox->currentText().trimmed())) {
outLog("stop server");
}
}
@ -327,7 +360,7 @@ void Dialog::on_wirelessConnectBtn_clicked()
if (checkAdbRun()) {
return;
}
QString addr = ui->deviceIpEdt->text().trimmed();
QString addr = ui->deviceIpEdt->currentText().trimmed();
if (!ui->devicePortEdt->text().isEmpty()) {
addr += ":";
addr += ui->devicePortEdt->text().trimmed();
@ -339,6 +372,12 @@ void Dialog::on_wirelessConnectBtn_clicked()
return;
}
// 保存IP历史记录 - 只保存IP部分,不包含端口
QString ip = addr.split(":").first();
if (!ip.isEmpty()) {
saveIpHistory(ip);
}
outLog("wireless connect...", false);
QStringList adbArgs;
adbArgs << "connect";
@ -430,12 +469,76 @@ void Dialog::getIPbyIp()
m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}
void Dialog::onDeviceConnected(bool success, const QString &serial, const QString &deviceName, const QSize &size)
{
Q_UNUSED(deviceName);
if (!success) {
return;
}
auto videoForm = new VideoForm(ui->framelessCheck->isChecked(), Config::getInstance().getSkin(), ui->showToolbar->isChecked());
videoForm->setSerial(serial);
qsc::IDeviceManage::getInstance().getDevice(serial)->setUserData(static_cast<void*>(videoForm));
qsc::IDeviceManage::getInstance().getDevice(serial)->registerDeviceObserver(videoForm);
videoForm->showFPS(ui->fpsCheck->isChecked());
if (ui->alwaysTopCheck->isChecked()) {
videoForm->staysOnTop();
}
#ifndef Q_OS_WIN32
// must be show before updateShowSize
videoForm->show();
#endif
QString name = Config::getInstance().getNickName(serial);
if (name.isEmpty()) {
name = Config::getInstance().getTitle();
}
videoForm->setWindowTitle(name + "-" + serial);
videoForm->updateShowSize(size);
bool deviceVer = size.height() > size.width();
QRect rc = Config::getInstance().getRect(serial);
bool rcVer = rc.height() > rc.width();
// same width/height rate
if (rc.isValid() && (deviceVer == rcVer)) {
// mark: resize is for fix setGeometry magneticwidget bug
videoForm->resize(rc.size());
videoForm->setGeometry(rc);
}
#ifdef Q_OS_WIN32
// windows是show太早可以看到resize的过程
QTimer::singleShot(200, videoForm, [videoForm](){videoForm->show();});
#endif
GroupController::instance().addDevice(serial);
}
void Dialog::onDeviceDisconnected(QString serial)
{
GroupController::instance().removeDevice(serial);
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
auto data = device->getUserData();
if (data) {
VideoForm* vf = static_cast<VideoForm*>(data);
qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf);
vf->close();
vf->deleteLater();
}
}
void Dialog::on_wirelessDisConnectBtn_clicked()
{
if (checkAdbRun()) {
return;
}
QString addr = ui->deviceIpEdt->text().trimmed();
QString addr = ui->deviceIpEdt->currentText().trimmed();
outLog("wireless disconnect...", false);
QStringList adbArgs;
adbArgs << "disconnect";
@ -473,13 +576,13 @@ void Dialog::on_clearOut_clicked()
void Dialog::on_stopAllServerBtn_clicked()
{
m_deviceManage.disconnectAllDevice();
qsc::IDeviceManage::getInstance().disconnectAllDevice();
}
void Dialog::on_refreshGameScriptBtn_clicked()
{
ui->gameBox->clear();
QDir dir(KeyMap::getKeyMapPath());
QDir dir(getKeyMapPath());
if (!dir.exists()) {
outLog("keymap directory not find", true);
return;
@ -496,7 +599,13 @@ void Dialog::on_refreshGameScriptBtn_clicked()
void Dialog::on_applyScriptBtn_clicked()
{
m_deviceManage.updateScript(getGameScript(ui->gameBox->currentText()));
auto curSerial = ui->serialBox->currentText().trimmed();
auto device = qsc::IDeviceManage::getInstance().getDevice(curSerial);
if (!device) {
return;
}
device->updateScript(getGameScript(ui->gameBox->currentText()));
}
void Dialog::on_recordScreenCheck_clicked(bool checked)
@ -529,14 +638,22 @@ void Dialog::on_usbConnectBtn_clicked()
on_startServerBtn_clicked();
}
int Dialog::findDeviceFromeSerialBox(bool wifi) {
QRegExp regIP("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\:([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])\\b");
for (int i = 0; i < ui->serialBox->count(); ++i)
{
int Dialog::findDeviceFromeSerialBox(bool wifi)
{
QString regStr = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\:([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])\\b";
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QRegExp regIP(regStr);
#else
QRegularExpression regIP(regStr);
#endif
for (int i = 0; i < ui->serialBox->count(); ++i) {
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
bool isWifi = regIP.exactMatch(ui->serialBox->itemText(i));
#else
bool isWifi = regIP.match(ui->serialBox->itemText(i)).hasMatch();
#endif
bool found = wifi ? isWifi : !isWifi;
if(found)
{
if (found) {
return i;
}
}
@ -590,8 +707,8 @@ void Dialog::on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item)
void Dialog::on_updateNameBtn_clicked()
{
if(ui->serialBox->count()!=0) {
if(ui->userNameEdt->text().isEmpty()) {
if (ui->serialBox->count() != 0) {
if (ui->userNameEdt->text().isEmpty()) {
Config::getInstance().setNickName(ui->serialBox->currentText(), "Phone");
} else {
Config::getInstance().setNickName(ui->serialBox->currentText(), ui->userNameEdt->text());
@ -599,31 +716,118 @@ void Dialog::on_updateNameBtn_clicked()
on_updateDevice_clicked();
qDebug()<<"Update OK!";
qDebug() << "Update OK!";
} else {
qWarning()<<"No device is connected!";
qWarning() << "No device is connected!";
}
}
void Dialog::on_useSingleModeCheck_clicked()
{
if(ui->useSingleModeCheck->isChecked())
{
ui->configGroupBox->hide();
ui->adbGroupBox->hide();
ui->wirelessGroupBox->hide();
ui->usbGroupBox->hide();
}
else
{
ui->configGroupBox->show();
ui->adbGroupBox->show();
ui->wirelessGroupBox->show();
ui->usbGroupBox->show();
if (ui->useSingleModeCheck->isChecked()) {
ui->rightWidget->hide();
} else {
ui->rightWidget->show();
}
adjustSize();
}
void Dialog::on_serialBox_currentIndexChanged(const QString &arg1)
{
ui->userNameEdt->setText(Config::getInstance().getNickName(arg1));
}
quint32 Dialog::getBitRate()
{
return ui->bitRateEdit->text().trimmed().toUInt() *
(ui->bitRateBox->currentText() == QString("Mbps") ? 1000000 : 1000);
}
const QString &Dialog::getServerPath()
{
static QString serverPath;
if (serverPath.isEmpty()) {
serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH"));
QFileInfo fileInfo(serverPath);
if (serverPath.isEmpty() || !fileInfo.isFile()) {
serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server";
}
}
return serverPath;
}
void Dialog::on_startAudioBtn_clicked()
{
if (ui->serialBox->count() == 0) {
qWarning() << "No device is connected!";
return;
}
m_audioOutput.start(ui->serialBox->currentText(), 28200);
}
void Dialog::on_stopAudioBtn_clicked()
{
m_audioOutput.stop();
}
void Dialog::on_installSndcpyBtn_clicked()
{
if (ui->serialBox->count() == 0) {
qWarning() << "No device is connected!";
return;
}
m_audioOutput.installonly(ui->serialBox->currentText(), 28200);
}
void Dialog::on_autoUpdatecheckBox_toggled(bool checked)
{
if (checked) {
m_autoUpdatetimer.start(5000);
} else {
m_autoUpdatetimer.stop();
}
}
void Dialog::loadIpHistory()
{
QStringList ipList = Config::getInstance().getIpHistory();
ui->deviceIpEdt->clear();
ui->deviceIpEdt->addItems(ipList);
ui->deviceIpEdt->setContentsMargins(0, 0, 0, 0);
if (ui->deviceIpEdt->lineEdit()) {
ui->deviceIpEdt->lineEdit()->setMaxLength(128);
ui->deviceIpEdt->lineEdit()->setPlaceholderText("192.168.0.1");
}
}
void Dialog::saveIpHistory(const QString &ip)
{
if (ip.isEmpty()) {
return;
}
Config::getInstance().saveIpHistory(ip);
// 更新ComboBox
loadIpHistory();
ui->deviceIpEdt->setCurrentText(ip);
}
void Dialog::showIpEditMenu(const QPoint &pos)
{
QMenu *menu = ui->deviceIpEdt->lineEdit()->createStandardContextMenu();
menu->addSeparator();
QAction *clearHistoryAction = new QAction(tr("Clear History"), menu);
connect(clearHistoryAction, &QAction::triggered, this, [this]() {
Config::getInstance().clearIpHistory();
loadIpHistory();
});
menu->addAction(clearHistoryAction);
menu->exec(ui->deviceIpEdt->lineEdit()->mapToGlobal(pos));
delete menu;
}

View file

@ -1,24 +1,26 @@
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QWidget>
#include <QPointer>
#include <QMessageBox>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QListWidget>
#include <QTimer>
#include "adbprocess.h"
#include "devicemanage.h"
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
#include "audio/audiooutput.h"
namespace Ui
{
class Dialog;
class Widget;
}
class QYUVOpenGLWidget;
class Dialog : public QDialog
class Dialog : public QWidget
{
Q_OBJECT
@ -31,50 +33,42 @@ public:
void getIPbyIp();
private slots:
void onDeviceConnected(bool success, const QString& serial, const QString& deviceName, const QSize& size);
void onDeviceDisconnected(QString serial);
void on_updateDevice_clicked();
void on_startServerBtn_clicked();
void on_stopServerBtn_clicked();
void on_wirelessConnectBtn_clicked();
void on_startAdbdBtn_clicked();
void on_getIPBtn_clicked();
void on_wirelessDisConnectBtn_clicked();
void on_selectRecordPathBtn_clicked();
void on_recordPathEdt_textChanged(const QString &arg1);
void on_adbCommandBtn_clicked();
void on_stopAdbBtn_clicked();
void on_clearOut_clicked();
void on_stopAllServerBtn_clicked();
void on_refreshGameScriptBtn_clicked();
void on_applyScriptBtn_clicked();
void on_recordScreenCheck_clicked(bool checked);
void on_usbConnectBtn_clicked();
void on_wifiConnectBtn_clicked();
void on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item);
void on_updateNameBtn_clicked();
void on_useSingleModeCheck_clicked();
void on_serialBox_currentIndexChanged(const QString &arg1);
void on_startAudioBtn_clicked();
void on_stopAudioBtn_clicked();
void on_installSndcpyBtn_clicked();
void on_autoUpdatecheckBox_toggled(bool checked);
void showIpEditMenu(const QPoint &pos);
private:
bool checkAdbRun();
void initUI();
@ -82,21 +76,25 @@ private:
void execAdbCmd();
void delayMs(int ms);
QString getGameScript(const QString &fileName);
void slotShow();
void slotActivated(QSystemTrayIcon::ActivationReason reason);
int findDeviceFromeSerialBox(bool wifi);
quint32 getBitRate();
const QString &getServerPath();
void loadIpHistory();
void saveIpHistory(const QString &ip);
protected:
void closeEvent(QCloseEvent *event);
private:
Ui::Dialog *ui;
AdbProcess m_adb;
DeviceManage m_deviceManage;
Ui::Widget *ui;
qsc::AdbProcess m_adb;
QSystemTrayIcon *m_hideIcon;
QMenu *m_menu;
QAction *m_showWindow;
QAction *m_quit;
AudioOutput m_audioOutput;
QTimer m_autoUpdatetimer;
};
#endif // DIALOG_H

1190
QtScrcpy/ui/dialog.ui Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,10 +3,11 @@
#include <QMouseEvent>
#include <QShowEvent>
#include "device.h"
#include "iconhelper.h"
#include "toolform.h"
#include "ui_toolform.h"
#include "videoform.h"
#include "../groupcontroller/groupcontroller.h"
ToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticWidget(adsorbWidget, adsorbPos), ui(new Ui::ToolForm)
{
@ -14,6 +15,8 @@ ToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticW
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
//setWindowFlags(windowFlags() & ~Qt::WindowMinMaxButtonsHint);
updateGroupControl();
initStyle();
}
@ -22,13 +25,14 @@ ToolForm::~ToolForm()
delete ui;
}
void ToolForm::setDevice(Device *device)
void ToolForm::setSerial(const QString &serial)
{
if (!device) {
return;
}
m_device = device;
connect(m_device, &Device::controlStateChange, this, &ToolForm::onControlStateChange);
m_serial = serial;
}
bool ToolForm::isHost()
{
return m_isHost;
}
void ToolForm::initStyle()
@ -41,6 +45,7 @@ void ToolForm::initStyle()
IconHelper::Instance()->SetIcon(ui->appSwitchBtn, QChar(0xf24d), 15);
IconHelper::Instance()->SetIcon(ui->volumeUpBtn, QChar(0xf028), 15);
IconHelper::Instance()->SetIcon(ui->volumeDownBtn, QChar(0xf027), 15);
IconHelper::Instance()->SetIcon(ui->openScreenBtn, QChar(0xf06e), 15);
IconHelper::Instance()->SetIcon(ui->closeScreenBtn, QChar(0xf070), 15);
IconHelper::Instance()->SetIcon(ui->powerBtn, QChar(0xf011), 15);
IconHelper::Instance()->SetIcon(ui->expandNotifyBtn, QChar(0xf103), 15);
@ -51,26 +56,23 @@ void ToolForm::initStyle()
void ToolForm::updateGroupControl()
{
if (!m_device) {
return;
}
switch (m_device->controlState()) {
case Device::GroupControlState::GCS_FREE:
ui->groupControlBtn->setStyleSheet("color: #DCDCDC");
break;
case Device::GroupControlState::GCS_HOST:
if (m_isHost) {
ui->groupControlBtn->setStyleSheet("color: red");
break;
case Device::GroupControlState::GCS_CLIENT:
} else {
ui->groupControlBtn->setStyleSheet("color: green");
break;
}
GroupController::instance().updateDeviceState(m_serial);
}
void ToolForm::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
#else
m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
#endif
event->accept();
}
}
@ -83,7 +85,11 @@ void ToolForm::mouseReleaseEvent(QMouseEvent *event)
void ToolForm::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
move(event->globalPos() - m_dragPosition);
#else
move(event->globalPosition().toPoint() - m_dragPosition);
#endif
event->accept();
}
}
@ -102,121 +108,126 @@ void ToolForm::hideEvent(QHideEvent *event)
void ToolForm::on_fullScreenBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->switchFullScreen();
dynamic_cast<VideoForm*>(parent())->switchFullScreen();
}
void ToolForm::on_returnBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postGoBack();
device->postGoBack();
}
void ToolForm::on_homeBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postGoHome();
device->postGoHome();
}
void ToolForm::on_menuBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postGoMenu();
device->postGoMenu();
}
void ToolForm::on_appSwitchBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postAppSwitch();
device->postAppSwitch();
}
void ToolForm::on_powerBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postPower();
device->postPower();
}
void ToolForm::on_screenShotBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->screenshot();
device->screenshot();
}
void ToolForm::on_volumeUpBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postVolumeUp();
device->postVolumeUp();
}
void ToolForm::on_volumeDownBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->postVolumeDown();
device->postVolumeDown();
}
void ToolForm::on_closeScreenBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF);
device->setDisplayPower(false);
}
void ToolForm::on_expandNotifyBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
emit m_device->expandNotificationPanel();
device->expandNotificationPanel();
}
void ToolForm::on_touchBtn_clicked()
{
if (!m_device) {
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
m_showTouch = !m_showTouch;
emit m_device->showTouch(m_showTouch);
device->showTouch(m_showTouch);
}
void ToolForm::on_groupControlBtn_clicked()
{
if (!m_device) {
return;
}
Device::GroupControlState state = m_device->controlState();
if (state == Device::GroupControlState::GCS_FREE) {
emit m_device->setControlState(m_device, Device::GroupControlState::GCS_HOST);
}
if (state == Device::GroupControlState::GCS_HOST) {
emit m_device->setControlState(m_device, Device::GroupControlState::GCS_FREE);
}
}
void ToolForm::onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState)
{
Q_UNUSED(device)
Q_UNUSED(oldState)
Q_UNUSED(newState)
m_isHost = !m_isHost;
updateGroupControl();
}
void ToolForm::on_openScreenBtn_clicked()
{
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (!device) {
return;
}
device->setDisplayPower(true);
}

View file

@ -4,7 +4,7 @@
#include <QPointer>
#include <QWidget>
#include "device.h"
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
#include "magneticwidget.h"
namespace Ui
@ -21,7 +21,8 @@ public:
explicit ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos);
~ToolForm();
void setDevice(Device *device);
void setSerial(const QString& serial);
bool isHost();
protected:
void mousePressEvent(QMouseEvent *event);
@ -45,8 +46,7 @@ private slots:
void on_expandNotifyBtn_clicked();
void on_touchBtn_clicked();
void on_groupControlBtn_clicked();
void onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
void on_openScreenBtn_clicked();
private:
void initStyle();
@ -55,8 +55,9 @@ private:
private:
Ui::ToolForm *ui;
QPoint m_dragPosition;
QPointer<Device> m_device;
QString m_serial;
bool m_showTouch = false;
bool m_isHost = false;
};
#endif // TOOLFORM_H

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