mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-23 23:04:43 +01:00
Compare commits
779 Commits
ctx
...
renderer.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0c8467d09 | ||
|
|
7266988bb3 | ||
|
|
ef4c377228 | ||
|
|
ea4554b0d5 | ||
|
|
8e836379dc | ||
|
|
cc3be7e46a | ||
|
|
6c59013205 | ||
|
|
ba4156b0c2 | ||
|
|
1cec2b0018 | ||
|
|
efe67fa8ec | ||
|
|
6c65f3a774 | ||
|
|
a7ac4d560f | ||
|
|
d40cc6f233 | ||
|
|
9426feabbb | ||
|
|
70b3b58705 | ||
|
|
b75270b11d | ||
|
|
342d8c31f0 | ||
|
|
7720d32357 | ||
|
|
80008accab | ||
|
|
c497718a3e | ||
|
|
07f056353b | ||
|
|
1a99a46b57 | ||
|
|
a9de53f0cb | ||
|
|
c07500bb03 | ||
|
|
373b366fa5 | ||
|
|
5607f12f2a | ||
|
|
8ac04d39f4 | ||
|
|
af355804ef | ||
|
|
1018f3e9be | ||
|
|
48fcfdd104 | ||
|
|
553813e97d | ||
|
|
968f178205 | ||
|
|
1b13d0a22d | ||
|
|
078565d40b | ||
|
|
cc7c07d4a3 | ||
|
|
24b46f11a5 | ||
|
|
f348a1f307 | ||
|
|
f8846aa76d | ||
|
|
1e51d2c83f | ||
|
|
826076f753 | ||
|
|
c36433999c | ||
|
|
78cba1b7c2 | ||
|
|
1015b42e53 | ||
|
|
f4cc07da24 | ||
|
|
fde02a7dfa | ||
|
|
dee1fd46a6 | ||
|
|
a90a039f50 | ||
|
|
ab04e0348d | ||
|
|
99b955f390 | ||
|
|
bbb855b5b0 | ||
|
|
4879950a06 | ||
|
|
170b7a02e7 | ||
|
|
9f1aac41a6 | ||
|
|
02989249f6 | ||
|
|
f8e0b9be4b | ||
|
|
ed62ca124c | ||
|
|
3571fe62ed | ||
|
|
bca98133d1 | ||
|
|
881c71b2e8 | ||
|
|
09eed565fc | ||
|
|
b0da401e6d | ||
|
|
dba2a3778f | ||
|
|
cda4387058 | ||
|
|
42632d3f53 | ||
|
|
db2f72a58a | ||
|
|
fb6381f5b9 | ||
|
|
06fd3b4786 | ||
|
|
1d1a4103cc | ||
|
|
5b51396a8c | ||
|
|
7e66062086 | ||
|
|
6f9eb31d52 | ||
|
|
9cfa5b197a | ||
|
|
b08093d1c0 | ||
|
|
7dd9bcaf60 | ||
|
|
3530851071 | ||
|
|
d0047b2110 | ||
|
|
3281fda6ef | ||
|
|
925949d54a | ||
|
|
f3d4fde15b | ||
|
|
eee3f24739 | ||
|
|
3e40b24737 | ||
|
|
9d7a4c88e0 | ||
|
|
e5e58b1b30 | ||
|
|
10a0974f43 | ||
|
|
be21e43be5 | ||
|
|
bfb0872493 | ||
|
|
e11399aff0 | ||
|
|
9d56d26d45 | ||
|
|
f663bbec12 | ||
|
|
2506d1768b | ||
|
|
4ee94cb845 | ||
|
|
afaca80b37 | ||
|
|
8057835a0d | ||
|
|
e47529ab9c | ||
|
|
939c8e7f68 | ||
|
|
eb576c44f8 | ||
|
|
0522d02d40 | ||
|
|
30bfc80f9b | ||
|
|
c3d2ef1b1f | ||
|
|
a79ddc35a7 | ||
|
|
04542a9f58 | ||
|
|
8761dcb7a8 | ||
|
|
f01231dff8 | ||
|
|
5b18ce0d2e | ||
|
|
4841fdd1ef | ||
|
|
fc75319bb2 | ||
|
|
7c8bdccbdc | ||
|
|
9787fe5d26 | ||
|
|
98d30288f7 | ||
|
|
d74cfd5711 | ||
|
|
cd3a5d50b6 | ||
|
|
772f42134a | ||
|
|
38256d8ff9 | ||
|
|
4e1cf13a50 | ||
|
|
696402c68c | ||
|
|
dc169e425e | ||
|
|
13fc75902a | ||
|
|
454beaa757 | ||
|
|
1a9ffb3814 | ||
|
|
ac16be54c8 | ||
|
|
8a02e3c2f5 | ||
|
|
283326b2f6 | ||
|
|
ca4f50c5ef | ||
|
|
7a3fe830d4 | ||
|
|
ee414231ed | ||
|
|
41ed40f5f9 | ||
|
|
d2cc930975 | ||
|
|
52f5d08d1f | ||
|
|
70bfa2cf39 | ||
|
|
38f779d9d3 | ||
|
|
8cd63cb63e | ||
|
|
cc309a2b34 | ||
|
|
91a4a74641 | ||
|
|
48f38c4bb6 | ||
|
|
6875e9aa88 | ||
|
|
c5ed2cfc28 | ||
|
|
1a0d300786 | ||
|
|
d2447b5c19 | ||
|
|
5900e9e39c | ||
|
|
882003f314 | ||
|
|
db9dc6ae83 | ||
|
|
e0f37f834b | ||
|
|
89b624770c | ||
|
|
79227af89f | ||
|
|
5d12d9071d | ||
|
|
b7add42154 | ||
|
|
dd1bfae4e0 | ||
|
|
bef2d8473b | ||
|
|
609719bde0 | ||
|
|
3a0703f428 | ||
|
|
245981281e | ||
|
|
1d25338119 | ||
|
|
457c7fe5cf | ||
|
|
7998811fa5 | ||
|
|
7044122fc5 | ||
|
|
c63d9e1803 | ||
|
|
d892a9aac5 | ||
|
|
fd8bef68b7 | ||
|
|
986328ff9e | ||
|
|
0ba9d35705 | ||
|
|
cac8e9c821 | ||
|
|
1c7680f689 | ||
|
|
c27d116a66 | ||
|
|
eac711ace6 | ||
|
|
af15c72f9c | ||
|
|
5b1229a55f | ||
|
|
69858c6f43 | ||
|
|
e0423653c8 | ||
|
|
5387644160 | ||
|
|
2f44da76f4 | ||
|
|
95c4f03c1b | ||
|
|
fb47b87eeb | ||
|
|
5ae01749bf | ||
|
|
1fd57ede1f | ||
|
|
48fc18e380 | ||
|
|
ea6a94d355 | ||
|
|
dc2fcc46f5 | ||
|
|
69264703b1 | ||
|
|
ec4e826976 | ||
|
|
17e205e54f | ||
|
|
f751274b17 | ||
|
|
6469054b15 | ||
|
|
0e2d084751 | ||
|
|
754f4fc6fe | ||
|
|
aca6d30af5 | ||
|
|
f2018e026c | ||
|
|
a507b4f559 | ||
|
|
a9aadc95df | ||
|
|
28b5bfb90e | ||
|
|
65256d7cc7 | ||
|
|
328bb74f80 | ||
|
|
7418fd0662 | ||
|
|
0a09518a49 | ||
|
|
27a5934a1d | ||
|
|
86a68fac6c | ||
|
|
1786f28e6f | ||
|
|
9cf4d52721 | ||
|
|
4bd1c5981d | ||
|
|
c59a3c3169 | ||
|
|
2780e0bd7b | ||
|
|
6c6607d404 | ||
|
|
988174805c | ||
|
|
f90dc216d1 | ||
|
|
97fa77c76c | ||
|
|
baa10ed0a3 | ||
|
|
2ed2247e8f | ||
|
|
5febb1e9fb | ||
|
|
5c3626ed47 | ||
|
|
0e473eb005 | ||
|
|
b26b4fb745 | ||
|
|
9555d3a537 | ||
|
|
aea6a371aa | ||
|
|
dc6c279b1e | ||
|
|
6d0ac3626d | ||
|
|
beee42fb06 | ||
|
|
131372d2c4 | ||
|
|
0fd7534bd5 | ||
|
|
36574d2ee7 | ||
|
|
3b2b3625e4 | ||
|
|
b2cdaa4bdc | ||
|
|
d01373c03c | ||
|
|
ff06b6dcc1 | ||
|
|
017a3672a4 | ||
|
|
c1351b250e | ||
|
|
618a978f5b | ||
|
|
acddd811bf | ||
|
|
ee9f7126ff | ||
|
|
a18ed1ee7a | ||
|
|
678025b316 | ||
|
|
3e689020ba | ||
|
|
3d1f036c04 | ||
|
|
3d5294c1e5 | ||
|
|
1d2f16dbb5 | ||
|
|
7fef051976 | ||
|
|
da8ade88fd | ||
|
|
74aecc00b5 | ||
|
|
5e05f2a25b | ||
|
|
3d478d7d5b | ||
|
|
54e1f8e060 | ||
|
|
d40224f299 | ||
|
|
0628ffcb0b | ||
|
|
6f9520f3e2 | ||
|
|
a7efb180b9 | ||
|
|
28c372e838 | ||
|
|
cb19686d79 | ||
|
|
93da693e8c | ||
|
|
179c664e2b | ||
|
|
360936248c | ||
|
|
98d2065d6d | ||
|
|
6a81fc438b | ||
|
|
cf0098abf0 | ||
|
|
73b595c806 | ||
|
|
d74f564f56 | ||
|
|
7fc6943284 | ||
|
|
a57180047c | ||
|
|
5df218d8f9 | ||
|
|
26bf209617 | ||
|
|
dc82425769 | ||
|
|
9f39a5f2d6 | ||
|
|
24588cb637 | ||
|
|
0e50d1e7db | ||
|
|
264110fd70 | ||
|
|
4608a19a13 | ||
|
|
f1f2711626 | ||
|
|
eeb04292a4 | ||
|
|
2ec30bdf80 | ||
|
|
145b823b1d | ||
|
|
28d64ef319 | ||
|
|
36d61f9ecd | ||
|
|
f95a5f97b1 | ||
|
|
adb674a5c8 | ||
|
|
d19045628e | ||
|
|
443f315f60 | ||
|
|
0904880816 | ||
|
|
4348f12194 | ||
|
|
371ff31225 | ||
|
|
456fa510f2 | ||
|
|
45382e3f01 | ||
|
|
9b03bfc3ae | ||
|
|
39d51ff2cc | ||
|
|
d72686c867 | ||
|
|
06385ce83b | ||
|
|
9fb0a3dac1 | ||
|
|
23960ca11a | ||
|
|
904f86152e | ||
|
|
e226950cfa | ||
|
|
019ce5eea4 | ||
|
|
d6033d28f5 | ||
|
|
89518f49ad | ||
|
|
2a04858a22 | ||
|
|
e411b74a16 | ||
|
|
5694562a74 | ||
|
|
bd9d93194b | ||
|
|
794595e3f0 | ||
|
|
5e10c37f02 | ||
|
|
0e399b65bd | ||
|
|
2337f524d1 | ||
|
|
df74cceb6f | ||
|
|
91373d906b | ||
|
|
04dd72b594 | ||
|
|
762816cac6 | ||
|
|
c0e2e27cf9 | ||
|
|
eff5b4b219 | ||
|
|
d3db9c4065 | ||
|
|
5936167ff7 | ||
|
|
e9dd0f68ad | ||
|
|
104195fc3b | ||
|
|
9958302e6f | ||
|
|
69b836930a | ||
|
|
790ea5e58c | ||
|
|
1270997f6b | ||
|
|
c905fbba8d | ||
|
|
f08a6d86c5 | ||
|
|
3ac4b64461 | ||
|
|
c7378f4dc8 | ||
|
|
e26bdb07a2 | ||
|
|
04a3e6fb06 | ||
|
|
c29ecd0314 | ||
|
|
d62fa8880e | ||
|
|
1f6634ea87 | ||
|
|
58ba00fa06 | ||
|
|
569c37cec1 | ||
|
|
58a0fbbf2e | ||
|
|
acff5b005c | ||
|
|
5474ae6bd6 | ||
|
|
2c25fd7a80 | ||
|
|
ce21f515e3 | ||
|
|
381fe95867 | ||
|
|
566b5be0f6 | ||
|
|
dd20efa41c | ||
|
|
13ce277e1f | ||
|
|
9c9d92fb1c | ||
|
|
408a388fc5 | ||
|
|
98ed5eb643 | ||
|
|
5d0e012a4c | ||
|
|
d19396718e | ||
|
|
7024d38199 | ||
|
|
f1368d9a8f | ||
|
|
d916429566 | ||
|
|
7cfefae5e1 | ||
|
|
b60e174780 | ||
|
|
5851b62580 | ||
|
|
12d5ca4d5e | ||
|
|
68e54d9b0b | ||
|
|
5f0480c039 | ||
|
|
874eaec487 | ||
|
|
14e5439dee | ||
|
|
a5844e198e | ||
|
|
2687d20280 | ||
|
|
9c0a328498 | ||
|
|
02ef3d57ce | ||
|
|
538a32a539 | ||
|
|
67d4dfb5ff | ||
|
|
9578aae34e | ||
|
|
7b3dd595b4 | ||
|
|
a46150f753 | ||
|
|
3acffaae57 | ||
|
|
e33be3d288 | ||
|
|
c15df01171 | ||
|
|
09741bc805 | ||
|
|
afbaf59abb | ||
|
|
5b10650f22 | ||
|
|
0d8014be52 | ||
|
|
064670ab4c | ||
|
|
ff9fb5994d | ||
|
|
a36de26969 | ||
|
|
281fcc7052 | ||
|
|
65fc53eace | ||
|
|
a6f74d72f5 | ||
|
|
e724ff4349 | ||
|
|
79014143b9 | ||
|
|
c0a6432967 | ||
|
|
ec602a0334 | ||
|
|
7a9ea5c66f | ||
|
|
d92b7a6024 | ||
|
|
0bb3955b95 | ||
|
|
62776fb261 | ||
|
|
10f60054ac | ||
|
|
42fb947780 | ||
|
|
2e7a15a998 | ||
|
|
a7e61fb871 | ||
|
|
0cc6f6aa09 | ||
|
|
f69ac40534 | ||
|
|
665ccb32f5 | ||
|
|
292adf294d | ||
|
|
f9f3bfabe3 | ||
|
|
6d23a389ca | ||
|
|
337901368e | ||
|
|
4cc4abdcc8 | ||
|
|
befc0fac5b | ||
|
|
f01a622ead | ||
|
|
0ba430a462 | ||
|
|
91d40c7548 | ||
|
|
9f3d51106d | ||
|
|
bf2b679e70 | ||
|
|
7f250dd669 | ||
|
|
68e27c7357 | ||
|
|
c4febd55eb | ||
|
|
f9d1a333a0 | ||
|
|
64a25f6e9d | ||
|
|
5fe884276b | ||
|
|
3e68244dd3 | ||
|
|
a34a62ca4b | ||
|
|
a59c6df4b7 | ||
|
|
f4d1e49ad9 | ||
|
|
4565f36ee6 | ||
|
|
c8479fe8bf | ||
|
|
de8455400c | ||
|
|
1f5be743b4 | ||
|
|
222916eebe | ||
|
|
6c707ad8a3 | ||
|
|
d748ac75e6 | ||
|
|
6f0c9eba9b | ||
|
|
f6219d2640 | ||
|
|
6e9b0d7d4c | ||
|
|
3e9c89c535 | ||
|
|
9af3bacdd6 | ||
|
|
2dd02ebb80 | ||
|
|
dad04bf138 | ||
|
|
1afc8ca368 | ||
|
|
785099b74d | ||
|
|
08da2e068e | ||
|
|
49c8ca34fd | ||
|
|
a84b0dfd0c | ||
|
|
cbf5db85c1 | ||
|
|
72ee195693 | ||
|
|
8620d06741 | ||
|
|
e9240f6804 | ||
|
|
e9b32d8a52 | ||
|
|
ce4e1fc420 | ||
|
|
e8f02685e9 | ||
|
|
4a6b335f7d | ||
|
|
90ee0062cb | ||
|
|
e03888d587 | ||
|
|
8453e3ba7d | ||
|
|
145a9468fd | ||
|
|
1d713d7598 | ||
|
|
265a15e0b1 | ||
|
|
6451ad271a | ||
|
|
bec3321fff | ||
|
|
dea1fe3386 | ||
|
|
a7cae59578 | ||
|
|
f089ea67e1 | ||
|
|
63ced79842 | ||
|
|
33a8c39beb | ||
|
|
903a5aaaf5 | ||
|
|
21b412cd98 | ||
|
|
3b241af3f6 | ||
|
|
21e2e2606e | ||
|
|
0c95794463 | ||
|
|
523f939532 | ||
|
|
dd47cefa47 | ||
|
|
44b3fd82b1 | ||
|
|
cc41115625 | ||
|
|
773c23fda2 | ||
|
|
992b4922fe | ||
|
|
67f93350f6 | ||
|
|
52136268ef | ||
|
|
0a6ccdc4df | ||
|
|
5d2441d198 | ||
|
|
2b6089cbfc | ||
|
|
f691ebb1b4 | ||
|
|
071d459ad7 | ||
|
|
bbfac9ae1f | ||
|
|
65bd6bd8d4 | ||
|
|
ed4066902d | ||
|
|
127a271d34 | ||
|
|
31116a60d7 | ||
|
|
a10f8cd798 | ||
|
|
53c6eb66ea | ||
|
|
0f076083e8 | ||
|
|
053bf83f58 | ||
|
|
414ce4c754 | ||
|
|
a2f3a5cf18 | ||
|
|
5e605b9b8f | ||
|
|
cf09e78323 | ||
|
|
3b8ec0c38d | ||
|
|
39132ff2dd | ||
|
|
9d1d79b004 | ||
|
|
e0cdc2ace3 | ||
|
|
bbcd763612 | ||
|
|
c57a0512ba | ||
|
|
e84db2914d | ||
|
|
80ca7b15e5 | ||
|
|
79242957a0 | ||
|
|
fe7494c492 | ||
|
|
9989668226 | ||
|
|
6baea57987 | ||
|
|
86b8286217 | ||
|
|
51fee79bf5 | ||
|
|
6808288823 | ||
|
|
0bce4d7f56 | ||
|
|
6d98766cd5 | ||
|
|
487a6b9cf4 | ||
|
|
b50f9eb41d | ||
|
|
46041e0cc0 | ||
|
|
b419eef55e | ||
|
|
cc8e6133b0 | ||
|
|
126da0cb18 | ||
|
|
1d3b6dac69 | ||
|
|
a4f8c02502 | ||
|
|
a8871bfad7 | ||
|
|
89df38f641 | ||
|
|
c95e6964c5 | ||
|
|
343f715323 | ||
|
|
f13f00021f | ||
|
|
48c2c03093 | ||
|
|
1e3deabd6c | ||
|
|
7633228278 | ||
|
|
f5e6b8092a | ||
|
|
035d60cf5d | ||
|
|
40493dff60 | ||
|
|
09ce0307fe | ||
|
|
9fa30ab1ae | ||
|
|
0b926922bc | ||
|
|
24bcc3fa2b | ||
|
|
592ca0b59b | ||
|
|
30e42af2d4 | ||
|
|
9030bd8be4 | ||
|
|
576e7552a2 | ||
|
|
24b9e0a970 | ||
|
|
9ea4446369 | ||
|
|
5d1d5bdc16 | ||
|
|
fd9498e07c | ||
|
|
c27ab46efb | ||
|
|
b5849db32f | ||
|
|
09e8c20168 | ||
|
|
da484b7ab9 | ||
|
|
063a8339ed | ||
|
|
b5c8de08e0 | ||
|
|
45fe6b602b | ||
|
|
cca2c9ffb7 | ||
|
|
22d78e8a82 | ||
|
|
bcb8503b26 | ||
|
|
9aa6cc71be | ||
|
|
54e08b4eae | ||
|
|
bd8b945bb3 | ||
|
|
a73bf932d6 | ||
|
|
7011dd1ef0 | ||
|
|
ee6620d123 | ||
|
|
aa34d63171 | ||
|
|
206809a99a | ||
|
|
bf625790fa | ||
|
|
db55edb196 | ||
|
|
1c3801a0b1 | ||
|
|
be3d357a6d | ||
|
|
79968a0ae6 | ||
|
|
7f23ff3f2c | ||
|
|
cc7719079a | ||
|
|
0c94b75eef | ||
|
|
af57309074 | ||
|
|
a720c946a6 | ||
|
|
8d87b91f69 | ||
|
|
125b1103e1 | ||
|
|
36189b90ea | ||
|
|
4dca08cfe3 | ||
|
|
fd0f432e87 | ||
|
|
cdf09805c0 | ||
|
|
bf069bd37b | ||
|
|
b9d244b4c9 | ||
|
|
dd479ed176 | ||
|
|
5f12132c47 | ||
|
|
1c5ad0e813 | ||
|
|
6a103c809f | ||
|
|
151a6225d4 | ||
|
|
54dede3630 | ||
|
|
f557188dc8 | ||
|
|
87da68ee0d | ||
|
|
021c5d371a | ||
|
|
840680f546 | ||
|
|
4d5b67cc80 | ||
|
|
604e59ac7b | ||
|
|
4d2c2514fc | ||
|
|
107f7a83ab | ||
|
|
2e7f6a6fc4 | ||
|
|
d95276467b | ||
|
|
91485e2863 | ||
|
|
f2d6203156 | ||
|
|
2d32557fde | ||
|
|
ae303b8d07 | ||
|
|
29ce03e337 | ||
|
|
48adae1728 | ||
|
|
ea98d49bae | ||
|
|
35add3daee | ||
|
|
c0a1aee8ce | ||
|
|
a976417572 | ||
|
|
ffa238b9d3 | ||
|
|
f6459dd742 | ||
|
|
295102a6d9 | ||
|
|
d894e270a7 | ||
|
|
746eaea556 | ||
|
|
78a7e4f293 | ||
|
|
9858eff856 | ||
|
|
9e22f3bf1c | ||
|
|
25f1e703b7 | ||
|
|
a7cf4daf3b | ||
|
|
c12fdf900f | ||
|
|
4502126e3b | ||
|
|
dfa3f97a87 | ||
|
|
edac4b8a9a | ||
|
|
44abed5c68 | ||
|
|
cfa4f7e2f2 | ||
|
|
d47ecef1b5 | ||
|
|
9efa162949 | ||
|
|
be3f949aa5 | ||
|
|
f7b4a18b43 | ||
|
|
05b5deacad | ||
|
|
d25cbc55f2 | ||
|
|
3333e67452 | ||
|
|
7c53a29d72 | ||
|
|
5187f7254e | ||
|
|
2ad93d1fc0 | ||
|
|
d067a11478 | ||
|
|
cd4056d0f3 | ||
|
|
6a58891e13 | ||
|
|
ec41896c85 | ||
|
|
4cd61b5a90 | ||
|
|
d2ed4510a7 | ||
|
|
604dfd7c6b | ||
|
|
af69689ec1 | ||
|
|
cbce42336d | ||
|
|
5a6b8310ca | ||
|
|
c9a4d2b38f | ||
|
|
1beec99f82 | ||
|
|
5ce8672ebc | ||
|
|
3001f8a2d5 | ||
|
|
c6ff78f414 | ||
|
|
40f2560d98 | ||
|
|
26aa28c998 | ||
|
|
ef79fcbbd2 | ||
|
|
9497f39fb4 | ||
|
|
bf056b1fee | ||
|
|
bd9292931e | ||
|
|
140a49b8be | ||
|
|
4135c411af | ||
|
|
5e061636f6 | ||
|
|
5f3fb843f5 | ||
|
|
ce8126f322 | ||
|
|
d037b02cc2 | ||
|
|
89761213c3 | ||
|
|
8db4e78b34 | ||
|
|
5d4b8a7e6d | ||
|
|
eed06b141a | ||
|
|
825d7f72c0 | ||
|
|
2370298b61 | ||
|
|
67f356f881 | ||
|
|
c573bd2a33 | ||
|
|
acb2988837 | ||
|
|
85a94dd4b5 | ||
|
|
94031dfe97 | ||
|
|
b43a9e8e7a | ||
|
|
a9d6cb5837 | ||
|
|
2f92686930 | ||
|
|
bb88b60227 | ||
|
|
25e33566f5 | ||
|
|
9df92ebe37 | ||
|
|
0801cf0627 | ||
|
|
4658c0e5d2 | ||
|
|
45a073a333 | ||
|
|
7e3b935932 | ||
|
|
abcb100597 | ||
|
|
e8801cc3c0 | ||
|
|
86808e8114 | ||
|
|
15a3bad4ab | ||
|
|
200488111e | ||
|
|
1713422c13 | ||
|
|
4b4f045e19 | ||
|
|
a402eac7f2 | ||
|
|
3bb6b0cb9f | ||
|
|
258eaaae2a | ||
|
|
4857c5dd59 | ||
|
|
f23be823fd | ||
|
|
783719c72e | ||
|
|
80defdd8aa | ||
|
|
e637feba51 | ||
|
|
5e59ed3135 | ||
|
|
4eb33054cd | ||
|
|
420d3a40dd | ||
|
|
9d5f53caa7 | ||
|
|
3c45625324 | ||
|
|
11d738321f | ||
|
|
ccaa832f48 | ||
|
|
4e4ddc499f | ||
|
|
8d76b3e06d | ||
|
|
85a0b935c9 | ||
|
|
8c3e2bae7b | ||
|
|
446ea818a4 | ||
|
|
c3c7bf7af3 | ||
|
|
5000368c2f | ||
|
|
855ae4adb1 | ||
|
|
a8db3ec9e2 | ||
|
|
ff579990c2 | ||
|
|
b8c5853aa6 | ||
|
|
c64d150202 | ||
|
|
8350a61926 | ||
|
|
5580803406 | ||
|
|
9bfc749803 | ||
|
|
6af4bd601f | ||
|
|
4722bff423 | ||
|
|
928f8b8eb3 | ||
|
|
9fc5835485 | ||
|
|
dd36d6135f | ||
|
|
faebb7d70a | ||
|
|
7f8d079c8c | ||
|
|
64930e71b9 | ||
|
|
d544e577c0 | ||
|
|
bfeecc0131 | ||
|
|
f032262cd7 | ||
|
|
cd63896d63 | ||
|
|
f085765e04 | ||
|
|
a2fb1b40f6 | ||
|
|
41ccb5883e | ||
|
|
23e116064d | ||
|
|
3432029a3d | ||
|
|
7a2b756f1e | ||
|
|
b7ad652a75 | ||
|
|
76a99a7fcd | ||
|
|
68b55ef2fe | ||
|
|
bc8913e12b | ||
|
|
3c2013de10 | ||
|
|
8cef8bac94 | ||
|
|
0bbe8a7007 | ||
|
|
9fdb882509 | ||
|
|
8e7b041f35 | ||
|
|
9ade389069 | ||
|
|
90ba885547 | ||
|
|
7adf98e9d4 | ||
|
|
90ca46ee41 | ||
|
|
1c864a88eb | ||
|
|
1650b7c058 | ||
|
|
a7c3c9a54c | ||
|
|
111d02fca4 | ||
|
|
36670dda40 | ||
|
|
0983f0a194 | ||
|
|
110b3a16f6 | ||
|
|
1ee46970e3 | ||
|
|
fcdf847dd3 | ||
|
|
ad05a01800 | ||
|
|
328ed3650d | ||
|
|
c14668b177 | ||
|
|
637f48f360 | ||
|
|
d391fc3b69 | ||
|
|
75ad925423 | ||
|
|
7e936fa879 | ||
|
|
01d785d9a3 | ||
|
|
fe6e9acb36 | ||
|
|
625934fb1b | ||
|
|
85b55b3c4e | ||
|
|
7b7076ef85 | ||
|
|
808bd14e30 | ||
|
|
0049b3ce07 | ||
|
|
5764f47fee | ||
|
|
2dab1f7024 | ||
|
|
744312ec64 | ||
|
|
b9315620e2 | ||
|
|
ea59d525bd | ||
|
|
0ffcfa0f5c | ||
|
|
c0f3c080b6 | ||
|
|
d046678f85 | ||
|
|
fae3fbc934 | ||
|
|
5061b7e02c | ||
|
|
09009c2aa7 | ||
|
|
fb21bbf763 | ||
|
|
0f1afff7a6 | ||
|
|
48a00fb481 | ||
|
|
3b7e2ca9c8 | ||
|
|
4ad7479425 | ||
|
|
958f22490b | ||
|
|
cb20bcb16f | ||
|
|
8f0b38cc4f | ||
|
|
a1e8a34001 | ||
|
|
00534b0b2d | ||
|
|
21df2c240e | ||
|
|
2d3059e1ab | ||
|
|
478aece68f | ||
|
|
55899c091e | ||
|
|
d9a644df9c | ||
|
|
45717733a1 | ||
|
|
6ad037ea04 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github: [rom1v]
|
||||
liberapay: rom1v
|
||||
custom: ["https://paypal.me/rom2v"]
|
||||
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,17 +7,25 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md).
|
||||
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
|
||||
_Please read the [prerequisites] to run scrcpy._
|
||||
|
||||
**Environment**
|
||||
- OS: [e.g. Debian, Windows, macOS...]
|
||||
- scrcpy version: [e.g. 1.12.1]
|
||||
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
|
||||
- device model:
|
||||
- Android version: [e.g. 10]
|
||||
[prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites
|
||||
|
||||
_Also read the [FAQ] and check if your [issue][issues] already exists._
|
||||
|
||||
[FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md
|
||||
[issues]: https://github.com/Genymobile/scrcpy/issues
|
||||
|
||||
## Environment
|
||||
|
||||
- **OS:** [e.g. Debian, Windows, macOS...]
|
||||
- **Scrcpy version:** [e.g. 2.5]
|
||||
- **Installation method:** [e.g. manual build, apt, snap, brew, Windows release...]
|
||||
- **Device model:**
|
||||
- **Android version:** [e.g. 14]
|
||||
|
||||
## Describe the bug
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
On errors, please provide the output of the console (and `adb logcat` if relevant).
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about scrcpy
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
529
.github/workflows/release.yml
vendored
Normal file
529
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,529 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
name:
|
||||
description: 'Version name (default is ref name)'
|
||||
|
||||
env:
|
||||
# $VERSION is used by release scripts
|
||||
VERSION: ${{ github.event.inputs.name || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
test-scrcpy-server:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GRADLE: gradle # use native gradle instead of ./gradlew in scripts
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Test scrcpy-server
|
||||
run: release/test_server.sh
|
||||
|
||||
build-scrcpy-server:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GRADLE: gradle # use native gradle instead of ./gradlew in scripts
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Build
|
||||
run: release/build_server.sh
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/scrcpy-server
|
||||
|
||||
test-build-scrcpy-server-without-gradle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Build without gradle
|
||||
run: server/build_without_gradle.sh
|
||||
|
||||
test-client:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Same as build-linux-x86_64
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
|
||||
# SDL3 is not available in Ubuntu yet
|
||||
- name: Install SDL3
|
||||
run: |
|
||||
app/deps/sdl.sh linux native shared
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
export PKG_CONFIG_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib/pkgconfig
|
||||
export LD_LIBRARY_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib
|
||||
release/test_client.sh
|
||||
|
||||
build-linux-x86_64:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check architecture
|
||||
run: |
|
||||
arch=$(uname -m)
|
||||
if [[ "$arch" != x86_64 ]]
|
||||
then
|
||||
echo "Unexpected architecture: $arch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
|
||||
- name: Build
|
||||
run: release/build_linux.sh x86_64
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Tar
|
||||
run: |
|
||||
cd release/work/build-linux-x86_64
|
||||
mkdir dist-tar
|
||||
cd dist-tar
|
||||
tar -C .. -cvf dist.tar.gz dist/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-linux-x86_64-intermediate
|
||||
path: release/work/build-linux-x86_64/dist-tar/
|
||||
|
||||
build-win32:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
run: release/build_windows.sh 32
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Tar
|
||||
run: |
|
||||
cd release/work/build-win32
|
||||
mkdir dist-tar
|
||||
cd dist-tar
|
||||
tar -C .. -cvf dist.tar.gz dist/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-win32-intermediate
|
||||
path: release/work/build-win32/dist-tar/
|
||||
|
||||
build-win64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
run: release/build_windows.sh 64
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Tar
|
||||
run: |
|
||||
cd release/work/build-win64
|
||||
mkdir dist-tar
|
||||
cd dist-tar
|
||||
tar -C .. -cvf dist.tar.gz dist/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-win64-intermediate
|
||||
path: release/work/build-win64/dist-tar/
|
||||
|
||||
build-macos-aarch64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check architecture
|
||||
run: |
|
||||
arch=$(uname -m)
|
||||
if [[ "$arch" != arm64 ]]
|
||||
then
|
||||
echo "Unexpected architecture: $arch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install meson nasm libiconv zlib automake autoconf libtool
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
# the default Xcode (and macOS SDK) version can be found at
|
||||
# <https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#xcode>
|
||||
#
|
||||
# then the minimal supported deployment target of that macOS SDK can be found at
|
||||
# <https://developer.apple.com/support/xcode/#minimum-requirements>
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.13
|
||||
run: release/build_macos.sh aarch64
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Tar
|
||||
run: |
|
||||
cd release/work/build-macos-aarch64
|
||||
mkdir dist-tar
|
||||
cd dist-tar
|
||||
tar -C .. -cvf dist.tar.gz dist/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-macos-aarch64-intermediate
|
||||
path: release/work/build-macos-aarch64/dist-tar/
|
||||
|
||||
build-macos-x86_64:
|
||||
runs-on: macos-15-intel
|
||||
steps:
|
||||
- name: Check architecture
|
||||
run: |
|
||||
arch=$(uname -m)
|
||||
if [[ "$arch" != x86_64 ]]
|
||||
then
|
||||
echo "Unexpected architecture: $arch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install meson nasm libiconv zlib automake
|
||||
# autoconf and libtool are already installed on macos-13
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
# the default Xcode (and macOS SDK) version can be found at
|
||||
# <https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode>
|
||||
#
|
||||
# then the minimal supported deployment target of that macOS SDK can be found at
|
||||
# <https://developer.apple.com/support/xcode/#minimum-requirements>
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.13
|
||||
run: release/build_macos.sh x86_64
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Tar
|
||||
run: |
|
||||
cd release/work/build-macos-x86_64
|
||||
mkdir dist-tar
|
||||
cd dist-tar
|
||||
tar -C .. -cvf dist.tar.gz dist/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-macos-x86_64-intermediate
|
||||
path: release/work/build-macos-x86_64/dist-tar/
|
||||
|
||||
package-linux-x86_64:
|
||||
needs:
|
||||
- build-scrcpy-server
|
||||
- build-linux-x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download scrcpy-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/
|
||||
|
||||
- name: Download build-linux-x86_64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-linux-x86_64-intermediate
|
||||
path: release/work/build-linux-x86_64/dist-tar/
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Detar
|
||||
run: |
|
||||
cd release/work/build-linux-x86_64
|
||||
tar xf dist-tar/dist.tar.gz
|
||||
|
||||
- name: Package
|
||||
run: release/package_client.sh linux-x86_64 tar.gz
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-linux-x86_64
|
||||
path: release/output/
|
||||
|
||||
package-win32:
|
||||
needs:
|
||||
- build-scrcpy-server
|
||||
- build-win32
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download scrcpy-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/
|
||||
|
||||
- name: Download build-win32
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-win32-intermediate
|
||||
path: release/work/build-win32/dist-tar/
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Detar
|
||||
run: |
|
||||
cd release/work/build-win32
|
||||
tar xf dist-tar/dist.tar.gz
|
||||
|
||||
- name: Package
|
||||
run: release/package_client.sh win32 zip
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-win32
|
||||
path: release/output/
|
||||
|
||||
package-win64:
|
||||
needs:
|
||||
- build-scrcpy-server
|
||||
- build-win64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download scrcpy-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/
|
||||
|
||||
- name: Download build-win64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-win64-intermediate
|
||||
path: release/work/build-win64/dist-tar/
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Detar
|
||||
run: |
|
||||
cd release/work/build-win64
|
||||
tar xf dist-tar/dist.tar.gz
|
||||
|
||||
- name: Package
|
||||
run: release/package_client.sh win64 zip
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-win64
|
||||
path: release/output
|
||||
|
||||
package-macos-aarch64:
|
||||
needs:
|
||||
- build-scrcpy-server
|
||||
- build-macos-aarch64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download scrcpy-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/
|
||||
|
||||
- name: Download build-macos-aarch64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-macos-aarch64-intermediate
|
||||
path: release/work/build-macos-aarch64/dist-tar/
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Detar
|
||||
run: |
|
||||
cd release/work/build-macos-aarch64
|
||||
tar xf dist-tar/dist.tar.gz
|
||||
|
||||
- name: Package
|
||||
run: release/package_client.sh macos-aarch64 tar.gz
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-macos-aarch64
|
||||
path: release/output/
|
||||
|
||||
package-macos-x86_64:
|
||||
needs:
|
||||
- build-scrcpy-server
|
||||
- build-macos-x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download scrcpy-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/
|
||||
|
||||
- name: Download build-macos
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-macos-x86_64-intermediate
|
||||
path: release/work/build-macos-x86_64/dist-tar/
|
||||
|
||||
# upload-artifact does not preserve permissions
|
||||
- name: Detar
|
||||
run: |
|
||||
cd release/work/build-macos-x86_64
|
||||
tar xf dist-tar/dist.tar.gz
|
||||
|
||||
- name: Package
|
||||
run: release/package_client.sh macos-x86_64 tar.gz
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-macos-x86_64
|
||||
path: release/output/
|
||||
|
||||
release:
|
||||
needs:
|
||||
- build-scrcpy-server
|
||||
- package-linux-x86_64
|
||||
- package-win32
|
||||
- package-win64
|
||||
- package-macos-aarch64
|
||||
- package-macos-x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download scrcpy-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scrcpy-server
|
||||
path: release/work/build-server/server/
|
||||
|
||||
- name: Download release-linux-x86_64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-linux-x86_64
|
||||
path: release/output/
|
||||
|
||||
- name: Download release-win32
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-win32
|
||||
path: release/output/
|
||||
|
||||
- name: Download release-win64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-win64
|
||||
path: release/output/
|
||||
|
||||
- name: Download release-macos-aarch64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-macos-aarch64
|
||||
path: release/output/
|
||||
|
||||
- name: Download release-macos-x86_64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-macos-x86_64
|
||||
path: release/output/
|
||||
|
||||
- name: Package server
|
||||
run: release/package_server.sh
|
||||
|
||||
- name: Generate checksums
|
||||
run: release/generate_checksums.sh
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: scrcpy-release-${{ env.VERSION }}
|
||||
path: release/output
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ build/
|
||||
.gradle/
|
||||
/x/
|
||||
local.properties
|
||||
/scrcpy-server
|
||||
|
||||
20
FAQ.md
20
FAQ.md
@@ -133,9 +133,9 @@ Try with another USB cable or plug it into another USB port. See [#281] and
|
||||
[#283]: https://github.com/Genymobile/scrcpy/issues/283
|
||||
|
||||
|
||||
## HID/OTG issues on Windows
|
||||
## OTG issues on Windows
|
||||
|
||||
On Windows, if `scrcpy --otg` (or `--hid-keyboard`/`--hid-mouse`) results in:
|
||||
On Windows, if `scrcpy --otg` (or `--keyboard=aoa`/`--mouse=aoa`) results in:
|
||||
|
||||
> ERROR: Could not find any USB device
|
||||
|
||||
@@ -159,21 +159,23 @@ In developer options, enable:
|
||||
> **USB debugging (Security settings)**
|
||||
> _Allow granting permissions and simulating input via USB debugging_
|
||||
|
||||
Rebooting the device is necessary once this option is set.
|
||||
|
||||
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||
|
||||
|
||||
### Special characters do not work
|
||||
|
||||
The default text injection method is [limited to ASCII characters][text-input].
|
||||
A trick allows to also inject some [accented characters][accented-characters],
|
||||
The default text injection method is limited to ASCII characters. A trick allows
|
||||
to also inject some [accented characters][accented-characters],
|
||||
but that's all. See [#37].
|
||||
|
||||
Since scrcpy v1.20, it is possible to simulate a [physical keyboard][hid] (HID).
|
||||
To avoid the problem, [change the keyboard mode to simulate a physical
|
||||
keyboard][hid].
|
||||
|
||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||
[hid]: README.md#physical-keyboard-simulation-hid
|
||||
[hid]: doc/keyboard.md#physical-keyboard-simulation
|
||||
|
||||
|
||||
## Client issues
|
||||
@@ -220,7 +222,7 @@ java.lang.IllegalStateException
|
||||
at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
|
||||
```
|
||||
|
||||
then try with another [encoder](doc/video.md#codec).
|
||||
then try with another [encoder](doc/video.md#encoder).
|
||||
|
||||
|
||||
## Translations
|
||||
@@ -229,4 +231,4 @@ Translations of this FAQ in other languages are available in the [wiki].
|
||||
|
||||
[wiki]: https://github.com/Genymobile/scrcpy/wiki
|
||||
|
||||
Only this README file is guaranteed to be up-to-date.
|
||||
Only this FAQ file is guaranteed to be up-to-date.
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2023 Romain Vimont
|
||||
Copyright (C) 2018-2025 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
132
README.md
132
README.md
@@ -1,13 +1,17 @@
|
||||
# scrcpy (v2.0)
|
||||
**This GitHub repo (<https://github.com/Genymobile/scrcpy>) is the only official
|
||||
source for the project. Do not download releases from random websites, even if
|
||||
their name contains `scrcpy`.**
|
||||
|
||||
# scrcpy (v3.3.4)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
_pronounced "**scr**een **c**o**py**"_
|
||||
|
||||
This application mirrors Android devices (video and audio) connected via
|
||||
USB or [over TCP/IP](doc/device.md#tcpip-wireless), and allows to control the
|
||||
device with the keyboard and the mouse of the computer. It does not require any
|
||||
_root_ access. It works on _Linux_, _Windows_ and _macOS_.
|
||||
This application mirrors Android devices (video and audio) connected via USB or
|
||||
[TCP/IP](doc/connection.md#tcpip-wireless) and allows control using the
|
||||
computer's keyboard and mouse. It does not require _root_ access or an app
|
||||
installed on the device. It works on _Linux_, _Windows_, and _macOS_.
|
||||
|
||||

|
||||
|
||||
@@ -25,53 +29,132 @@ It focuses on:
|
||||
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||
|
||||
Its features include:
|
||||
- [audio forwarding](doc/audio.md) (Android >= 11)
|
||||
- [audio forwarding](doc/audio.md) (Android 11+)
|
||||
- [recording](doc/recording.md)
|
||||
- [virtual display](doc/virtual_display.md)
|
||||
- mirroring with [Android device screen off](doc/device.md#turn-screen-off)
|
||||
- [copy-paste](doc/control.md#copy-paste) in both directions
|
||||
- [configurable quality](doc/video.md)
|
||||
- Android device [as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
|
||||
- [physical keyboard/mouse simulation (HID)](doc/hid-otg.md)
|
||||
- [OTG mode](doc/hid-otg.md#otg)
|
||||
- [camera mirroring](doc/camera.md) (Android 12+)
|
||||
- [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
|
||||
- physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID)
|
||||
- [gamepad](doc/gamepad.md) support
|
||||
- [OTG mode](doc/otg.md)
|
||||
- and more…
|
||||
|
||||
[hid-keyboard]: doc/keyboard.md#physical-keyboard-simulation
|
||||
[hid-mouse]: doc/mouse.md#physical-mouse-simulation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The Android device requires at least API 21 (Android 5.0).
|
||||
|
||||
[Audio forwarding](doc/audio.md) is supported from API 30 (Android 11).
|
||||
[Audio forwarding](doc/audio.md) is supported for API >= 30 (Android 11+).
|
||||
|
||||
Make sure you [enabled USB debugging][enable-adb] on your device(s).
|
||||
|
||||
[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable
|
||||
|
||||
On some devices, you also need to enable [an additional option][control] `USB
|
||||
debugging (Security Settings)` (this is an item different from `USB debugging`)
|
||||
to control it using a keyboard and mouse.
|
||||
On some devices (especially Xiaomi), you might get the following error:
|
||||
|
||||
```
|
||||
Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission.
|
||||
```
|
||||
|
||||
In that case, you need to enable [an additional option][control] `USB debugging
|
||||
(Security Settings)` (this is an item different from `USB debugging`) to control
|
||||
it using a keyboard and mouse. Rebooting the device is necessary once this
|
||||
option is set.
|
||||
|
||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||
|
||||
Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
|
||||
|
||||
|
||||
## Get the app
|
||||
|
||||
- [Linux](doc/linux.md)
|
||||
- [Windows](doc/windows.md)
|
||||
- [Windows](doc/windows.md) (read [how to run](doc/windows.md#run))
|
||||
- [macOS](doc/macos.md)
|
||||
|
||||
|
||||
## Must-know tips
|
||||
|
||||
- [Reducing resolution](doc/video.md#size) may greatly improve performance
|
||||
(`scrcpy -m1024`)
|
||||
- [_Right-click_](doc/mouse.md#mouse-bindings) triggers `BACK`
|
||||
- [_Middle-click_](doc/mouse.md#mouse-bindings) triggers `HOME`
|
||||
- <kbd>Alt</kbd>+<kbd>f</kbd> toggles [fullscreen](doc/window.md#fullscreen)
|
||||
- There are many other [shortcuts](doc/shortcuts.md)
|
||||
|
||||
|
||||
## Usage examples
|
||||
|
||||
There are a lot of options, [documented](#user-documentation) in separate pages.
|
||||
Here are just some common examples.
|
||||
|
||||
- Capture the screen in H.265 (better quality), limit the size to 1920, limit
|
||||
the frame rate to 60fps, disable audio, and control the device by simulating
|
||||
a physical keyboard:
|
||||
|
||||
```bash
|
||||
scrcpy --video-codec=h265 --max-size=1920 --max-fps=60 --no-audio --keyboard=uhid
|
||||
scrcpy --video-codec=h265 -m1920 --max-fps=60 --no-audio -K # short version
|
||||
```
|
||||
|
||||
- Start VLC in a new virtual display (separate from the device display):
|
||||
|
||||
```bash
|
||||
scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc
|
||||
```
|
||||
|
||||
- Record the device camera in H.265 at 1920x1080 (and microphone) to an MP4
|
||||
file:
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --video-codec=h265 --camera-size=1920x1080 --record=file.mp4
|
||||
```
|
||||
|
||||
- Capture the device front camera and expose it as a webcam on the computer (on
|
||||
Linux):
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --camera-size=1920x1080 --camera-facing=front --v4l2-sink=/dev/video2 --no-playback
|
||||
```
|
||||
|
||||
- Control the device without mirroring by simulating a physical keyboard and
|
||||
mouse (USB debugging not required):
|
||||
|
||||
```bash
|
||||
scrcpy --otg
|
||||
```
|
||||
|
||||
- Control the device using gamepads plugged into the computer:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=uhid
|
||||
scrcpy -G # short version
|
||||
```
|
||||
|
||||
## User documentation
|
||||
|
||||
The application provides a lot of features and configuration options. They are
|
||||
documented in the following pages:
|
||||
|
||||
- [Device](doc/device.md)
|
||||
- [Connection](doc/connection.md)
|
||||
- [Video](doc/video.md)
|
||||
- [Audio](doc/audio.md)
|
||||
- [Control](doc/control.md)
|
||||
- [Keyboard](doc/keyboard.md)
|
||||
- [Mouse](doc/mouse.md)
|
||||
- [Gamepad](doc/gamepad.md)
|
||||
- [Device](doc/device.md)
|
||||
- [Window](doc/window.md)
|
||||
- [Recording](doc/recording.md)
|
||||
- [Virtual display](doc/virtual_display.md)
|
||||
- [Tunnels](doc/tunnels.md)
|
||||
- [HID/OTG](doc/hid-otg.md)
|
||||
- [OTG](doc/otg.md)
|
||||
- [Camera](doc/camera.md)
|
||||
- [Video4Linux](doc/v4l2.md)
|
||||
- [Shortcuts](doc/shortcuts.md)
|
||||
|
||||
@@ -98,13 +181,17 @@ documented in the following pages:
|
||||
|
||||
## Contact
|
||||
|
||||
If you encounter a bug, please read the [FAQ](FAQ.md) first, then open an [issue].
|
||||
You can open an [issue] for bug reports, feature requests or general questions.
|
||||
|
||||
For bug reports, please read the [FAQ](FAQ.md) first, you might find a solution
|
||||
to your problem immediately.
|
||||
|
||||
[issue]: https://github.com/Genymobile/scrcpy/issues
|
||||
|
||||
For general questions or discussions, you can also use:
|
||||
You can also use:
|
||||
|
||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
||||
- BlueSky: [`@scrcpy.bsky.social`](https://bsky.app/profile/scrcpy.bsky.social)
|
||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
||||
|
||||
|
||||
@@ -113,14 +200,17 @@ For general questions or discussions, you can also use:
|
||||
I'm [@rom1v](https://github.com/rom1v), the author and maintainer of _scrcpy_.
|
||||
|
||||
If you appreciate this application, you can [support my open source
|
||||
work][donate].
|
||||
work][donate]:
|
||||
- [GitHub Sponsors](https://github.com/sponsors/rom1v)
|
||||
- [Liberapay](https://liberapay.com/rom1v/)
|
||||
- [PayPal](https://paypal.me/rom2v)
|
||||
|
||||
[donate]: https://blog.rom1v.com/about/#support-my-open-source-work
|
||||
|
||||
## Licence
|
||||
## License
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2023 Romain Vimont
|
||||
Copyright (C) 2018-2025 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -2,36 +2,55 @@ _scrcpy() {
|
||||
local cur prev words cword
|
||||
local opts="
|
||||
--always-on-top
|
||||
--angle
|
||||
--audio-bit-rate=
|
||||
--audio-buffer=
|
||||
--audio-codec=
|
||||
--audio-codec-options=
|
||||
--audio-dup
|
||||
--audio-encoder=
|
||||
--audio-source=
|
||||
--audio-output-buffer=
|
||||
-b --video-bit-rate=
|
||||
--camera-ar=
|
||||
--camera-id=
|
||||
--camera-facing=
|
||||
--camera-fps=
|
||||
--camera-high-speed
|
||||
--camera-size=
|
||||
--camera-torch
|
||||
--camera-zoom=
|
||||
--capture-orientation=
|
||||
--crop=
|
||||
-d --select-usb
|
||||
--disable-screensaver
|
||||
--display=
|
||||
--display-buffer=
|
||||
--display-id=
|
||||
--display-ime-policy=
|
||||
--display-orientation=
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
--forward-all-clicks
|
||||
-G
|
||||
--gamepad=
|
||||
-h --help
|
||||
-K
|
||||
--keyboard=
|
||||
--kill-adb-on-close
|
||||
-K --hid-keyboard
|
||||
--legacy-paste
|
||||
--list-apps
|
||||
--list-camera-sizes
|
||||
--list-cameras
|
||||
--list-displays
|
||||
--list-encoders
|
||||
--lock-video-orientation
|
||||
--lock-video-orientation=
|
||||
-m --max-size=
|
||||
-M --hid-mouse
|
||||
-M
|
||||
--max-fps=
|
||||
--mouse=
|
||||
--mouse-bind=
|
||||
-n --no-control
|
||||
-N --no-playback
|
||||
--new-display
|
||||
--new-display=
|
||||
--no-audio
|
||||
--no-audio-playback
|
||||
--no-cleanup
|
||||
@@ -39,11 +58,17 @@ _scrcpy() {
|
||||
--no-downsize-on-error
|
||||
--no-key-repeat
|
||||
--no-mipmaps
|
||||
--no-mouse-hover
|
||||
--no-power-on
|
||||
--no-vd-destroy-content
|
||||
--no-vd-system-decorations
|
||||
--no-video
|
||||
--no-video-playback
|
||||
--orientation=
|
||||
--otg
|
||||
-p --port=
|
||||
--pause-on-exit
|
||||
--pause-on-exit=
|
||||
--power-off-on-close
|
||||
--prefer-text
|
||||
--print-fps
|
||||
@@ -51,12 +76,15 @@ _scrcpy() {
|
||||
-r --record=
|
||||
--raw-key-events
|
||||
--record-format=
|
||||
--record-orientation=
|
||||
--render-driver=
|
||||
--require-audio
|
||||
--rotation=
|
||||
-s --serial=
|
||||
-S --turn-screen-off
|
||||
--screen-off-timeout=
|
||||
--shortcut-mod=
|
||||
--start-app=
|
||||
-t --show-touches
|
||||
--tcpip
|
||||
--tcpip=
|
||||
@@ -67,9 +95,11 @@ _scrcpy() {
|
||||
--v4l2-sink=
|
||||
-v --version
|
||||
-V --verbosity=
|
||||
--video-buffer=
|
||||
--video-codec=
|
||||
--video-codec-options=
|
||||
--video-encoder=
|
||||
--video-source=
|
||||
-w --stay-awake
|
||||
--window-borderless
|
||||
--window-title=
|
||||
@@ -86,15 +116,51 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--audio-codec)
|
||||
COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'opus aac flac raw' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--video-source)
|
||||
COMPREPLY=($(compgen -W 'display camera' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--audio-source)
|
||||
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'output playback mic mic-unprocessed mic-camcorder mic-voice-recognition mic-voice-communication voice-call voice-call-uplink voice-call-downlink voice-performance' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--lock-video-orientation)
|
||||
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
|
||||
--camera-facing)
|
||||
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--keyboard)
|
||||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--mouse)
|
||||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--gamepad)
|
||||
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--capture-orientation)
|
||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--orientation|--display-orientation)
|
||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--display-ime-policy)
|
||||
COMPREPLY=($(compgen -W 'local fallback hide' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--record-orientation)
|
||||
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--pause-on-exit)
|
||||
COMPREPLY=($(compgen -W 'true false if-error' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
-r|--record)
|
||||
@@ -102,17 +168,13 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--record-format)
|
||||
COMPREPLY=($(compgen -W 'mkv mp4' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'mp4 mkv m4a mka opus aac flac wav' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--render-driver)
|
||||
COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--rotation)
|
||||
COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--shortcut-mod)
|
||||
# Only auto-complete a single key
|
||||
COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur"))
|
||||
@@ -133,18 +195,26 @@ _scrcpy() {
|
||||
|--audio-codec-options \
|
||||
|--audio-encoder \
|
||||
|--audio-output-buffer \
|
||||
|--camera-ar \
|
||||
|--camera-id \
|
||||
|--camera-fps \
|
||||
|--camera-size \
|
||||
|--camera-torch \
|
||||
|--camera-zoom \
|
||||
|--crop \
|
||||
|--display \
|
||||
|--display-buffer \
|
||||
|--display-id \
|
||||
|--max-fps \
|
||||
|-m|--max-size \
|
||||
|--new-display \
|
||||
|-p|--port \
|
||||
|--push-target \
|
||||
|--rotation \
|
||||
|--screen-off-timeout \
|
||||
|--tunnel-host \
|
||||
|--tunnel-port \
|
||||
|--v4l2-buffer \
|
||||
|--v4l2-sink \
|
||||
|--video-buffer \
|
||||
|--video-codec-options \
|
||||
|--video-encoder \
|
||||
|--tcpip \
|
||||
|
||||
BIN
app/data/disconnected.png
Normal file
BIN
app/data/disconnected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -1,4 +1,2 @@
|
||||
@echo off
|
||||
scrcpy.exe %*
|
||||
:: if the exit code is >= 1, then pause
|
||||
if errorlevel 1 pause
|
||||
scrcpy.exe --pause-on-exit=if-error %*
|
||||
|
||||
@@ -5,7 +5,7 @@ Comment=Display and control your Android device
|
||||
# For some users, the PATH or ADB environment variables are set from the shell
|
||||
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
||||
# environment correctly initialized.
|
||||
Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press Enter to quit...'"
|
||||
Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'"
|
||||
Icon=scrcpy
|
||||
Terminal=true
|
||||
Type=Application
|
||||
|
||||
@@ -5,7 +5,7 @@ Comment=Display and control your Android device
|
||||
# For some users, the PATH or ADB environment variables are set from the shell
|
||||
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
||||
# environment correctly initialized.
|
||||
Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy"
|
||||
Exec=/bin/sh -c "\\$SHELL -i -c scrcpy"
|
||||
Icon=scrcpy
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -1,4 +1,4 @@
|
||||
#compdef -N scrcpy -N scrcpy.exe
|
||||
#compdef scrcpy scrcpy.exe
|
||||
#
|
||||
# name: scrcpy
|
||||
# auth: hltdev [hltdev8642@gmail.com]
|
||||
@@ -9,35 +9,54 @@ local arguments
|
||||
|
||||
arguments=(
|
||||
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
||||
'--angle=[Rotate the video content by a custom angle, in degrees]'
|
||||
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
|
||||
'--audio-buffer=[Configure the audio buffering delay \(in milliseconds\)]'
|
||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||
'--audio-dup=[Duplicate audio]'
|
||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||
'--audio-source=[Select the audio source]:source:(output mic)'
|
||||
'--audio-source=[Select the audio source]:source:(output playback mic mic-unprocessed mic-camcorder mic-voice-recognition mic-voice-communication voice-call voice-call-uplink voice-call-downlink voice-performance)'
|
||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
||||
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||
'--camera-ar=[Select the camera size by its aspect ratio]'
|
||||
'--camera-high-speed=[Enable high-speed camera capture mode]'
|
||||
'--camera-id=[Specify the camera id to mirror]'
|
||||
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
||||
'--camera-fps=[Specify the camera capture frame rate]'
|
||||
'--camera-size=[Specify an explicit camera capture size]'
|
||||
'--camera-torch[Turn on the camera torch when the camera starts]'
|
||||
'--camera-zoom[Specify the camera zoom initial value]'
|
||||
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270)'
|
||||
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
||||
{-d,--select-usb}'[Use USB device]'
|
||||
'--disable-screensaver[Disable screensaver while scrcpy is running]'
|
||||
'--display=[Specify the display id to mirror]'
|
||||
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
|
||||
'--display-id=[Specify the display id to mirror]'
|
||||
'--display-ime-policy[Set the policy for selecting where the IME should be displayed]'
|
||||
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||
{-f,--fullscreen}'[Start in fullscreen]'
|
||||
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
||||
'--forward-all-clicks[Forward clicks to device]'
|
||||
'-G[Use UHID/AOA gamepad \(same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode\)]'
|
||||
'--gamepad=[Set the gamepad input mode]:mode:(disabled uhid aoa)'
|
||||
{-h,--help}'[Print the help]'
|
||||
'-K[Use UHID/AOA keyboard \(same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode\)]'
|
||||
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
|
||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||
'--list-apps[List Android apps installed on the device]'
|
||||
'--list-camera-sizes[List the valid camera capture sizes]'
|
||||
'--list-cameras[List cameras available on the device]'
|
||||
'--list-displays[List displays available on the device]'
|
||||
'--list-encoders[List video and audio encoders available on the device]'
|
||||
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
|
||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
|
||||
'-M[Use UHID/AOA mouse \(same as --mouse=uhid or --mouse=aoa, depending on OTG mode\)]'
|
||||
'--max-fps=[Limit the frame rate of screen capture]'
|
||||
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
|
||||
{-N,--no-playback}'[Disable video and audio playback]'
|
||||
'--new-display=[Create a new display]'
|
||||
'--no-audio[Disable audio forwarding]'
|
||||
'--no-audio-playback[Disable audio playback]'
|
||||
'--no-cleanup[Disable device cleanup actions on exit]'
|
||||
@@ -45,24 +64,31 @@ arguments=(
|
||||
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
||||
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||
'--no-mouse-hover[Do not forward mouse hover events]'
|
||||
'--no-power-on[Do not power on the device on start]'
|
||||
'--no-vd-destroy-content[Disable virtual display "destroy content on removal" flag]'
|
||||
'--no-vd-system-decorations[Disable virtual display system decorations flag]'
|
||||
'--no-video[Disable video forwarding]'
|
||||
'--no-video-playback[Disable video playback]'
|
||||
'--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
||||
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]'
|
||||
{-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]'
|
||||
'--pause-on-exit=[Make scrcpy pause before exiting]:mode:(true false if-error)'
|
||||
'--power-off-on-close[Turn the device screen off when closing scrcpy]'
|
||||
'--prefer-text[Inject alpha characters and space as text events instead of key events]'
|
||||
'--print-fps[Start FPS counter, to print frame logs to the console]'
|
||||
'--push-target=[Set the target directory for pushing files to the device by drag and drop]'
|
||||
{-r,--record=}'[Record screen to file]:record file:_files'
|
||||
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
|
||||
'--record-format=[Force recording format]:format:(mp4 mkv)'
|
||||
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)'
|
||||
'--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)'
|
||||
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
|
||||
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
||||
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
|
||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
||||
'--screen-off-timeout=[Set the screen off timeout in seconds]'
|
||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
||||
'--start-app=[Start an Android app]'
|
||||
{-t,--show-touches}'[Show physical touches]'
|
||||
'--tcpip[\(optional \[ip\:port\]\) Configure and connect the device over TCP/IP]'
|
||||
'--time-limit=[Set the maximum mirroring time, in seconds]'
|
||||
@@ -72,9 +98,11 @@ arguments=(
|
||||
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
|
||||
{-v,--version}'[Print the version of scrcpy]'
|
||||
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
|
||||
'--video-buffer=[Add a buffering delay \(in milliseconds\) before displaying video frames]'
|
||||
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
|
||||
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
|
||||
'--video-encoder=[Use a specific MediaCodec video encoder]'
|
||||
'--video-source=[Select the video source]:source:(display camera)'
|
||||
{-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]'
|
||||
'--window-borderless[Disable window decorations \(display borderless window\)]'
|
||||
'--window-title=[Set a custom window title]'
|
||||
|
||||
1
app/deps/.gitignore
vendored
Normal file
1
app/deps/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/work
|
||||
27
app/deps/README
Normal file
27
app/deps/README
Normal file
@@ -0,0 +1,27 @@
|
||||
This directory (app/deps/) contains:
|
||||
|
||||
*.sh : shell scripts to download and build dependencies
|
||||
|
||||
patches/ : patches to fix dependencies (used by scripts)
|
||||
|
||||
work/sources/ : downloaded tarballs and extracted folders
|
||||
ffmpeg-6.1.1.tar.xz
|
||||
ffmpeg-6.1.1/
|
||||
libusb-1.0.27.tar.gz
|
||||
libusb-1.0.27/
|
||||
...
|
||||
work/build/ : build dirs for each dependency/version/architecture
|
||||
ffmpeg-6.1.1/win32/
|
||||
ffmpeg-6.1.1/win64/
|
||||
libusb-1.0.27/win32/
|
||||
libusb-1.0.27/win64/
|
||||
...
|
||||
work/install/ : install dirs for each architexture
|
||||
win32/bin/
|
||||
win32/include/
|
||||
win32/lib/
|
||||
win32/share/
|
||||
win64/bin/
|
||||
win64/include/
|
||||
win64/lib/
|
||||
win64/share/
|
||||
76
app/deps/_init
Normal file
76
app/deps/_init
Normal file
@@ -0,0 +1,76 @@
|
||||
# This file is intended to be sourced by other scripts, not executed
|
||||
|
||||
process_args() {
|
||||
if [[ $# != 3 ]]
|
||||
then
|
||||
# <host>: linux, macos, win32 or win64
|
||||
# <build_type>: native or cross
|
||||
# <link_type>: static or shared
|
||||
echo "Syntax: $0 <host> <build_type> <link_type>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HOST="$1"
|
||||
BUILD_TYPE="$2"
|
||||
LINK_TYPE="$3"
|
||||
DIRNAME="$HOST-$BUILD_TYPE-$LINK_TYPE"
|
||||
|
||||
if [[ "$BUILD_TYPE" != native && "$BUILD_TYPE" != cross ]]
|
||||
then
|
||||
echo "Unsupported build type (expected native or cross): $BUILD_TYPE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$LINK_TYPE" != static && "$LINK_TYPE" != shared ]]
|
||||
then
|
||||
echo "Unsupported link type (expected static or shared): $LINK_TYPE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
if [[ "$HOST" = win32 ]]
|
||||
then
|
||||
HOST_TRIPLET=i686-w64-mingw32
|
||||
elif [[ "$HOST" = win64 ]]
|
||||
then
|
||||
HOST_TRIPLET=x86_64-w64-mingw32
|
||||
else
|
||||
echo "Unsupported cross-build to host: $HOST" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
|
||||
PATCHES_DIR="$PWD/patches"
|
||||
|
||||
WORK_DIR="$PWD/work"
|
||||
SOURCES_DIR="$WORK_DIR/sources"
|
||||
BUILD_DIR="$WORK_DIR/build"
|
||||
INSTALL_DIR="$WORK_DIR/install"
|
||||
|
||||
mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR"
|
||||
|
||||
checksum() {
|
||||
local file="$1"
|
||||
local sum="$2"
|
||||
echo "$file: verifying checksum..."
|
||||
echo "$sum $file" | shasum -a256 -c
|
||||
}
|
||||
|
||||
get_file() {
|
||||
local url="$1"
|
||||
local file="$2"
|
||||
local sum="$3"
|
||||
if [[ -f "$file" ]]
|
||||
then
|
||||
echo "$file: found"
|
||||
else
|
||||
echo "$file: not found, downloading..."
|
||||
wget "$url" -O "$file"
|
||||
fi
|
||||
checksum "$file" "$sum"
|
||||
}
|
||||
29
app/deps/adb_linux.sh
Executable file
29
app/deps/adb_linux.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
|
||||
|
||||
VERSION=36.0.0
|
||||
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-linux.zip"
|
||||
SHA256SUM=0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8
|
||||
|
||||
PROJECT_DIR="platform-tools-$VERSION-linux"
|
||||
FILENAME="$PROJECT_DIR.zip"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
unzip "../$FILENAME" "$ZIP_PREFIX"/adb
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR/adb-linux"
|
||||
cd "$INSTALL_DIR/adb-linux"
|
||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-linux/"
|
||||
29
app/deps/adb_macos.sh
Executable file
29
app/deps/adb_macos.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
|
||||
|
||||
VERSION=36.0.0
|
||||
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-darwin.zip"
|
||||
SHA256SUM=d3e9fa1df3345cf728586908426615a60863d2632f73f1ce14f0f1349ef000fd
|
||||
|
||||
PROJECT_DIR="platform-tools-$VERSION-darwin"
|
||||
FILENAME="$PROJECT_DIR.zip"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
unzip "../$FILENAME" "$ZIP_PREFIX"/adb
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR/adb-macos"
|
||||
cd "$INSTALL_DIR/adb-macos"
|
||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-macos/"
|
||||
32
app/deps/adb_windows.sh
Executable file
32
app/deps/adb_windows.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
|
||||
|
||||
VERSION=36.0.0
|
||||
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-win.zip"
|
||||
SHA256SUM=12c2841f354e92a0eb2fd7bf6f0f9bf8538abce7bd6b060ac8349d6f6a61107c
|
||||
|
||||
PROJECT_DIR="platform-tools-$VERSION-windows"
|
||||
FILENAME="$PROJECT_DIR.zip"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
unzip "../$FILENAME" \
|
||||
"$ZIP_PREFIX"/AdbWinApi.dll \
|
||||
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
|
||||
"$ZIP_PREFIX"/adb.exe
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR/adb-windows"
|
||||
cd "$INSTALL_DIR/adb-windows"
|
||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-windows/"
|
||||
68
app/deps/dav1d.sh
Executable file
68
app/deps/dav1d.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=1.5.0
|
||||
URL="https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/dav1d-$VERSION.tar.gz"
|
||||
SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b
|
||||
|
||||
PROJECT_DIR="dav1d-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
if [[ -d "$DIRNAME" ]]
|
||||
then
|
||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
||||
cd "$DIRNAME"
|
||||
else
|
||||
mkdir "$DIRNAME"
|
||||
cd "$DIRNAME"
|
||||
|
||||
conf=(
|
||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
||||
--libdir=lib
|
||||
-Denable_tests=false
|
||||
-Denable_tools=false
|
||||
# Always build dav1d statically
|
||||
--default-library=static
|
||||
)
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
case "$HOST" in
|
||||
win32)
|
||||
conf+=(
|
||||
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/i686-w64-mingw32.meson"
|
||||
)
|
||||
;;
|
||||
|
||||
win64)
|
||||
conf+=(
|
||||
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/x86_64-w64-mingw32.meson"
|
||||
)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unsupported host: $HOST" >&2
|
||||
exit 1
|
||||
esac
|
||||
fi
|
||||
|
||||
meson setup . "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
|
||||
fi
|
||||
|
||||
ninja
|
||||
ninja install
|
||||
144
app/deps/ffmpeg.sh
Executable file
144
app/deps/ffmpeg.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=7.1.1
|
||||
URL="https://ffmpeg.org/releases/ffmpeg-$VERSION.tar.xz"
|
||||
SHA256SUM=733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1
|
||||
|
||||
PROJECT_DIR="ffmpeg-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.xz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
if [[ -d "$DIRNAME" ]]
|
||||
then
|
||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
||||
cd "$DIRNAME"
|
||||
else
|
||||
mkdir "$DIRNAME"
|
||||
cd "$DIRNAME"
|
||||
|
||||
if [[ "$HOST" == win* ]]
|
||||
then
|
||||
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll
|
||||
# -static to avoid dynamic dependency to zlib
|
||||
export CFLAGS='-static-libgcc -static'
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
export LDFLAGS='-static-libgcc -static'
|
||||
elif [[ "$HOST" == "macos" ]]
|
||||
then
|
||||
export PKG_CONFIG_PATH="/opt/homebrew/opt/zlib/lib/pkgconfig"
|
||||
fi
|
||||
|
||||
export PKG_CONFIG_PATH="$INSTALL_DIR/$DIRNAME/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
|
||||
conf=(
|
||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
||||
--pkg-config-flags="--static"
|
||||
--extra-cflags="-O2 -fPIC"
|
||||
--disable-programs
|
||||
--disable-doc
|
||||
--disable-swscale
|
||||
--disable-postproc
|
||||
--disable-avfilter
|
||||
--disable-network
|
||||
--disable-everything
|
||||
--disable-vulkan
|
||||
--disable-vaapi
|
||||
--disable-vdpau
|
||||
--enable-swresample
|
||||
--enable-libdav1d
|
||||
--enable-decoder=h264
|
||||
--enable-decoder=hevc
|
||||
--enable-decoder=av1
|
||||
--enable-decoder=libdav1d
|
||||
--enable-decoder=pcm_s16le
|
||||
--enable-decoder=opus
|
||||
--enable-decoder=aac
|
||||
--enable-decoder=flac
|
||||
--enable-decoder=png
|
||||
--enable-protocol=file
|
||||
--enable-demuxer=image2
|
||||
--enable-parser=png
|
||||
--enable-zlib
|
||||
--enable-muxer=matroska
|
||||
--enable-muxer=mp4
|
||||
--enable-muxer=opus
|
||||
--enable-muxer=flac
|
||||
--enable-muxer=wav
|
||||
)
|
||||
|
||||
if [[ "$HOST" == linux ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-libv4l2
|
||||
--enable-outdev=v4l2
|
||||
--enable-encoder=rawvideo
|
||||
)
|
||||
else
|
||||
# libavdevice is only used for V4L2 on Linux
|
||||
conf+=(
|
||||
--disable-avdevice
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$LINK_TYPE" == static ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-static
|
||||
--disable-shared
|
||||
)
|
||||
else
|
||||
conf+=(
|
||||
--disable-static
|
||||
--enable-shared
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-cross-compile
|
||||
--cross-prefix="${HOST_TRIPLET}-"
|
||||
--cc="${HOST_TRIPLET}-gcc"
|
||||
)
|
||||
|
||||
case "$HOST" in
|
||||
win32)
|
||||
conf+=(
|
||||
--target-os=mingw32
|
||||
--arch=x86
|
||||
)
|
||||
;;
|
||||
|
||||
win64)
|
||||
conf+=(
|
||||
--target-os=mingw32
|
||||
--arch=x86_64
|
||||
)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unsupported host: $HOST" >&2
|
||||
exit 1
|
||||
esac
|
||||
fi
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
|
||||
fi
|
||||
|
||||
make -j
|
||||
make install
|
||||
66
app/deps/libusb.sh
Executable file
66
app/deps/libusb.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=1.0.29
|
||||
URL="https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz"
|
||||
SHA256SUM=7c2dd39c0b2589236e48c93247c986ae272e27570942b4163cb00a060fcf1b74
|
||||
|
||||
PROJECT_DIR="libusb-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
export CFLAGS='-O2'
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
|
||||
if [[ -d "$DIRNAME" ]]
|
||||
then
|
||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
||||
cd "$DIRNAME"
|
||||
else
|
||||
mkdir "$DIRNAME"
|
||||
cd "$DIRNAME"
|
||||
|
||||
conf=(
|
||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
||||
)
|
||||
|
||||
if [[ "$LINK_TYPE" == static ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-static
|
||||
--disable-shared
|
||||
)
|
||||
else
|
||||
conf+=(
|
||||
--disable-static
|
||||
--enable-shared
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
conf+=(
|
||||
--host="$HOST_TRIPLET"
|
||||
)
|
||||
fi
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
|
||||
fi
|
||||
|
||||
make -j
|
||||
make install-strip
|
||||
84
app/deps/sdl.sh
Executable file
84
app/deps/sdl.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=3.4.0
|
||||
URL="https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz"
|
||||
SHA256SUM=9614b9696abc4597ffce6b888829dc6537ae500423474c342ac4a67222c5654c
|
||||
|
||||
PROJECT_DIR="sdl-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "SDL-release-$VERSION"
|
||||
mv "SDL-release-$VERSION" "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
export CFLAGS='-O2'
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
|
||||
if [[ -d "$DIRNAME" ]]
|
||||
then
|
||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
||||
cd "$DIRNAME"
|
||||
else
|
||||
mkdir "$DIRNAME"
|
||||
cd "$DIRNAME"
|
||||
|
||||
conf=(
|
||||
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
|
||||
-DSDL_TESTS=OFF
|
||||
)
|
||||
|
||||
if [[ "$HOST" == linux ]]
|
||||
then
|
||||
conf+=(
|
||||
-DSDL_WAYLAND=ON
|
||||
-DSDL_X11=ON
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$LINK_TYPE" == static ]]
|
||||
then
|
||||
conf+=(
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
)
|
||||
else
|
||||
conf+=(
|
||||
-DBUILD_SHARED_LIBS=ON
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
if [[ "$HOST" = win32 ]]
|
||||
then
|
||||
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-i686.cmake"
|
||||
elif [[ "$HOST" = win64 ]]
|
||||
then
|
||||
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-x86_64.cmake"
|
||||
else
|
||||
echo "Unsupported cross-build to host: $HOST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
conf+=(
|
||||
-DCMAKE_TOOLCHAIN_FILE="$SOURCES_DIR/$PROJECT_DIR/build-scripts/$TOOLCHAIN_FILENAME"
|
||||
)
|
||||
fi
|
||||
|
||||
cmake "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
|
||||
fi
|
||||
|
||||
cmake --build .
|
||||
cmake --install .
|
||||
141
app/meson.build
141
app/meson.build
@@ -5,6 +5,7 @@ src = [
|
||||
'src/adb/adb_parser.c',
|
||||
'src/adb/adb_tunnel.c',
|
||||
'src/audio_player.c',
|
||||
'src/audio_regulator.c',
|
||||
'src/cli.c',
|
||||
'src/clock.c',
|
||||
'src/compat.c',
|
||||
@@ -14,14 +15,16 @@ src = [
|
||||
'src/delay_buffer.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/display.c',
|
||||
'src/disconnect.c',
|
||||
'src/events.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
'src/fps_counter.c',
|
||||
'src/frame_buffer.c',
|
||||
'src/input_manager.c',
|
||||
'src/keyboard_inject.c',
|
||||
'src/mouse_inject.c',
|
||||
'src/keyboard_sdk.c',
|
||||
'src/mouse_capture.c',
|
||||
'src/mouse_sdk.c',
|
||||
'src/opengl.c',
|
||||
'src/options.c',
|
||||
'src/packet_merger.c',
|
||||
@@ -30,12 +33,21 @@ src = [
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/texture.c',
|
||||
'src/version.c',
|
||||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
'src/hid/hid_mouse.c',
|
||||
'src/trait/frame_source.c',
|
||||
'src/trait/packet_source.c',
|
||||
'src/uhid/gamepad_uhid.c',
|
||||
'src/uhid/keyboard_uhid.c',
|
||||
'src/uhid/mouse_uhid.c',
|
||||
'src/uhid/uhid_output.c',
|
||||
'src/util/acksync.c',
|
||||
'src/util/audiobuf.c',
|
||||
'src/util/average.c',
|
||||
'src/util/bytebuf.c',
|
||||
'src/util/env.c',
|
||||
'src/util/file.c',
|
||||
'src/util/intmap.c',
|
||||
'src/util/intr.c',
|
||||
@@ -46,6 +58,7 @@ src = [
|
||||
'src/util/process.c',
|
||||
'src/util/process_intr.c',
|
||||
'src/util/rand.c',
|
||||
'src/util/sdl.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str.c',
|
||||
'src/util/term.c',
|
||||
@@ -88,87 +101,36 @@ usb_support = get_option('usb')
|
||||
if usb_support
|
||||
src += [
|
||||
'src/usb/aoa_hid.c',
|
||||
'src/usb/hid_keyboard.c',
|
||||
'src/usb/hid_mouse.c',
|
||||
'src/usb/gamepad_aoa.c',
|
||||
'src/usb/keyboard_aoa.c',
|
||||
'src/usb/mouse_aoa.c',
|
||||
'src/usb/scrcpy_otg.c',
|
||||
'src/usb/screen_otg.c',
|
||||
'src/usb/usb.c',
|
||||
]
|
||||
endif
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
|
||||
static = get_option('static')
|
||||
|
||||
if not crossbuild_windows
|
||||
dependencies = [
|
||||
dependency('libavformat', version: '>= 57.33', static: static),
|
||||
dependency('libavcodec', version: '>= 57.37', static: static),
|
||||
dependency('libavutil', static: static),
|
||||
dependency('libswresample', static: static),
|
||||
dependency('sdl3', version: '>= 3.2.0', static: static),
|
||||
]
|
||||
|
||||
# native build
|
||||
dependencies = [
|
||||
dependency('libavformat', version: '>= 57.33'),
|
||||
dependency('libavcodec', version: '>= 57.37'),
|
||||
dependency('libavutil'),
|
||||
dependency('libswresample'),
|
||||
dependency('sdl2', version: '>= 2.0.5'),
|
||||
]
|
||||
|
||||
if v4l2_support
|
||||
dependencies += dependency('libavdevice')
|
||||
endif
|
||||
|
||||
if usb_support
|
||||
dependencies += dependency('libusb-1.0')
|
||||
endif
|
||||
|
||||
else
|
||||
# cross-compile mingw32 build (from Linux to Windows)
|
||||
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
|
||||
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
|
||||
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
|
||||
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
|
||||
|
||||
sdl2 = declare_dependency(
|
||||
dependencies: [
|
||||
cc.find_library('SDL2', dirs: sdl2_bin_dir),
|
||||
cc.find_library('SDL2main', dirs: sdl2_lib_dir),
|
||||
],
|
||||
include_directories: include_directories(sdl2_include_dir)
|
||||
)
|
||||
|
||||
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
|
||||
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
|
||||
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
|
||||
|
||||
ffmpeg = declare_dependency(
|
||||
dependencies: [
|
||||
cc.find_library('avcodec-60', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avformat-60', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avutil-58', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('swresample-4', dirs: ffmpeg_bin_dir),
|
||||
],
|
||||
include_directories: include_directories(ffmpeg_include_dir)
|
||||
)
|
||||
|
||||
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
|
||||
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/bin'
|
||||
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb + '/include'
|
||||
|
||||
libusb = declare_dependency(
|
||||
dependencies: [
|
||||
cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
|
||||
],
|
||||
include_directories: include_directories(libusb_include_dir)
|
||||
)
|
||||
|
||||
dependencies = [
|
||||
ffmpeg,
|
||||
sdl2,
|
||||
libusb,
|
||||
cc.find_library('mingw32')
|
||||
]
|
||||
if v4l2_support
|
||||
dependencies += dependency('libavdevice', static: static)
|
||||
endif
|
||||
|
||||
if usb_support
|
||||
dependencies += dependency('libusb-1.0', static: static)
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
dependencies += cc.find_library('mingw32')
|
||||
dependencies += cc.find_library('ws2_32')
|
||||
endif
|
||||
|
||||
@@ -209,9 +171,6 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
||||
# run a server debugger and wait for a client to be attached
|
||||
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||
|
||||
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
||||
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
|
||||
|
||||
# enable V4L2 support (linux only)
|
||||
conf.set('HAVE_V4L2', v4l2_support)
|
||||
|
||||
@@ -232,21 +191,20 @@ executable('scrcpy', src,
|
||||
datadir = get_option('datadir') # by default 'share'
|
||||
|
||||
install_man('scrcpy.1')
|
||||
install_data('data/icon.png',
|
||||
rename: 'scrcpy.png',
|
||||
install_dir: join_paths(datadir, 'icons/hicolor/256x256/apps'))
|
||||
install_data('data/scrcpy.png',
|
||||
install_dir: datadir / 'icons/hicolor/256x256/apps')
|
||||
install_data('data/zsh-completion/_scrcpy',
|
||||
install_dir: join_paths(datadir, 'zsh/site-functions'))
|
||||
install_dir: datadir / 'zsh/site-functions')
|
||||
install_data('data/bash-completion/scrcpy',
|
||||
install_dir: join_paths(datadir, 'bash-completion/completions'))
|
||||
install_dir: datadir / 'bash-completion/completions')
|
||||
|
||||
# Desktop entry file for application launchers
|
||||
if host_machine.system() == 'linux'
|
||||
# Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop)
|
||||
install_data('data/scrcpy.desktop',
|
||||
install_dir: join_paths(datadir, 'applications'))
|
||||
install_dir: datadir / 'applications')
|
||||
install_data('data/scrcpy-console.desktop',
|
||||
install_dir: join_paths(datadir, 'applications'))
|
||||
install_dir: datadir / 'applications')
|
||||
endif
|
||||
|
||||
|
||||
@@ -265,9 +223,10 @@ if get_option('buildtype') == 'debug'
|
||||
['test_binary', [
|
||||
'tests/test_binary.c',
|
||||
]],
|
||||
['test_bytebuf', [
|
||||
'tests/test_bytebuf.c',
|
||||
'src/util/bytebuf.c',
|
||||
['test_audiobuf', [
|
||||
'tests/test_audiobuf.c',
|
||||
'src/util/audiobuf.c',
|
||||
'src/util/memory.c',
|
||||
]],
|
||||
['test_cli', [
|
||||
'tests/test_cli.c',
|
||||
@@ -289,6 +248,10 @@ if get_option('buildtype') == 'debug'
|
||||
'tests/test_device_msg_deserialize.c',
|
||||
'src/device_msg.c',
|
||||
]],
|
||||
['test_orientation', [
|
||||
'tests/test_orientation.c',
|
||||
'src/options.c',
|
||||
]],
|
||||
['test_strbuf', [
|
||||
'tests/test_strbuf.c',
|
||||
'src/util/strbuf.c',
|
||||
@@ -312,7 +275,13 @@ if get_option('buildtype') == 'debug'
|
||||
exe = executable(t[0], sources,
|
||||
include_directories: src_dir,
|
||||
dependencies: dependencies,
|
||||
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
|
||||
c_args: ['-DSC_TEST'])
|
||||
test(t[0], exe)
|
||||
endforeach
|
||||
endif
|
||||
|
||||
if meson.version().version_compare('>= 0.58.0')
|
||||
devenv = environment()
|
||||
devenv.set('SCRCPY_ICON_DIR', meson.current_source_dir() / 'data')
|
||||
meson.add_devenv(devenv)
|
||||
endif
|
||||
|
||||
1
app/prebuilt-deps/.gitignore
vendored
1
app/prebuilt-deps/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/data
|
||||
@@ -1,22 +0,0 @@
|
||||
PREBUILT_DATA_DIR=data
|
||||
|
||||
checksum() {
|
||||
local file="$1"
|
||||
local sum="$2"
|
||||
echo "$file: verifying checksum..."
|
||||
echo "$sum $file" | sha256sum -c
|
||||
}
|
||||
|
||||
get_file() {
|
||||
local url="$1"
|
||||
local file="$2"
|
||||
local sum="$3"
|
||||
if [[ -f "$file" ]]
|
||||
then
|
||||
echo "$file: found"
|
||||
else
|
||||
echo "$file: not found, downloading..."
|
||||
wget "$url" -O "$file"
|
||||
fi
|
||||
checksum "$file" "$sum"
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
DEP_DIR=platform-tools-34.0.1
|
||||
|
||||
FILENAME=platform-tools_r34.0.1-windows.zip
|
||||
SHA256SUM=5dd9c2be744c224fa3a7cbe30ba02d2cb378c763bd0f797a7e47e9f3156a5daa
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://dl.google.com/android/repository/$FILENAME" \
|
||||
"$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
ZIP_PREFIX=platform-tools
|
||||
unzip "../$FILENAME" \
|
||||
"$ZIP_PREFIX"/AdbWinApi.dll \
|
||||
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
|
||||
"$ZIP_PREFIX"/adb.exe
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
VERSION=6.0-scrcpy-4
|
||||
DEP_DIR="ffmpeg-$VERSION"
|
||||
|
||||
FILENAME="$DEP_DIR".7z
|
||||
SHA256SUM=39274b321491ce83e76cab5d24e7cbe3f402d3ccf382f739b13be5651c146b60
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \
|
||||
"$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
ZIP_PREFIX=ffmpeg
|
||||
7z x "../$FILENAME"
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
DEP_DIR=libusb-1.0.26
|
||||
|
||||
FILENAME=libusb-1.0.26-binaries.7z
|
||||
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
7z x "../$FILENAME" \
|
||||
libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
|
||||
libusb-1.0.26-binaries/libusb-MinGW-Win32/include/ \
|
||||
libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
|
||||
libusb-1.0.26-binaries/libusb-MinGW-x64/include/
|
||||
|
||||
mv libusb-1.0.26-binaries/libusb-MinGW-Win32 .
|
||||
mv libusb-1.0.26-binaries/libusb-MinGW-x64 .
|
||||
rm -rf libusb-1.0.26-binaries
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
DEP_DIR=SDL2-2.26.4
|
||||
|
||||
FILENAME=SDL2-devel-2.26.4-mingw.tar.gz
|
||||
SHA256SUM=fe899c8642caac2f180b1ee6f786857ddcaa0adc1fa82474312b09dd47d74712
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
|
||||
tar xf "../$FILENAME" --strip-components=1 \
|
||||
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
|
||||
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
|
||||
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
|
||||
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
|
||||
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
|
||||
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \
|
||||
@@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "2.0"
|
||||
VALUE "ProductVersion", "3.3.4"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
455
app/scrcpy.1
455
app/scrcpy.1
@@ -19,23 +19,27 @@ provides display and control of Android devices connected on USB (or over TCP/IP
|
||||
.B \-\-always\-on\-top
|
||||
Make scrcpy window always on top (above other windows).
|
||||
|
||||
.TP
|
||||
.BI "\-\-angle " degrees
|
||||
Rotate the video content by a custom angle, in degrees (clockwise).
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-bit\-rate " value
|
||||
Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||
|
||||
Default is 128K (128000).
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-buffer ms
|
||||
.BI "\-\-audio\-buffer " ms
|
||||
Configure the audio buffering delay (in milliseconds).
|
||||
|
||||
Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches).
|
||||
Lower values decrease the latency, but increase the likelihood of buffer underrun (causing audio glitches).
|
||||
|
||||
Default is 50.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-codec " name
|
||||
Select an audio codec (opus, aac or raw).
|
||||
Select an audio codec (opus, aac, flac or raw).
|
||||
|
||||
Default is opus.
|
||||
|
||||
@@ -45,24 +49,42 @@ Set a list of comma-separated key:type=value options for the device audio encode
|
||||
|
||||
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
|
||||
|
||||
The list of possible codec options is available in the Android documentation
|
||||
.UR https://d.android.com/reference/android/media/MediaFormat
|
||||
.UE .
|
||||
The list of possible codec options is available in the Android documentation:
|
||||
|
||||
<https://d.android.com/reference/android/media/MediaFormat>
|
||||
|
||||
.TP
|
||||
.B \-\-audio\-dup
|
||||
Duplicate audio (capture and keep playing on the device).
|
||||
|
||||
This feature is only available with --audio-source=playback.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-encoder " name
|
||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
||||
|
||||
The available encoders can be listed by \-\-list\-encoders.
|
||||
The available encoders can be listed by \fB\-\-list\-encoders\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-source " source
|
||||
Select the audio source (output or mic).
|
||||
Select the audio source. Possible values are:
|
||||
|
||||
- "output": forwards the whole audio output, and disables playback on the device.
|
||||
- "playback": captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
- "mic": captures the microphone.
|
||||
- "mic-unprocessed": captures the microphone unprocessed (raw) sound.
|
||||
- "mic-camcorder": captures the microphone tuned for video recording, with the same orientation as the camera if available.
|
||||
- "mic-voice-recognition": captures the microphone tuned for voice recognition.
|
||||
- "mic-voice-communication": captures the microphone tuned for voice communications (it will for instance take advantage of echo cancellation or automatic gain control if available).
|
||||
- "voice-call": captures voice call.
|
||||
- "voice-call-uplink": captures voice call uplink only.
|
||||
- "voice-call-downlink": captures voice call downlink only.
|
||||
- "voice-performance": captures audio meant to be processed for live performance (karaoke), includes both the microphone and the device playback.
|
||||
|
||||
Default is output.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-output\-buffer ms
|
||||
.BI "\-\-audio\-output\-buffer " ms
|
||||
Configure the size of the SDL audio output buffer (in milliseconds).
|
||||
|
||||
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
|
||||
@@ -71,17 +93,69 @@ Default is 5.
|
||||
|
||||
.TP
|
||||
.BI "\-b, \-\-video\-bit\-rate " value
|
||||
Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||
Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||
|
||||
Default is 8M (8000000).
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera\-ar " ar
|
||||
Select the camera size by its aspect ratio (+/- 10%).
|
||||
|
||||
Possible values are "sensor" (use the camera sensor aspect ratio), "\fInum\fR:\fIden\fR" (e.g. "4:3") and "\fIvalue\fR" (e.g. "1.6").
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera\-facing " facing
|
||||
Select the device camera by its facing direction.
|
||||
|
||||
Possible values are "front", "back" and "external".
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera\-fps " fps
|
||||
Specify the camera capture frame rate.
|
||||
|
||||
If not specified, Android's default frame rate (30 fps) is used.
|
||||
|
||||
.TP
|
||||
.B \-\-camera\-high\-speed
|
||||
Enable high-speed camera capture mode.
|
||||
|
||||
This mode is restricted to specific resolutions and frame rates, listed by \fB\-\-list\-camera\-sizes\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera\-id " id
|
||||
Specify the device camera id to mirror.
|
||||
|
||||
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera\-size " width\fRx\fIheight
|
||||
Specify an explicit camera capture size.
|
||||
|
||||
.TP
|
||||
.BI \-\-camera\-torch
|
||||
Turn on the camera torch when the camera starts.
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera-zoom " zoom
|
||||
Specify the camera zoom initial value.
|
||||
|
||||
.TP
|
||||
.BI "\-\-capture\-orientation " value
|
||||
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270, possibly prefixed by '@'.
|
||||
|
||||
The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
|
||||
|
||||
If a leading '@' is passed (@90) for display capture, then the rotation is locked, and is relative to the natural device orientation.
|
||||
|
||||
If '@' is passed alone, then the rotation is locked to the initial device orientation.
|
||||
|
||||
Default is 0.
|
||||
|
||||
.TP
|
||||
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||
Crop the device screen on the server.
|
||||
|
||||
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any
|
||||
.B \-\-max\-size
|
||||
value is computed on the cropped size.
|
||||
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet).
|
||||
|
||||
.TP
|
||||
.B \-d, \-\-select\-usb
|
||||
@@ -90,22 +164,37 @@ Use USB device (if there is exactly one, like adb -d).
|
||||
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
|
||||
|
||||
.TP
|
||||
.BI "\-\-disable-screensaver"
|
||||
.BI "\-\-disable\-screensaver"
|
||||
Disable screensaver while scrcpy is running.
|
||||
|
||||
.TP
|
||||
.BI "\-\-display " id
|
||||
.BI "\-\-display\-id " id
|
||||
Specify the device display id to mirror.
|
||||
|
||||
The available display ids can be listed by \-\-list\-displays.
|
||||
The available display ids can be listed by \fB\-\-list\-displays\fR.
|
||||
|
||||
Default is 0.
|
||||
|
||||
.TP
|
||||
.BI "\-\-display\-buffer ms
|
||||
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
|
||||
.BI "\-\-display\-ime\-policy " value
|
||||
Set the policy for selecting where the IME should be displayed.
|
||||
|
||||
Default is 0 (no buffering).
|
||||
Possible values are "local", "fallback" and "hide":
|
||||
|
||||
- "local" means that the IME should appear on the local display.
|
||||
- "fallback" means that the IME should appear on a fallback display (the default display).
|
||||
- "hide" means that the IME should be hidden.
|
||||
|
||||
By default, the IME policy is left unchanged.
|
||||
|
||||
|
||||
.TP
|
||||
.BI "\-\-display\-orientation " value
|
||||
Set the initial display orientation.
|
||||
|
||||
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270. The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
|
||||
|
||||
Default is 0.
|
||||
|
||||
.TP
|
||||
.B \-e, \-\-select\-tcpip
|
||||
@@ -122,39 +211,69 @@ Start in fullscreen.
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-\-forward\-all\-clicks
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
||||
.B \-G
|
||||
Same as \fB\-\-gamepad=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-gamepad " mode
|
||||
Select how to send gamepad inputs to the device.
|
||||
|
||||
Possible values are "disabled", "uhid" and "aoa":
|
||||
|
||||
- "disabled" does not send gamepad inputs to the device.
|
||||
- "uhid" simulates physical HID gamepads using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates physical HID gamepads using the AOAv2 protocol. It may only work over USB.
|
||||
|
||||
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print this help.
|
||||
|
||||
.TP
|
||||
.B \-K
|
||||
Same as \fB\-\-keyboard=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-keyboard " mode
|
||||
Select how to send keyboard inputs to the device.
|
||||
|
||||
Possible values are "disabled", "sdk", "uhid" and "aoa":
|
||||
|
||||
- "disabled" does not send keyboard inputs to the device.
|
||||
- "sdk" uses the Android system API to deliver keyboard events to applications.
|
||||
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB.
|
||||
|
||||
For "uhid" and "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly using the shortcut MOD+k (except in OTG mode), or by executing:
|
||||
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
|
||||
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||
|
||||
Also see \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.B \-\-kill\-adb\-on\-close
|
||||
Kill adb when scrcpy terminates.
|
||||
|
||||
.TP
|
||||
.B \-K, \-\-hid\-keyboard
|
||||
Simulate a physical keyboard by using HID over AOAv2.
|
||||
|
||||
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
|
||||
|
||||
It may only work over USB.
|
||||
|
||||
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
|
||||
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
|
||||
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||
|
||||
Also see \fB\-\-hid\-mouse\fR.
|
||||
|
||||
.TP
|
||||
.B \-\-legacy\-paste
|
||||
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
||||
|
||||
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||
|
||||
.TP
|
||||
.B \-\-list\-apps
|
||||
List Android apps installed on the device.
|
||||
|
||||
.TP
|
||||
.B \-\-list\-camera\-sizes
|
||||
List the valid camera capture sizes.
|
||||
|
||||
.TP
|
||||
.B \-\-list\-cameras
|
||||
List cameras available on the device.
|
||||
|
||||
.TP
|
||||
.B \-\-list\-encoders
|
||||
List video and audio encoders available on the device.
|
||||
@@ -163,14 +282,6 @@ List video and audio encoders available on the device.
|
||||
.B \-\-list\-displays
|
||||
List displays available on the device.
|
||||
|
||||
.TP
|
||||
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
|
||||
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
|
||||
|
||||
Default is "unlocked".
|
||||
|
||||
Passing the option without argument is equivalent to passing "initial".
|
||||
|
||||
.TP
|
||||
.BI "\-m, \-\-max\-size " value
|
||||
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
||||
@@ -178,28 +289,70 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
|
||||
Default is 0 (unlimited).
|
||||
|
||||
.TP
|
||||
.B \-M, \-\-hid\-mouse
|
||||
Simulate a physical mouse by using HID over AOAv2.
|
||||
|
||||
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
|
||||
|
||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
||||
|
||||
It may only work over USB.
|
||||
|
||||
Also see \fB\-\-hid\-keyboard\fR.
|
||||
.B \-M
|
||||
Same as \fB\-\-mouse=uhid\fR, or \fB\-\-mouse=aoa\fR if \fB\-\-otg\fR is set.
|
||||
|
||||
.TP
|
||||
.BI "\-\-max\-fps " value
|
||||
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
|
||||
|
||||
.TP
|
||||
.BI "\-\-mouse " mode
|
||||
Select how to send mouse inputs to the device.
|
||||
|
||||
Possible values are "disabled", "sdk", "uhid" and "aoa":
|
||||
|
||||
- "disabled" does not send mouse inputs to the device.
|
||||
- "sdk" uses the Android system API to deliver mouse events to applications.
|
||||
- "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB.
|
||||
|
||||
In "uhid" and "aoa" modes, the computer mouse is captured to control the device directly (relative mouse mode).
|
||||
|
||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
||||
|
||||
Also see \fB\-\-keyboard\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-mouse\-bind " xxxx[:xxxx]
|
||||
Configure bindings of secondary clicks.
|
||||
|
||||
The argument must be one or two sequences (separated by ':') of exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click).
|
||||
|
||||
The first sequence defines the primary bindings, used when a mouse button is pressed alone. The second sequence defines the secondary bindings, used when a mouse button is pressed while the Shift key is held.
|
||||
|
||||
If the second sequence of bindings is omitted, then it is the same as the first one.
|
||||
|
||||
Each character must be one of the following:
|
||||
|
||||
- '+': forward the click to the device
|
||||
- '-': ignore the click
|
||||
- 'b': trigger shortcut BACK (or turn screen on if off)
|
||||
- 'h': trigger shortcut HOME
|
||||
- 's': trigger shortcut APP_SWITCH
|
||||
- 'n': trigger shortcut "expand notification panel"
|
||||
|
||||
Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA and UHID.
|
||||
|
||||
|
||||
.TP
|
||||
.B \-n, \-\-no\-control
|
||||
Disable device control (mirror the device in read\-only).
|
||||
|
||||
.TP
|
||||
.B \-N, \-\-no\-playback
|
||||
Disable video and audio playback on the computer (equivalent to --no-video-playback --no-audio-playback).
|
||||
Disable video and audio playback on the computer (equivalent to \fB\-\-no\-video\-playback \-\-no\-audio\-playback\fR).
|
||||
|
||||
.TP
|
||||
\fB\-\-new\-display\fR[=[\fIwidth\fRx\fIheight\fR][/\fIdpi\fR]]
|
||||
Create a new display with the specified resolution and density. If not provided, they default to the main display dimensions and DPI.
|
||||
|
||||
Examples:
|
||||
|
||||
\-\-new\-display=1920x1080
|
||||
\-\-new\-display=1920x1080/420
|
||||
\-\-new\-display # main display size and density
|
||||
\-\-new\-display=/240 # main display size and 240 dpi
|
||||
|
||||
.TP
|
||||
.B \-\-no\-audio
|
||||
@@ -235,10 +388,24 @@ Do not forward repeated key events when a key is held down.
|
||||
.B \-\-no\-mipmaps
|
||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-mouse\-hover
|
||||
Do not forward mouse hover (mouse motion without any clicks) events.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-power\-on
|
||||
Do not power on the device on start.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-vd\-destroy\-content
|
||||
Disable virtual display "destroy content on removal" flag.
|
||||
|
||||
With this option, when the virtual display is closed, the running apps are moved to the main display rather than being destroyed.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-vd\-system\-decorations
|
||||
Disable virtual display system decorations flag.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-video
|
||||
Disable video forwarding.
|
||||
@@ -247,6 +414,14 @@ Disable video forwarding.
|
||||
.B \-\-no\-video\-playback
|
||||
Disable video playback on the computer.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-window
|
||||
Disable scrcpy window. Implies --no-video-playback.
|
||||
|
||||
.TP
|
||||
.BI "\-\-orientation " value
|
||||
Same as --display-orientation=value --record-orientation=value.
|
||||
|
||||
.TP
|
||||
.B \-\-otg
|
||||
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
|
||||
@@ -259,7 +434,7 @@ If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable ke
|
||||
|
||||
It may only work over USB.
|
||||
|
||||
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
|
||||
See \fB\-\-keyboard\fR, \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
||||
@@ -267,6 +442,16 @@ Set the TCP port (range) used by the client to listen.
|
||||
|
||||
Default is 27183:27199.
|
||||
|
||||
.TP
|
||||
\fB\-\-pause\-on\-exit\fR[=\fImode\fR]
|
||||
Configure pause on exit. Possible values are "true" (always pause on exit), "false" (never pause on exit) and "if-error" (pause only if an error occurred).
|
||||
|
||||
This is useful to prevent the terminal window from automatically closing, so that error messages can be read.
|
||||
|
||||
Default is "false".
|
||||
|
||||
Passing the option without argument is equivalent to passing "true".
|
||||
|
||||
.TP
|
||||
.B \-\-power\-off\-on\-close
|
||||
Turn the device screen off when closing scrcpy.
|
||||
@@ -295,7 +480,7 @@ Record screen to
|
||||
|
||||
The format is determined by the
|
||||
.B \-\-record\-format
|
||||
option if set, or by the file extension (.mp4 or .mkv).
|
||||
option if set, or by the file extension.
|
||||
|
||||
.TP
|
||||
.B \-\-raw\-key\-events
|
||||
@@ -303,7 +488,15 @@ Inject key events for all input keys, and ignore text events.
|
||||
|
||||
.TP
|
||||
.BI "\-\-record\-format " format
|
||||
Force recording format (either mp4 or mkv).
|
||||
Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav).
|
||||
|
||||
.TP
|
||||
.BI "\-\-record\-orientation " value
|
||||
Set the record orientation.
|
||||
|
||||
Possible values are 0, 90, 180 and 270. The number represents the clockwise rotation in degrees.
|
||||
|
||||
Default is 0.
|
||||
|
||||
.TP
|
||||
.BI "\-\-render\-driver " name
|
||||
@@ -311,17 +504,12 @@ Request SDL to use the given render driver (this is just a hint).
|
||||
|
||||
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
|
||||
|
||||
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
||||
.UE
|
||||
<https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>
|
||||
|
||||
.TP
|
||||
.B \-\-require\-audio
|
||||
By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work.
|
||||
|
||||
.TP
|
||||
.BI "\-\-rotation " value
|
||||
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
|
||||
|
||||
.TP
|
||||
.BI "\-s, \-\-serial " number
|
||||
The device serial number. Mandatory only if several devices are connected to adb.
|
||||
@@ -330,16 +518,36 @@ The device serial number. Mandatory only if several devices are connected to adb
|
||||
.B \-S, \-\-turn\-screen\-off
|
||||
Turn the device screen off immediately.
|
||||
|
||||
.TP
|
||||
.B "\-\-screen\-off\-timeout " seconds
|
||||
Set the screen off timeout while scrcpy is running (restore the initial value on exit).
|
||||
|
||||
.TP
|
||||
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
|
||||
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||
|
||||
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
||||
Several shortcut modifiers can be specified, separated by ','.
|
||||
|
||||
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
|
||||
For example, to use either LCtrl or LSuper for scrcpy shortcuts, pass "lctrl,lsuper".
|
||||
|
||||
Default is "lalt,lsuper" (left-Alt or left-Super).
|
||||
|
||||
.TP
|
||||
.BI "\-\-start\-app " name
|
||||
Start an Android app, by its exact package name.
|
||||
|
||||
Add a '?' prefix to select an app whose name starts with the given name, case-insensitive (retrieving app names on the device may take some time):
|
||||
|
||||
scrcpy --start-app=?firefox
|
||||
|
||||
Add a '+' prefix to force-stop before starting the app:
|
||||
|
||||
scrcpy --new-display --start-app=+org.mozilla.firefox
|
||||
|
||||
Both prefixes can be used, in that order:
|
||||
|
||||
scrcpy --start-app=+?firefox
|
||||
|
||||
.TP
|
||||
.B \-t, \-\-show\-touches
|
||||
Enable "show touches" on start, restore the initial value on exit.
|
||||
@@ -347,26 +555,28 @@ Enable "show touches" on start, restore the initial value on exit.
|
||||
It only shows physical touches (not clicks from scrcpy).
|
||||
|
||||
.TP
|
||||
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]]
|
||||
Configure and reconnect the device over TCP/IP.
|
||||
.BI "\-\-tcpip\fR[=[+]\fIip\fR[:\fIport\fR]]
|
||||
Configure and connect the device over TCP/IP.
|
||||
|
||||
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
|
||||
|
||||
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
|
||||
|
||||
Prefix the address with a '+' to force a reconnection.
|
||||
|
||||
.TP
|
||||
.BI "\-\-time\-limit " seconds
|
||||
Set the maximum mirroring time, in seconds.
|
||||
|
||||
.TP
|
||||
.BI "\-\-tunnel\-host " ip
|
||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables \fB\-\-force\-adb\-forward\fR.
|
||||
|
||||
Default is localhost.
|
||||
|
||||
.TP
|
||||
.BI "\-\-tunnel\-port " port
|
||||
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
||||
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables \fB\-\-force\-adb\-forward\fR.
|
||||
|
||||
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
|
||||
|
||||
@@ -384,13 +594,19 @@ Default is "info" for release builds, "debug" for debug builds.
|
||||
.BI "\-\-v4l2-sink " /dev/videoN
|
||||
Output to v4l2loopback device.
|
||||
|
||||
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR).
|
||||
|
||||
.TP
|
||||
.BI "\-\-v4l2-buffer " ms
|
||||
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
|
||||
|
||||
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
|
||||
This option is similar to \fB\-\-video\-buffer\fR, but specific to V4L2 sink.
|
||||
|
||||
Default is 0 (no buffering).
|
||||
|
||||
.TP
|
||||
.BI "\-\-video\-buffer " ms
|
||||
Add a buffering delay (in milliseconds) before displaying video frames.
|
||||
|
||||
This increases latency to compensate for jitter.
|
||||
|
||||
Default is 0 (no buffering).
|
||||
|
||||
@@ -406,15 +622,23 @@ Set a list of comma-separated key:type=value options for the device video encode
|
||||
|
||||
The possible values for 'type' are 'int' (default), 'long', 'float' and 'string'.
|
||||
|
||||
The list of possible codec options is available in the Android documentation
|
||||
.UR https://d.android.com/reference/android/media/MediaFormat
|
||||
.UE .
|
||||
The list of possible codec options is available in the Android documentation:
|
||||
|
||||
<https://d.android.com/reference/android/media/MediaFormat>
|
||||
|
||||
.TP
|
||||
.BI "\-\-video\-encoder " name
|
||||
Use a specific MediaCodec video encoder (depending on the codec provided by \fB\-\-video\-codec\fR).
|
||||
|
||||
The available encoders can be listed by \-\-list\-encoders.
|
||||
The available encoders can be listed by \fB\-\-list\-encoders\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-video\-source " source
|
||||
Select the video source (display or camera).
|
||||
|
||||
Camera mirroring requires Android 12+.
|
||||
|
||||
Default is display.
|
||||
|
||||
.TP
|
||||
.B \-w, \-\-stay-awake
|
||||
@@ -475,6 +699,26 @@ Rotate display left
|
||||
.B MOD+Right
|
||||
Rotate display right
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+Left, MOD+Shift+Right
|
||||
Flip display horizontally
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+Up, MOD+Shift+Down
|
||||
Flip display vertically
|
||||
|
||||
.TP
|
||||
.B MOD+z
|
||||
Pause or re-pause display
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+z
|
||||
Unpause display
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+r
|
||||
Reset video capture/encoding
|
||||
|
||||
.TP
|
||||
.B MOD+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
@@ -551,13 +795,25 @@ Copy computer clipboard to device, then paste (inject PASTE keycode, Android >=
|
||||
.B MOD+Shift+v
|
||||
Inject computer clipboard text as a sequence of key events
|
||||
|
||||
.TP
|
||||
.B MOD+k
|
||||
Open keyboard settings on the device (for HID keyboard only)
|
||||
|
||||
.TP
|
||||
.B MOD+i
|
||||
Enable/disable FPS counter (print frames/second in logs)
|
||||
|
||||
.TP
|
||||
.B Ctrl+click-and-move
|
||||
Pinch-to-zoom from the center of the screen
|
||||
Pinch-to-zoom and rotate from the center of the screen
|
||||
|
||||
.TP
|
||||
.B Shift+click-and-move
|
||||
Tilt vertically (slide with 2 fingers)
|
||||
|
||||
.TP
|
||||
.B Ctrl+Shift+click-and-move
|
||||
Tilt horizontally (slide with 2 fingers)
|
||||
|
||||
.TP
|
||||
.B Drag & drop APK file
|
||||
@@ -567,6 +823,22 @@ Install APK from computer
|
||||
.B Drag & drop non-APK file
|
||||
Push file to device (see \fB\-\-push\-target\fR)
|
||||
|
||||
.TP
|
||||
.B MOD+t
|
||||
Turn on the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+t
|
||||
Turn off the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Up
|
||||
Zoom camera in (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Down
|
||||
Zoom camera out (camera mode only)
|
||||
|
||||
|
||||
.SH Environment variables
|
||||
|
||||
@@ -576,11 +848,11 @@ Path to adb.
|
||||
|
||||
.TP
|
||||
.B ANDROID_SERIAL
|
||||
Device serial to use if no selector (-s, -d, -e or --tcpip=<addr>) is specified.
|
||||
Device serial to use if no selector (\fB-s\fR, \fB-d\fR, \fB-e\fR or \fB\-\-tcpip=\fIaddr\fR) is specified.
|
||||
|
||||
.TP
|
||||
.B SCRCPY_ICON_PATH
|
||||
Path to the program icon.
|
||||
.B SCRCPY_ICON_DIR
|
||||
Path to the icon directory.
|
||||
|
||||
.TP
|
||||
.B SCRCPY_SERVER_PATH
|
||||
@@ -599,23 +871,14 @@ for the Debian Project (and may be used by others).
|
||||
|
||||
|
||||
.SH "REPORTING BUGS"
|
||||
Report bugs to
|
||||
.UR https://github.com/Genymobile/scrcpy/issues
|
||||
.UE .
|
||||
Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
|
||||
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2018 Genymobile
|
||||
.UR https://www.genymobile.com
|
||||
Genymobile
|
||||
.UE
|
||||
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
|
||||
|
||||
Copyright \(co 2018\-2023
|
||||
.MT rom@rom1v.com
|
||||
Romain Vimont
|
||||
.ME
|
||||
Copyright \(co 2018\-2025 Romain Vimont <rom@rom1v.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
|
||||
.SH WWW
|
||||
.UR https://github.com/Genymobile/scrcpy
|
||||
.UE
|
||||
<https://github.com/Genymobile/scrcpy>
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "adb_device.h"
|
||||
#include "adb_parser.h"
|
||||
#include "adb/adb_device.h"
|
||||
#include "adb/adb_parser.h"
|
||||
#include "util/env.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/process_intr.h"
|
||||
@@ -24,15 +26,45 @@
|
||||
*/
|
||||
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
|
||||
|
||||
static const char *adb_executable;
|
||||
static char *adb_executable;
|
||||
|
||||
bool
|
||||
sc_adb_init(void) {
|
||||
adb_executable = sc_get_env("ADB");
|
||||
if (adb_executable) {
|
||||
LOGD("Using adb: %s", adb_executable);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(PORTABLE) || defined(_WIN32)
|
||||
adb_executable = strdup("adb");
|
||||
if (!adb_executable) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// For portable builds, use the absolute path to the adb executable
|
||||
// in the same directory as scrcpy (except on Windows, where "adb"
|
||||
// is sufficient)
|
||||
adb_executable = sc_file_get_local_path("adb");
|
||||
if (!adb_executable) {
|
||||
// Error already logged
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("Using adb (portable): %s", adb_executable);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_adb_destroy(void) {
|
||||
free(adb_executable);
|
||||
}
|
||||
|
||||
const char *
|
||||
sc_adb_get_executable(void) {
|
||||
if (!adb_executable) {
|
||||
adb_executable = getenv("ADB");
|
||||
if (!adb_executable)
|
||||
adb_executable = "adb";
|
||||
}
|
||||
return adb_executable;
|
||||
}
|
||||
|
||||
@@ -70,15 +102,15 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
|
||||
}
|
||||
|
||||
static void
|
||||
show_adb_installation_msg() {
|
||||
#ifndef __WINDOWS__
|
||||
show_adb_installation_msg(void) {
|
||||
#ifndef _WIN32
|
||||
static const struct {
|
||||
const char *binary;
|
||||
const char *command;
|
||||
} pkg_managers[] = {
|
||||
{"apt", "apt install adb"},
|
||||
{"apt-get", "apt-get install adb"},
|
||||
{"brew", "brew cask install android-platform-tools"},
|
||||
{"brew", "brew install --cask android-platform-tools"},
|
||||
{"dnf", "dnf install android-tools"},
|
||||
{"emerge", "emerge dev-util/android-tools"},
|
||||
{"pacman", "pacman -S android-tools"},
|
||||
@@ -218,8 +250,16 @@ sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
|
||||
const char *device_socket_name, unsigned flags) {
|
||||
char local[4 + 5 + 1]; // tcp:PORT
|
||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||
|
||||
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
|
||||
assert(r >= 0 && (size_t) r < sizeof(local));
|
||||
|
||||
r = snprintf(remote, sizeof(remote), "localabstract:%s",
|
||||
device_socket_name);
|
||||
if (r < 0 || (size_t) r >= sizeof(remote)) {
|
||||
LOGE("Could not write socket name");
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
@@ -233,7 +273,9 @@ bool
|
||||
sc_adb_forward_remove(struct sc_intr *intr, const char *serial,
|
||||
uint16_t local_port, unsigned flags) {
|
||||
char local[4 + 5 + 1]; // tcp:PORT
|
||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
|
||||
assert(r >= 0 && (size_t) r < sizeof(local));
|
||||
(void) r;
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
@@ -249,8 +291,16 @@ sc_adb_reverse(struct sc_intr *intr, const char *serial,
|
||||
unsigned flags) {
|
||||
char local[4 + 5 + 1]; // tcp:PORT
|
||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||
sprintf(local, "tcp:%" PRIu16, local_port);
|
||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
|
||||
assert(r >= 0 && (size_t) r < sizeof(local));
|
||||
|
||||
r = snprintf(remote, sizeof(remote), "localabstract:%s",
|
||||
device_socket_name);
|
||||
if (r < 0 || (size_t) r >= sizeof(remote)) {
|
||||
LOGE("Could not write socket name");
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
SC_ADB_COMMAND("-s", serial, "reverse", remote, local);
|
||||
@@ -263,7 +313,12 @@ bool
|
||||
sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
||||
const char *device_socket_name, unsigned flags) {
|
||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
|
||||
int r = snprintf(remote, sizeof(remote), "localabstract:%s",
|
||||
device_socket_name);
|
||||
if (r < 0 || (size_t) r >= sizeof(remote)) {
|
||||
LOGE("Device socket name too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
@@ -276,7 +331,7 @@ sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
||||
bool
|
||||
sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
const char *remote, unsigned flags) {
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
// Windows will parse the string, so the paths must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
@@ -296,7 +351,7 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
free((void *) remote);
|
||||
free((void *) local);
|
||||
#endif
|
||||
@@ -307,7 +362,7 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
bool
|
||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||
unsigned flags) {
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
// Windows will parse the string, so the local name must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
@@ -322,7 +377,7 @@ sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
@@ -333,7 +388,9 @@ bool
|
||||
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
||||
unsigned flags) {
|
||||
char port_string[5 + 1];
|
||||
sprintf(port_string, "%" PRIu16, port);
|
||||
int r = snprintf(port_string, sizeof(port_string), "%" PRIu16, port);
|
||||
assert(r >= 0 && (size_t) r < sizeof(port_string));
|
||||
(void) r;
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
@@ -356,7 +413,7 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
||||
|
||||
// "adb connect" always returns successfully (with exit code 0), even in
|
||||
// case of failure. As a workaround, check if its output starts with
|
||||
// "connected".
|
||||
// "connected" or "already connected".
|
||||
char buf[128];
|
||||
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
|
||||
sc_pipe_close(pout);
|
||||
@@ -373,7 +430,8 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
|
||||
assert((size_t) r < sizeof(buf));
|
||||
buf[r] = '\0';
|
||||
|
||||
ok = !strncmp("connected", buf, sizeof("connected") - 1);
|
||||
ok = !strncmp("connected", buf, sizeof("connected") - 1)
|
||||
|| !strncmp("already connected", buf, sizeof("already connected") - 1);
|
||||
if (!ok && !(flags & SC_ADB_NO_STDERR)) {
|
||||
// "adb connect" also prints errors to stdout. Since we capture it,
|
||||
// re-print the error to stderr.
|
||||
@@ -433,6 +491,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
|
||||
// in the buffer in a single pass
|
||||
LOGW("Result of \"adb devices -l\" does not fit in 64Kb. "
|
||||
"Please report an issue.");
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -628,8 +687,8 @@ sc_adb_select_device(struct sc_intr *intr,
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("ADB device found:");
|
||||
sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size);
|
||||
LOGI("ADB device found:");
|
||||
sc_adb_devices_log(SC_LOG_LEVEL_INFO, vec.data, vec.size);
|
||||
|
||||
// Move devics into out_device (do not destroy device)
|
||||
sc_adb_device_move(out_device, device);
|
||||
@@ -713,3 +772,21 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
||||
|
||||
return sc_adb_parse_device_ip(buf);
|
||||
}
|
||||
|
||||
uint16_t
|
||||
sc_adb_get_device_sdk_version(struct sc_intr *intr, const char *serial) {
|
||||
char *sdk_version =
|
||||
sc_adb_getprop(intr, serial, "ro.build.version.sdk", SC_ADB_SILENT);
|
||||
if (!sdk_version) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long value;
|
||||
bool ok = sc_str_parse_integer(sdk_version, &value);
|
||||
free(sdk_version);
|
||||
if (!ok || value < 0 || value > 0xFFFF) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "adb_device.h"
|
||||
#include "adb/adb_device.h"
|
||||
#include "util/intr.h"
|
||||
|
||||
#define SC_ADB_NO_STDOUT (1 << 0)
|
||||
@@ -15,6 +15,12 @@
|
||||
|
||||
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
||||
|
||||
bool
|
||||
sc_adb_init(void);
|
||||
|
||||
void
|
||||
sc_adb_destroy(void);
|
||||
|
||||
const char *
|
||||
sc_adb_get_executable(void);
|
||||
|
||||
@@ -114,4 +120,10 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
||||
char *
|
||||
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
||||
|
||||
/**
|
||||
* Return the device SDK version.
|
||||
*/
|
||||
uint16_t
|
||||
sc_adb_get_device_sdk_version(struct sc_intr *intr, const char *serial);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "util/vector.h"
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
bool
|
||||
static bool
|
||||
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
||||
// One device line looks like:
|
||||
// "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "adb_device.h"
|
||||
#include "adb/adb_device.h"
|
||||
|
||||
/**
|
||||
* Parse the available devices from the output of `adb devices`
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "adb_tunnel.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "adb.h"
|
||||
#include "adb/adb.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net_intr.h"
|
||||
#include "util/process_intr.h"
|
||||
|
||||
static bool
|
||||
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||
|
||||
@@ -633,7 +633,7 @@ enum android_keycode {
|
||||
* Toggles between BS and CS digital satellite services. */
|
||||
AKEYCODE_TV_SATELLITE_SERVICE = 240,
|
||||
/** Toggle Network key.
|
||||
* Toggles selecting broacast services. */
|
||||
* Toggles selecting broadcast services. */
|
||||
AKEYCODE_TV_NETWORK = 241,
|
||||
/** Antenna/Cable key.
|
||||
* Toggles broadcast input source between antenna and cable. */
|
||||
|
||||
@@ -1,139 +1,40 @@
|
||||
#include "audio_player.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
|
||||
|
||||
/**
|
||||
* Real-time audio player with configurable latency
|
||||
*
|
||||
* As input, the player regularly receives AVFrames of decoded audio samples.
|
||||
* As output, an SDL callback regularly requests audio samples to be played.
|
||||
* In the middle, an audio buffer stores the samples produced but not consumed
|
||||
* yet.
|
||||
*
|
||||
* The goal of the player is to feed the audio output with a latency as low as
|
||||
* possible while avoiding buffer underrun (i.e. not being able to provide
|
||||
* samples when requested).
|
||||
*
|
||||
* The player aims to feed the audio output with as little latency as possible
|
||||
* while avoiding buffer underrun. To achieve this, it attempts to maintain the
|
||||
* average buffering (the number of samples present in the buffer) around a
|
||||
* target value. If this target buffering is too low, then buffer underrun will
|
||||
* occur frequently. If it is too high, then latency will become unacceptable.
|
||||
* This target value is configured using the scrcpy option --audio-buffer.
|
||||
*
|
||||
* The player cannot adjust the sample input rate (it receives samples produced
|
||||
* in real-time) or the sample output rate (it must provide samples as
|
||||
* requested by the audio output callback). Therefore, it may only apply
|
||||
* compensation by resampling (converting _m_ input samples to _n_ output
|
||||
* samples).
|
||||
*
|
||||
* The compensation itself is applied by libswresample (FFmpeg). It is
|
||||
* configured using swr_set_compensation(). An important work for the player
|
||||
* is to estimate the compensation value regularly and apply it.
|
||||
*
|
||||
* The estimated buffering level is the result of averaging the "natural"
|
||||
* buffering (samples are produced and consumed by blocks, so it must be
|
||||
* smoothed), and making instant adjustments resulting of its own actions
|
||||
* (explicit compensation and silence insertion on underflow), which are not
|
||||
* smoothed.
|
||||
*
|
||||
* Buffer underflow events can occur when packets arrive too late. In that case,
|
||||
* the player inserts silence. Once the packets finally arrive (late), one
|
||||
* strategy could be to drop the samples that were replaced by silence, in
|
||||
* order to keep a minimal latency. However, dropping samples in case of buffer
|
||||
* underflow is inadvisable, as it would temporarily increase the underflow
|
||||
* even more and cause very noticeable audio glitches.
|
||||
*
|
||||
* Therefore, the player doesn't drop any sample on underflow. The compensation
|
||||
* mechanism will absorb the delay introduced by the inserted silence.
|
||||
*/
|
||||
#include "SDL3/SDL_hints.h"
|
||||
|
||||
/** Downcast frame_sink to sc_audio_player */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
|
||||
|
||||
#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT
|
||||
#define SC_SDL_SAMPLE_FMT AUDIO_F32
|
||||
|
||||
#define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ap->buf, (SAMPLES))
|
||||
#define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ap->buf, (BYTES))
|
||||
#define SC_SDL_SAMPLE_FMT SDL_AUDIO_F32LE
|
||||
|
||||
static void SDLCALL
|
||||
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
sc_audio_player_stream_callback(void *userdata, SDL_AudioStream *stream,
|
||||
int additional_amount, int total_amount) {
|
||||
(void) total_amount;
|
||||
|
||||
struct sc_audio_player *ap = userdata;
|
||||
|
||||
// This callback is called with the lock used by SDL_AudioDeviceLock(), so
|
||||
// the audiobuf is protected
|
||||
size_t len = additional_amount;
|
||||
assert(len % ap->audioreg.sample_size == 0);
|
||||
|
||||
assert(len_int > 0);
|
||||
size_t len = len_int;
|
||||
uint32_t count = TO_SAMPLES(len);
|
||||
// The requested amount may exceed the internal aout_buffer size.
|
||||
// In this (unlikely) case, send the data to the stream in multiple chunks.
|
||||
while (len) {
|
||||
size_t chunk_size = MIN(ap->aout_buffer_size, len);
|
||||
uint32_t out_samples = chunk_size / ap->audioreg.sample_size;
|
||||
sc_audio_regulator_pull(&ap->audioreg, ap->aout_buffer,
|
||||
out_samples);
|
||||
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
||||
#endif
|
||||
|
||||
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
||||
if (!ap->played) {
|
||||
// Part of the buffering is handled by inserting initial silence. The
|
||||
// remaining (margin) last samples will be handled by compensation.
|
||||
uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms
|
||||
if (buffered_samples + margin < ap->target_buffering) {
|
||||
LOGV("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||
" samples", count);
|
||||
// Delay playback starting to reach the target buffering. Fill the
|
||||
// whole buffer with silence (len is small compared to the
|
||||
// arbitrary margin value).
|
||||
memset(stream, 0, len);
|
||||
assert(chunk_size <= len);
|
||||
len -= chunk_size;
|
||||
bool ok =
|
||||
SDL_PutAudioStreamData(stream, ap->aout_buffer, chunk_size);
|
||||
if (!ok) {
|
||||
LOGW("Audio stream error: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t read = MIN(buffered_samples, count);
|
||||
if (read) {
|
||||
sc_audiobuf_read(&ap->buf, stream, read);
|
||||
}
|
||||
|
||||
if (read < count) {
|
||||
uint32_t silence = count - read;
|
||||
// Insert silence. In theory, the inserted silent samples replace the
|
||||
// missing real samples, which will arrive later, so they should be
|
||||
// dropped to keep the latency minimal. However, this would cause very
|
||||
// audible glitches, so let the clock compensation restore the target
|
||||
// latency.
|
||||
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
|
||||
silence);
|
||||
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence));
|
||||
|
||||
if (ap->received) {
|
||||
// Inserting additional samples immediately increases buffering
|
||||
ap->underflow += silence;
|
||||
}
|
||||
}
|
||||
|
||||
ap->played = true;
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
sc_audio_player_get_swr_buf(struct sc_audio_player *ap, uint32_t min_samples) {
|
||||
size_t min_buf_size = TO_BYTES(min_samples);
|
||||
if (min_buf_size > ap->swr_buf_alloc_size) {
|
||||
size_t new_size = min_buf_size + 4096;
|
||||
uint8_t *buf = realloc(ap->swr_buf, new_size);
|
||||
if (!buf) {
|
||||
LOG_OOM();
|
||||
// Could not realloc to the requested size
|
||||
return NULL;
|
||||
}
|
||||
ap->swr_buf = buf;
|
||||
ap->swr_buf_alloc_size = new_size;
|
||||
}
|
||||
|
||||
return ap->swr_buf;
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -141,194 +42,24 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
const AVFrame *frame) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
SwrContext *swr_ctx = ap->swr_ctx;
|
||||
|
||||
int64_t swr_delay = swr_get_delay(swr_ctx, ap->sample_rate);
|
||||
// No need to av_rescale_rnd(), input and output sample rates are the same.
|
||||
// Add more space (256) for clock compensation.
|
||||
int dst_nb_samples = swr_delay + frame->nb_samples + 256;
|
||||
|
||||
uint8_t *swr_buf = sc_audio_player_get_swr_buf(ap, dst_nb_samples);
|
||||
if (!swr_buf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = swr_convert(swr_ctx, &swr_buf, dst_nb_samples,
|
||||
(const uint8_t **) frame->data, frame->nb_samples);
|
||||
if (ret < 0) {
|
||||
LOGE("Resampling failed: %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// swr_convert() returns the number of samples which would have been
|
||||
// written if the buffer was big enough.
|
||||
uint32_t samples_written = MIN(ret, dst_nb_samples);
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written);
|
||||
#endif
|
||||
|
||||
// Since this function is the only writer, the current available space is
|
||||
// at least the previous available space. In practice, it should almost
|
||||
// always be possible to write without lock.
|
||||
bool lockless_write = samples_written <= ap->previous_can_write;
|
||||
if (lockless_write) {
|
||||
sc_audiobuf_prepare_write(&ap->buf, swr_buf, samples_written);
|
||||
}
|
||||
|
||||
SDL_LockAudioDevice(ap->device);
|
||||
|
||||
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
||||
|
||||
if (lockless_write) {
|
||||
sc_audiobuf_commit_write(&ap->buf, samples_written);
|
||||
} else {
|
||||
uint32_t can_write = sc_audiobuf_can_write(&ap->buf);
|
||||
if (samples_written > can_write) {
|
||||
// Entering this branch is very unlikely, the audio buffer is
|
||||
// allocated with a size sufficient to store 1 second more than the
|
||||
// target buffering. If this happens, though, we have to skip old
|
||||
// samples.
|
||||
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
|
||||
if (samples_written > cap) {
|
||||
// Very very unlikely: a single resampled frame should never
|
||||
// exceed the audio buffer size (or something is very wrong).
|
||||
// Ignore the first bytes in swr_buf
|
||||
swr_buf += TO_BYTES(samples_written - cap);
|
||||
// This change in samples_written will impact the
|
||||
// instant_compensation below
|
||||
samples_written = cap;
|
||||
}
|
||||
|
||||
assert(samples_written >= can_write);
|
||||
if (samples_written > can_write) {
|
||||
uint32_t skip_samples = samples_written - can_write;
|
||||
assert(buffered_samples >= skip_samples);
|
||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
||||
buffered_samples -= skip_samples;
|
||||
if (ap->played) {
|
||||
// Dropping input samples instantly decreases buffering
|
||||
ap->avg_buffering.avg -= skip_samples;
|
||||
}
|
||||
}
|
||||
|
||||
// It should remain exactly the expected size to write the new
|
||||
// samples.
|
||||
assert(sc_audiobuf_can_write(&ap->buf) == samples_written);
|
||||
}
|
||||
|
||||
sc_audiobuf_write(&ap->buf, swr_buf, samples_written);
|
||||
}
|
||||
|
||||
buffered_samples += samples_written;
|
||||
assert(buffered_samples == sc_audiobuf_can_read(&ap->buf));
|
||||
|
||||
// Read with lock held, to be used after unlocking
|
||||
bool played = ap->played;
|
||||
uint32_t underflow = ap->underflow;
|
||||
|
||||
if (played) {
|
||||
uint32_t max_buffered_samples = ap->target_buffering
|
||||
+ 12 * ap->output_buffer
|
||||
+ ap->target_buffering / 10;
|
||||
if (buffered_samples > max_buffered_samples) {
|
||||
uint32_t skip_samples = buffered_samples - max_buffered_samples;
|
||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
||||
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||
" samples", skip_samples);
|
||||
}
|
||||
|
||||
// reset (the current value was copied to a local variable)
|
||||
ap->underflow = 0;
|
||||
} else {
|
||||
// SDL playback not started yet, do not accumulate more than
|
||||
// max_initial_buffering samples, this would cause unnecessary delay
|
||||
// (and glitches to compensate) on start.
|
||||
uint32_t max_initial_buffering = ap->target_buffering
|
||||
+ 2 * ap->output_buffer;
|
||||
if (buffered_samples > max_initial_buffering) {
|
||||
uint32_t skip_samples = buffered_samples - max_initial_buffering;
|
||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
|
||||
skip_samples);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
|
||||
ap->received = true;
|
||||
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
|
||||
if (played) {
|
||||
// Number of samples added (or removed, if negative) for compensation
|
||||
int32_t instant_compensation =
|
||||
(int32_t) samples_written - frame->nb_samples;
|
||||
int32_t inserted_silence = (int32_t) underflow;
|
||||
|
||||
// The compensation must apply instantly, it must not be smoothed
|
||||
ap->avg_buffering.avg += instant_compensation + inserted_silence;
|
||||
|
||||
|
||||
// However, the buffering level must be smoothed
|
||||
sc_average_push(&ap->avg_buffering, buffered_samples);
|
||||
|
||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||
LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f",
|
||||
buffered_samples, sc_average_get(&ap->avg_buffering));
|
||||
#endif
|
||||
|
||||
ap->samples_since_resync += samples_written;
|
||||
if (ap->samples_since_resync >= ap->sample_rate) {
|
||||
// Recompute compensation every second
|
||||
ap->samples_since_resync = 0;
|
||||
|
||||
float avg = sc_average_get(&ap->avg_buffering);
|
||||
int diff = ap->target_buffering - avg;
|
||||
if (abs(diff) < (int) ap->sample_rate / 1000) {
|
||||
// Do not compensate for less than 1ms, the error is just noise
|
||||
diff = 0;
|
||||
} else if (diff < 0 && buffered_samples < ap->target_buffering) {
|
||||
// Do not accelerate if the instant buffering level is below
|
||||
// the average, this would increase underflow
|
||||
diff = 0;
|
||||
}
|
||||
// Compensate the diff over 4 seconds (but will be recomputed after
|
||||
// 1 second)
|
||||
int distance = 4 * ap->sample_rate;
|
||||
// Limit compensation rate to 2%
|
||||
int abs_max_diff = distance / 50;
|
||||
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||
" compensation=%d", ap->target_buffering, avg,
|
||||
buffered_samples, diff);
|
||||
|
||||
if (diff != ap->compensation) {
|
||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||
if (ret < 0) {
|
||||
LOGW("Resampling compensation failed: %d", ret);
|
||||
// not fatal
|
||||
} else {
|
||||
ap->compensation = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return sc_audio_regulator_push(&ap->audioreg, frame);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
assert(ctx->ch_layout.nb_channels > 0);
|
||||
unsigned nb_channels = ctx->ch_layout.nb_channels;
|
||||
assert(ctx->ch_layout.nb_channels > 0 && ctx->ch_layout.nb_channels < 256);
|
||||
uint8_t nb_channels = ctx->ch_layout.nb_channels;
|
||||
#else
|
||||
int tmp = av_get_channel_layout_nb_channels(ctx->channel_layout);
|
||||
assert(tmp > 0);
|
||||
unsigned nb_channels = tmp;
|
||||
assert(tmp > 0 && tmp < 256);
|
||||
uint8_t nb_channels = tmp;
|
||||
#endif
|
||||
|
||||
assert(ctx->sample_rate > 0);
|
||||
@@ -336,128 +67,85 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
int out_bytes_per_sample = av_get_bytes_per_sample(SC_AV_SAMPLE_FMT);
|
||||
assert(out_bytes_per_sample > 0);
|
||||
|
||||
ap->sample_rate = ctx->sample_rate;
|
||||
ap->nb_channels = nb_channels;
|
||||
ap->out_bytes_per_sample = out_bytes_per_sample;
|
||||
uint32_t target_buffering_samples =
|
||||
ap->target_buffering_delay * ctx->sample_rate / SC_TICK_FREQ;
|
||||
|
||||
ap->target_buffering = ap->target_buffering_delay * ap->sample_rate
|
||||
/ SC_TICK_FREQ;
|
||||
|
||||
uint64_t aout_samples = ap->output_buffer_duration * ap->sample_rate
|
||||
/ SC_TICK_FREQ;
|
||||
assert(aout_samples <= 0xFFFF);
|
||||
ap->output_buffer = (uint16_t) aout_samples;
|
||||
|
||||
SDL_AudioSpec desired = {
|
||||
.freq = ctx->sample_rate,
|
||||
.format = SC_SDL_SAMPLE_FMT,
|
||||
.channels = nb_channels,
|
||||
.samples = aout_samples,
|
||||
.callback = sc_audio_player_sdl_callback,
|
||||
.userdata = ap,
|
||||
};
|
||||
SDL_AudioSpec obtained;
|
||||
|
||||
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
||||
if (!ap->device) {
|
||||
LOGE("Could not open audio device: %s", SDL_GetError());
|
||||
size_t sample_size = nb_channels * out_bytes_per_sample;
|
||||
bool ok = sc_audio_regulator_init(&ap->audioreg, sample_size, ctx,
|
||||
target_buffering_samples);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SwrContext *swr_ctx = swr_alloc();
|
||||
if (!swr_ctx) {
|
||||
LOG_OOM();
|
||||
goto error_close_audio_device;
|
||||
}
|
||||
ap->swr_ctx = swr_ctx;
|
||||
uint64_t aout_samples = ap->output_buffer_duration * ctx->sample_rate
|
||||
/ SC_TICK_FREQ;
|
||||
assert(aout_samples <= 0xFFFF);
|
||||
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0);
|
||||
av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0);
|
||||
#else
|
||||
av_opt_set_channel_layout(swr_ctx, "in_channel_layout",
|
||||
ctx->channel_layout, 0);
|
||||
av_opt_set_channel_layout(swr_ctx, "out_channel_layout",
|
||||
ctx->channel_layout, 0);
|
||||
#endif
|
||||
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", ctx->sample_rate, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", ctx->sample_rate, 0);
|
||||
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", ctx->sample_fmt, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", SC_AV_SAMPLE_FMT, 0);
|
||||
|
||||
int ret = swr_init(swr_ctx);
|
||||
if (ret) {
|
||||
LOGE("Failed to initialize the resampling context");
|
||||
goto error_free_swr_ctx;
|
||||
char str[5 + 1]; // max 65535
|
||||
int r = snprintf(str, sizeof(str), "%" PRIu16, (uint16_t) aout_samples);
|
||||
assert(r >= 0 && (size_t) r < sizeof(str));
|
||||
(void) r;
|
||||
if (!SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, str)) {
|
||||
LOGE("Could not set audio output buffer");
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use a ring-buffer of the target buffering size plus 1 second between the
|
||||
// producer and the consumer. It's too big on purpose, to guarantee that
|
||||
// the producer and the consumer will be able to access it in parallel
|
||||
// without locking.
|
||||
size_t audiobuf_samples = ap->target_buffering + ap->sample_rate;
|
||||
// Make the buffer at least 1024 samples long (the hint is not always
|
||||
// honored)
|
||||
uint64_t aout_buffer_samples = MAX(1024, aout_samples);
|
||||
ap->aout_buffer_size = aout_buffer_samples * sample_size;
|
||||
ap->aout_buffer = malloc(ap->aout_buffer_size);
|
||||
if (!ap->aout_buffer) {
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample;
|
||||
bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples);
|
||||
SDL_AudioSpec spec = {
|
||||
.freq = ctx->sample_rate,
|
||||
.format = SC_SDL_SAMPLE_FMT,
|
||||
.channels = nb_channels,
|
||||
};
|
||||
|
||||
ap->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&spec,
|
||||
sc_audio_player_stream_callback, ap);
|
||||
if (!ap->stream) {
|
||||
LOGE("Could not open audio device: %s", SDL_GetError());
|
||||
free(ap->aout_buffer);
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
ap->device = SDL_GetAudioStreamDevice(ap->stream);
|
||||
assert(ap->device);
|
||||
|
||||
ok = SDL_ResumeAudioDevice(ap->device);
|
||||
if (!ok) {
|
||||
goto error_free_swr_ctx;
|
||||
LOGE("Could not resume audio device: %s", SDL_GetError());
|
||||
SDL_DestroyAudioStream(ap->stream);
|
||||
free(ap->aout_buffer);
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t initial_swr_buf_size = TO_BYTES(4096);
|
||||
ap->swr_buf = malloc(initial_swr_buf_size);
|
||||
if (!ap->swr_buf) {
|
||||
LOG_OOM();
|
||||
goto error_destroy_audiobuf;
|
||||
}
|
||||
ap->swr_buf_alloc_size = initial_swr_buf_size;
|
||||
|
||||
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
|
||||
|
||||
// Samples are produced and consumed by blocks, so the buffering must be
|
||||
// smoothed to get a relatively stable value.
|
||||
sc_average_init(&ap->avg_buffering, 32);
|
||||
ap->samples_since_resync = 0;
|
||||
|
||||
ap->received = false;
|
||||
ap->played = false;
|
||||
ap->underflow = 0;
|
||||
ap->compensation = 0;
|
||||
|
||||
// The thread calling open() is the thread calling push(), which fills the
|
||||
// audio buffer consumed by the SDL audio thread.
|
||||
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
if (!ok) {
|
||||
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_HIGH);
|
||||
(void) ok; // We don't care if it worked, at least we tried
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(ap->device, 0);
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_audiobuf:
|
||||
sc_audiobuf_destroy(&ap->buf);
|
||||
error_free_swr_ctx:
|
||||
swr_free(&ap->swr_ctx);
|
||||
error_close_audio_device:
|
||||
SDL_CloseAudioDevice(ap->device);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
assert(ap->stream);
|
||||
assert(ap->device);
|
||||
SDL_PauseAudioDevice(ap->device, 1);
|
||||
SDL_CloseAudioDevice(ap->device);
|
||||
SDL_PauseAudioDevice(ap->device);
|
||||
|
||||
free(ap->swr_buf);
|
||||
sc_audiobuf_destroy(&ap->buf);
|
||||
swr_free(&ap->swr_ctx);
|
||||
// ap->device is owned by ap->stream
|
||||
SDL_DestroyAudioStream(ap->stream);
|
||||
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
|
||||
free(ap->aout_buffer);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -3,84 +3,31 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "trait/frame_sink.h"
|
||||
#include <util/audiobuf.h>
|
||||
#include <util/average.h>
|
||||
#include <util/thread.h>
|
||||
#include <util/tick.h>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include "audio_regulator.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "util/tick.h"
|
||||
|
||||
struct sc_audio_player {
|
||||
struct sc_frame_sink frame_sink;
|
||||
|
||||
SDL_AudioDeviceID device;
|
||||
|
||||
// The target buffering between the producer and the consumer. This value
|
||||
// is directly use for compensation.
|
||||
// Since audio capture and/or encoding on the device typically produce
|
||||
// blocks of 960 samples (20ms) or 1024 samples (~21.3ms), this target
|
||||
// value should be higher.
|
||||
sc_tick target_buffering_delay;
|
||||
uint32_t target_buffering; // in samples
|
||||
|
||||
// SDL audio output buffer size.
|
||||
// SDL audio output buffer size
|
||||
sc_tick output_buffer_duration;
|
||||
uint16_t output_buffer;
|
||||
|
||||
// Audio buffer to communicate between the receiver and the SDL audio
|
||||
// callback (protected by SDL_AudioDeviceLock())
|
||||
struct sc_audiobuf buf;
|
||||
uint8_t *aout_buffer;
|
||||
size_t aout_buffer_size;
|
||||
|
||||
// The previous empty space in the buffer (only used by the receiver
|
||||
// thread)
|
||||
uint32_t previous_can_write;
|
||||
|
||||
// Resampler (only used from the receiver thread)
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
// The sample rate is the same for input and output
|
||||
unsigned sample_rate;
|
||||
// The number of channels is the same for input and output
|
||||
unsigned nb_channels;
|
||||
// The number of bytes per sample for a single channel
|
||||
unsigned out_bytes_per_sample;
|
||||
|
||||
// Target buffer for resampling (only used by the receiver thread)
|
||||
uint8_t *swr_buf;
|
||||
size_t swr_buf_alloc_size;
|
||||
|
||||
// Number of buffered samples (may be negative on underflow) (only used by
|
||||
// the receiver thread)
|
||||
struct sc_average avg_buffering;
|
||||
// Count the number of samples to trigger a compensation update regularly
|
||||
// (only used by the receiver thread)
|
||||
uint32_t samples_since_resync;
|
||||
|
||||
// Number of silence samples inserted since the last received packet
|
||||
// (protected by SDL_AudioDeviceLock())
|
||||
uint32_t underflow;
|
||||
|
||||
// Current applied compensation value (only used by the receiver thread)
|
||||
int compensation;
|
||||
|
||||
// Set to true the first time a sample is received (protected by
|
||||
// SDL_AudioDeviceLock())
|
||||
bool received;
|
||||
|
||||
// Set to true the first time the SDL callback is called (protected by
|
||||
// SDL_AudioDeviceLock())
|
||||
bool played;
|
||||
|
||||
const struct sc_audio_player_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_audio_player_callbacks {
|
||||
void (*on_ended)(struct sc_audio_player *ap, bool success, void *userdata);
|
||||
SDL_AudioStream *stream;
|
||||
SDL_AudioDeviceID device; // owned by the audio stream
|
||||
struct sc_audio_regulator audioreg;
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
456
app/src/audio_regulator.c
Normal file
456
app/src/audio_regulator.c
Normal file
@@ -0,0 +1,456 @@
|
||||
#include "audio_regulator.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
//#define SC_AUDIO_REGULATOR_DEBUG // uncomment to debug
|
||||
|
||||
/**
|
||||
* Real-time audio regulator with configurable latency
|
||||
*
|
||||
* As input, the regulator regularly receives AVFrames of decoded audio samples.
|
||||
* As output, the audio player regularly requests audio samples to be played.
|
||||
* In the middle, an audio buffer stores the samples produced but not consumed
|
||||
* yet.
|
||||
*
|
||||
* The goal of the regulator is to feed the audio player with a latency as low
|
||||
* as possible while avoiding buffer underrun (i.e. not being able to provide
|
||||
* samples when requested).
|
||||
*
|
||||
* To achieve this, it attempts to maintain the average buffering (the number
|
||||
* of samples present in the buffer) around a target value. If this target
|
||||
* buffering is too low, then buffer underrun will occur frequently. If it is
|
||||
* too high, then latency will become unacceptable. This target value is
|
||||
* configured using the scrcpy option --audio-buffer.
|
||||
*
|
||||
* The regulator cannot adjust the sample input rate (it receives samples
|
||||
* produced in real-time) or the sample output rate (it must provide samples as
|
||||
* requested by the audio player). Therefore, it may only apply compensation by
|
||||
* resampling (converting _m_ input samples to _n_ output samples).
|
||||
*
|
||||
* The compensation itself is applied by libswresample (FFmpeg). It is
|
||||
* configured using swr_set_compensation(). An important work for the regulator
|
||||
* is to estimate the compensation value regularly and apply it.
|
||||
*
|
||||
* The estimated buffering level is the result of averaging the "natural"
|
||||
* buffering (samples are produced and consumed by blocks, so it must be
|
||||
* smoothed), and making instant adjustments resulting of its own actions
|
||||
* (explicit compensation and silence insertion on underflow), which are not
|
||||
* smoothed.
|
||||
*
|
||||
* Buffer underflow events can occur when packets arrive too late. In that case,
|
||||
* the regulator inserts silence. Once the packets finally arrive (late), one
|
||||
* strategy could be to drop the samples that were replaced by silence, in
|
||||
* order to keep a minimal latency. However, dropping samples in case of buffer
|
||||
* underflow is inadvisable, as it would temporarily increase the underflow
|
||||
* even more and cause very noticeable audio glitches.
|
||||
*
|
||||
* Therefore, the regulator doesn't drop any sample on underflow. The
|
||||
* compensation mechanism will absorb the delay introduced by the inserted
|
||||
* silence.
|
||||
*/
|
||||
|
||||
#define TO_BYTES(SAMPLES) sc_audiobuf_to_bytes(&ar->buf, (SAMPLES))
|
||||
#define TO_SAMPLES(BYTES) sc_audiobuf_to_samples(&ar->buf, (BYTES))
|
||||
|
||||
void
|
||||
sc_audio_regulator_pull(struct sc_audio_regulator *ar, uint8_t *out,
|
||||
uint32_t out_samples) {
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Audio regulator pulls %" PRIu32 " samples", out_samples);
|
||||
#endif
|
||||
|
||||
// A lock is necessary in the rare case where the producer needs to drop
|
||||
// samples already pushed (when the buffer is full)
|
||||
sc_mutex_lock(&ar->mutex);
|
||||
|
||||
bool played = atomic_load_explicit(&ar->played, memory_order_relaxed);
|
||||
if (!played) {
|
||||
uint32_t buffered_samples = sc_audiobuf_can_read(&ar->buf);
|
||||
// Wait until the buffer is filled up to at least target_buffering
|
||||
// before playing
|
||||
if (buffered_samples < ar->target_buffering) {
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||
" samples", out_samples);
|
||||
#endif
|
||||
// Delay playback starting to reach the target buffering. Fill the
|
||||
// whole buffer with silence (len is small compared to the
|
||||
// arbitrary margin value).
|
||||
memset(out, 0, out_samples * ar->sample_size);
|
||||
sc_mutex_unlock(&ar->mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t read = sc_audiobuf_read(&ar->buf, out, out_samples);
|
||||
|
||||
sc_mutex_unlock(&ar->mutex);
|
||||
|
||||
if (read < out_samples) {
|
||||
uint32_t silence = out_samples - read;
|
||||
// Insert silence. In theory, the inserted silent samples replace the
|
||||
// missing real samples, which will arrive later, so they should be
|
||||
// dropped to keep the latency minimal. However, this would cause very
|
||||
// audible glitches, so let the clock compensation restore the target
|
||||
// latency.
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
|
||||
silence);
|
||||
#endif
|
||||
memset(out + TO_BYTES(read), 0, TO_BYTES(silence));
|
||||
|
||||
bool received = atomic_load_explicit(&ar->received,
|
||||
memory_order_relaxed);
|
||||
if (received) {
|
||||
// Inserting additional samples immediately increases buffering
|
||||
atomic_fetch_add_explicit(&ar->underflow, silence,
|
||||
memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store_explicit(&ar->played, true, memory_order_relaxed);
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
sc_audio_regulator_get_swr_buf(struct sc_audio_regulator *ar,
|
||||
uint32_t min_samples) {
|
||||
size_t min_buf_size = TO_BYTES(min_samples);
|
||||
if (min_buf_size > ar->swr_buf_alloc_size) {
|
||||
size_t new_size = min_buf_size + 4096;
|
||||
uint8_t *buf = realloc(ar->swr_buf, new_size);
|
||||
if (!buf) {
|
||||
LOG_OOM();
|
||||
// Could not realloc to the requested size
|
||||
return NULL;
|
||||
}
|
||||
ar->swr_buf = buf;
|
||||
ar->swr_buf_alloc_size = new_size;
|
||||
}
|
||||
|
||||
return ar->swr_buf;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
SwrContext *swr_ctx = ar->swr_ctx;
|
||||
|
||||
uint32_t input_samples = frame->nb_samples;
|
||||
|
||||
assert(frame->pts >= 0);
|
||||
int64_t pts = frame->pts;
|
||||
if (ar->next_expected_pts && pts - ar->next_expected_pts > 100000) {
|
||||
LOGV("[Audio] Discontinuity detected: %" PRIi64 "µs",
|
||||
pts - ar->next_expected_pts);
|
||||
// More than 100ms: consider it as a discontinuity
|
||||
// (typically because silence packets were not captured)
|
||||
uint32_t can_read = sc_audiobuf_can_read(&ar->buf);
|
||||
if (input_samples + can_read < ar->target_buffering) {
|
||||
// Adjust buffering to the target value directly
|
||||
uint32_t silence = ar->target_buffering - can_read - input_samples;
|
||||
sc_audiobuf_write_silence(&ar->buf, silence);
|
||||
}
|
||||
|
||||
// Reset state
|
||||
ar->avg_buffering.avg = ar->target_buffering;
|
||||
int ret = swr_set_compensation(swr_ctx, 0, 0);
|
||||
(void) ret;
|
||||
assert(!ret); // disabling compensation should never fail
|
||||
ar->compensation_active = false;
|
||||
ar->samples_since_resync = 0;
|
||||
atomic_store_explicit(&ar->underflow, 0, memory_order_relaxed);
|
||||
}
|
||||
|
||||
int64_t packet_duration = input_samples * INT64_C(1000000)
|
||||
/ ar->sample_rate;
|
||||
ar->next_expected_pts = pts + packet_duration;
|
||||
|
||||
int64_t swr_delay = swr_get_delay(swr_ctx, ar->sample_rate);
|
||||
// No need to av_rescale_rnd(), input and output sample rates are the same.
|
||||
// Add more space (256) for clock compensation.
|
||||
int dst_nb_samples = swr_delay + frame->nb_samples + 256;
|
||||
|
||||
uint8_t *swr_buf = sc_audio_regulator_get_swr_buf(ar, dst_nb_samples);
|
||||
if (!swr_buf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = swr_convert(swr_ctx, &swr_buf, dst_nb_samples,
|
||||
(const uint8_t **) frame->data, frame->nb_samples);
|
||||
if (ret < 0) {
|
||||
LOGE("Resampling failed: %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// swr_convert() returns the number of samples which would have been
|
||||
// written if the buffer was big enough.
|
||||
uint32_t samples = MIN(ret, dst_nb_samples);
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
|
||||
#endif
|
||||
|
||||
uint32_t cap = sc_audiobuf_capacity(&ar->buf);
|
||||
if (samples > cap) {
|
||||
// Very very unlikely: a single resampled frame should never
|
||||
// exceed the audio buffer size (or something is very wrong).
|
||||
// Ignore the first bytes in swr_buf to avoid memory corruption anyway.
|
||||
swr_buf += TO_BYTES(samples - cap);
|
||||
samples = cap;
|
||||
}
|
||||
|
||||
uint32_t skipped_samples = 0;
|
||||
|
||||
uint32_t written = sc_audiobuf_write(&ar->buf, swr_buf, samples);
|
||||
if (written < samples) {
|
||||
uint32_t remaining = samples - written;
|
||||
|
||||
// All samples that could be written without locking have been written,
|
||||
// now we need to lock to drop/consume old samples
|
||||
sc_mutex_lock(&ar->mutex);
|
||||
|
||||
// Retry with the lock
|
||||
written += sc_audiobuf_write(&ar->buf,
|
||||
swr_buf + TO_BYTES(written),
|
||||
remaining);
|
||||
if (written < samples) {
|
||||
remaining = samples - written;
|
||||
// Still insufficient, drop old samples to make space
|
||||
skipped_samples = sc_audiobuf_read(&ar->buf, NULL, remaining);
|
||||
assert(skipped_samples == remaining);
|
||||
}
|
||||
|
||||
sc_mutex_unlock(&ar->mutex);
|
||||
|
||||
if (written < samples) {
|
||||
// Now there is enough space
|
||||
uint32_t w = sc_audiobuf_write(&ar->buf,
|
||||
swr_buf + TO_BYTES(written),
|
||||
remaining);
|
||||
assert(w == remaining);
|
||||
(void) w;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t underflow = 0;
|
||||
uint32_t max_buffered_samples;
|
||||
bool played = atomic_load_explicit(&ar->played, memory_order_relaxed);
|
||||
if (played) {
|
||||
underflow = atomic_exchange_explicit(&ar->underflow, 0,
|
||||
memory_order_relaxed);
|
||||
ar->underflow_report += underflow;
|
||||
|
||||
max_buffered_samples = ar->target_buffering * 11 / 10
|
||||
+ 60 * ar->sample_rate / 1000 /* 60 ms */;
|
||||
} else {
|
||||
// Playback not started yet, do not accumulate more than
|
||||
// max_initial_buffering samples, this would cause unnecessary delay
|
||||
// (and glitches to compensate) on start.
|
||||
max_buffered_samples = ar->target_buffering
|
||||
+ 10 * ar->sample_rate / 1000 /* 10 ms */;
|
||||
}
|
||||
|
||||
uint32_t can_read = sc_audiobuf_can_read(&ar->buf);
|
||||
if (can_read > max_buffered_samples) {
|
||||
uint32_t skip_samples = 0;
|
||||
|
||||
sc_mutex_lock(&ar->mutex);
|
||||
can_read = sc_audiobuf_can_read(&ar->buf);
|
||||
if (can_read > max_buffered_samples) {
|
||||
skip_samples = can_read - max_buffered_samples;
|
||||
uint32_t r = sc_audiobuf_read(&ar->buf, NULL, skip_samples);
|
||||
assert(r == skip_samples);
|
||||
(void) r;
|
||||
skipped_samples += skip_samples;
|
||||
}
|
||||
sc_mutex_unlock(&ar->mutex);
|
||||
|
||||
if (skip_samples) {
|
||||
if (played) {
|
||||
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||
" samples", skip_samples);
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
} else {
|
||||
LOGD("[Audio] Playback not started, skipping %" PRIu32
|
||||
" samples", skip_samples);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store_explicit(&ar->received, true, memory_order_relaxed);
|
||||
if (!played) {
|
||||
// Nothing more to do
|
||||
return true;
|
||||
}
|
||||
|
||||
// Number of samples added (or removed, if negative) for compensation
|
||||
int32_t instant_compensation = (int32_t) written - input_samples;
|
||||
// Inserting silence instantly increases buffering
|
||||
int32_t inserted_silence = (int32_t) underflow;
|
||||
// Dropping input samples instantly decreases buffering
|
||||
int32_t dropped = (int32_t) skipped_samples;
|
||||
|
||||
// The compensation must apply instantly, it must not be smoothed
|
||||
ar->avg_buffering.avg += instant_compensation + inserted_silence - dropped;
|
||||
if (ar->avg_buffering.avg < 0) {
|
||||
// Since dropping samples instantly reduces buffering, the difference
|
||||
// is applied immediately to the average value, assuming that the delay
|
||||
// between the producer and the consumer will be caught up.
|
||||
//
|
||||
// However, when this assumption is not valid, the average buffering
|
||||
// may decrease indefinitely. Prevent it to become negative to limit
|
||||
// the consequences.
|
||||
ar->avg_buffering.avg = 0;
|
||||
}
|
||||
|
||||
// However, the buffering level must be smoothed
|
||||
sc_average_push(&ar->avg_buffering, can_read);
|
||||
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
|
||||
can_read, sc_average_get(&ar->avg_buffering));
|
||||
#endif
|
||||
|
||||
ar->samples_since_resync += written;
|
||||
if (ar->samples_since_resync >= ar->sample_rate) {
|
||||
// Recompute compensation every second
|
||||
ar->samples_since_resync = 0;
|
||||
|
||||
float avg = sc_average_get(&ar->avg_buffering);
|
||||
int diff = ar->target_buffering - avg;
|
||||
|
||||
// Enable compensation when the difference exceeds +/- 4ms.
|
||||
// Disable compensation when the difference is lower than +/- 1ms.
|
||||
int threshold = ar->compensation_active
|
||||
? ar->sample_rate / 1000 /* 1ms */
|
||||
: ar->sample_rate * 4 / 1000; /* 4ms */
|
||||
|
||||
if (abs(diff) < threshold) {
|
||||
// Do not compensate for small values, the error is just noise
|
||||
diff = 0;
|
||||
} else if (diff < 0 && can_read < ar->target_buffering) {
|
||||
// Do not accelerate if the instant buffering level is below the
|
||||
// target, this would increase underflow
|
||||
diff = 0;
|
||||
}
|
||||
// Compensate the diff over 4 seconds (but will be recomputed after 1
|
||||
// second)
|
||||
int distance = 4 * ar->sample_rate;
|
||||
// Limit compensation rate to 2%
|
||||
int abs_max_diff = distance / 50;
|
||||
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||
" compensation=%d (underflow=%" PRIu32 ")",
|
||||
ar->target_buffering, avg, can_read, diff, ar->underflow_report);
|
||||
ar->underflow_report = 0;
|
||||
|
||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||
if (ret < 0) {
|
||||
LOGW("Resampling compensation failed: %d", ret);
|
||||
// not fatal
|
||||
} else {
|
||||
ar->compensation_active = diff != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
||||
const AVCodecContext *ctx, uint32_t target_buffering) {
|
||||
SwrContext *swr_ctx = swr_alloc();
|
||||
if (!swr_ctx) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
ar->swr_ctx = swr_ctx;
|
||||
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
av_opt_set_chlayout(swr_ctx, "in_chlayout", &ctx->ch_layout, 0);
|
||||
av_opt_set_chlayout(swr_ctx, "out_chlayout", &ctx->ch_layout, 0);
|
||||
#else
|
||||
av_opt_set_channel_layout(swr_ctx, "in_channel_layout",
|
||||
ctx->channel_layout, 0);
|
||||
av_opt_set_channel_layout(swr_ctx, "out_channel_layout",
|
||||
ctx->channel_layout, 0);
|
||||
#endif
|
||||
|
||||
av_opt_set_int(swr_ctx, "in_sample_rate", ctx->sample_rate, 0);
|
||||
av_opt_set_int(swr_ctx, "out_sample_rate", ctx->sample_rate, 0);
|
||||
|
||||
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", ctx->sample_fmt, 0);
|
||||
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", SC_AV_SAMPLE_FMT, 0);
|
||||
|
||||
int ret = swr_init(swr_ctx);
|
||||
if (ret) {
|
||||
LOGE("Failed to initialize the resampling context");
|
||||
goto error_free_swr_ctx;
|
||||
}
|
||||
|
||||
bool ok = sc_mutex_init(&ar->mutex);
|
||||
if (!ok) {
|
||||
goto error_free_swr_ctx;
|
||||
}
|
||||
|
||||
ar->target_buffering = target_buffering;
|
||||
ar->sample_size = sample_size;
|
||||
ar->sample_rate = ctx->sample_rate;
|
||||
|
||||
// Use a ring-buffer of the target buffering size plus 1 second between the
|
||||
// producer and the consumer. It's too big on purpose, to guarantee that
|
||||
// the producer and the consumer will be able to access it in parallel
|
||||
// without locking.
|
||||
uint32_t audiobuf_samples = target_buffering + ar->sample_rate;
|
||||
|
||||
ok = sc_audiobuf_init(&ar->buf, sample_size, audiobuf_samples);
|
||||
if (!ok) {
|
||||
goto error_destroy_mutex;
|
||||
}
|
||||
|
||||
size_t initial_swr_buf_size = TO_BYTES(4096);
|
||||
ar->swr_buf = malloc(initial_swr_buf_size);
|
||||
if (!ar->swr_buf) {
|
||||
LOG_OOM();
|
||||
goto error_destroy_audiobuf;
|
||||
}
|
||||
ar->swr_buf_alloc_size = initial_swr_buf_size;
|
||||
|
||||
// Samples are produced and consumed by blocks, so the buffering must be
|
||||
// smoothed to get a relatively stable value.
|
||||
sc_average_init(&ar->avg_buffering, 128);
|
||||
ar->samples_since_resync = 0;
|
||||
|
||||
ar->received = false;
|
||||
atomic_init(&ar->played, false);
|
||||
atomic_init(&ar->received, false);
|
||||
atomic_init(&ar->underflow, 0);
|
||||
ar->underflow_report = 0;
|
||||
ar->compensation_active = false;
|
||||
ar->next_expected_pts = 0;
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_audiobuf:
|
||||
sc_audiobuf_destroy(&ar->buf);
|
||||
error_destroy_mutex:
|
||||
sc_mutex_destroy(&ar->mutex);
|
||||
error_free_swr_ctx:
|
||||
swr_free(&ar->swr_ctx);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
sc_audio_regulator_destroy(struct sc_audio_regulator *ar) {
|
||||
free(ar->swr_buf);
|
||||
sc_audiobuf_destroy(&ar->buf);
|
||||
sc_mutex_destroy(&ar->mutex);
|
||||
swr_free(&ar->swr_ctx);
|
||||
}
|
||||
79
app/src/audio_regulator.h
Normal file
79
app/src/audio_regulator.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef SC_AUDIO_REGULATOR_H
|
||||
#define SC_AUDIO_REGULATOR_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include "util/audiobuf.h"
|
||||
#include "util/average.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
#define SC_AV_SAMPLE_FMT AV_SAMPLE_FMT_FLT
|
||||
|
||||
struct sc_audio_regulator {
|
||||
sc_mutex mutex;
|
||||
|
||||
// Target buffering between the producer and the consumer (in samples)
|
||||
uint32_t target_buffering;
|
||||
|
||||
// Audio buffer to communicate between the receiver and the player
|
||||
struct sc_audiobuf buf;
|
||||
|
||||
// Resampler (only used from the receiver thread)
|
||||
struct SwrContext *swr_ctx;
|
||||
|
||||
// The sample rate is the same for input and output
|
||||
uint32_t sample_rate;
|
||||
// The number of bytes per sample (for all channels)
|
||||
size_t sample_size;
|
||||
|
||||
// Target buffer for resampling (only used by the receiver thread)
|
||||
uint8_t *swr_buf;
|
||||
size_t swr_buf_alloc_size;
|
||||
|
||||
// Number of buffered samples (may be negative on underflow) (only used by
|
||||
// the receiver thread)
|
||||
struct sc_average avg_buffering;
|
||||
// Count the number of samples to trigger a compensation update regularly
|
||||
// (only used by the receiver thread)
|
||||
uint32_t samples_since_resync;
|
||||
|
||||
// Number of silence samples inserted since the last received packet
|
||||
atomic_uint_least32_t underflow;
|
||||
|
||||
// Number of silence samples inserted since the last log
|
||||
uint32_t underflow_report;
|
||||
|
||||
// Non-zero compensation applied (only used by the receiver thread)
|
||||
bool compensation_active;
|
||||
|
||||
// Set to true the first time a sample is received
|
||||
atomic_bool received;
|
||||
|
||||
// Set to true the first time samples are pulled by the player
|
||||
atomic_bool played;
|
||||
|
||||
// PTS of the next expected packet (useful to detect discontinuities)
|
||||
int64_t next_expected_pts;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
||||
const AVCodecContext *ctx, uint32_t target_buffering);
|
||||
|
||||
void
|
||||
sc_audio_regulator_destroy(struct sc_audio_regulator *ar);
|
||||
|
||||
bool
|
||||
sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame);
|
||||
|
||||
void
|
||||
sc_audio_regulator_pull(struct sc_audio_regulator *ar, uint8_t *out,
|
||||
uint32_t samples);
|
||||
|
||||
#endif
|
||||
1636
app/src/cli.c
1636
app/src/cli.c
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,17 @@
|
||||
|
||||
#include "options.h"
|
||||
|
||||
enum sc_pause_on_exit {
|
||||
SC_PAUSE_ON_EXIT_TRUE,
|
||||
SC_PAUSE_ON_EXIT_FALSE,
|
||||
SC_PAUSE_ON_EXIT_IF_ERROR,
|
||||
};
|
||||
|
||||
struct scrcpy_cli_args {
|
||||
struct scrcpy_options opts;
|
||||
bool help;
|
||||
bool version;
|
||||
enum sc_pause_on_exit pause_on_exit;
|
||||
};
|
||||
|
||||
void
|
||||
@@ -21,7 +28,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
|
||||
|
||||
#ifdef SC_TEST
|
||||
bool
|
||||
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
|
||||
sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_CLOCK_NDEBUG // comment to debug
|
||||
//#define SC_CLOCK_DEBUG // uncomment to debug
|
||||
|
||||
#define SC_CLOCK_RANGE 32
|
||||
|
||||
@@ -21,10 +21,12 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
|
||||
}
|
||||
|
||||
sc_tick offset = system - stream;
|
||||
clock->offset = ((clock->range - 1) * clock->offset + offset)
|
||||
/ clock->range;
|
||||
unsigned clock_weight = clock->range - 1;
|
||||
unsigned value_weight = SC_CLOCK_RANGE - clock->range + 1;
|
||||
clock->offset = (clock->offset * clock_weight + offset * value_weight)
|
||||
/ SC_CLOCK_RANGE;
|
||||
|
||||
#ifndef SC_CLOCK_NDEBUG
|
||||
#ifdef SC_CLOCK_DEBUG
|
||||
LOGD("Clock estimation: pts + %" PRItick, clock->offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavformat/version.h>
|
||||
#include <SDL2/SDL_version.h>
|
||||
#include <libavutil/version.h>
|
||||
#include <SDL3/SDL_version.h>
|
||||
|
||||
#ifndef __WIN32
|
||||
#ifndef _WIN32
|
||||
# define PRIu64_ PRIu64
|
||||
# define SC_PRIsizet "zu"
|
||||
#else
|
||||
@@ -50,18 +52,13 @@
|
||||
# define SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
||||
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
|
||||
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 16)
|
||||
# define SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2023-10-06 - 5432d2aacad - lavc 60.15.100 - avformat.h
|
||||
// Deprecate AVFormatContext.{nb_,}side_data, av_stream_add_side_data(),
|
||||
// av_stream_new_side_data(), and av_stream_get_side_data(). Side data fields
|
||||
// from AVFormatContext.codecpar should be used from now on.
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
|
||||
# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_STRDUP
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
#define MOTIONEVENT_ACTION_LABEL(value) \
|
||||
ENUM_TO_LABEL(android_motionevent_action_labels, value)
|
||||
|
||||
#define SCREEN_POWER_MODE_LABEL(value) \
|
||||
ENUM_TO_LABEL(screen_power_mode_labels, value)
|
||||
|
||||
static const char *const android_keyevent_action_labels[] = {
|
||||
"down",
|
||||
"up",
|
||||
@@ -47,14 +44,6 @@ static const char *const android_motionevent_action_labels[] = {
|
||||
"btn-release",
|
||||
};
|
||||
|
||||
static const char *const screen_power_mode_labels[] = {
|
||||
"off",
|
||||
"doze",
|
||||
"normal",
|
||||
"doze-suspend",
|
||||
"suspend",
|
||||
};
|
||||
|
||||
static const char *const copy_key_labels[] = {
|
||||
"none",
|
||||
"copy",
|
||||
@@ -64,13 +53,11 @@ static const char *const copy_key_labels[] = {
|
||||
static inline const char *
|
||||
get_well_known_pointer_id_name(uint64_t pointer_id) {
|
||||
switch (pointer_id) {
|
||||
case POINTER_ID_MOUSE:
|
||||
case SC_POINTER_ID_MOUSE:
|
||||
return "mouse";
|
||||
case POINTER_ID_GENERIC_FINGER:
|
||||
case SC_POINTER_ID_GENERIC_FINGER:
|
||||
return "finger";
|
||||
case POINTER_ID_VIRTUAL_MOUSE:
|
||||
return "vmouse";
|
||||
case POINTER_ID_VIRTUAL_FINGER:
|
||||
case SC_POINTER_ID_VIRTUAL_FINGER:
|
||||
return "vfinger";
|
||||
default:
|
||||
return NULL;
|
||||
@@ -85,17 +72,36 @@ write_position(uint8_t *buf, const struct sc_position *position) {
|
||||
sc_write16be(&buf[10], position->screen_size.height);
|
||||
}
|
||||
|
||||
// write length (4 bytes) + string (non null-terminated)
|
||||
// Write truncated string, and return the size
|
||||
static size_t
|
||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||
write_string_payload(uint8_t *payload, const char *utf8, size_t max_len) {
|
||||
if (!utf8) {
|
||||
return 0;
|
||||
}
|
||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
||||
memcpy(payload, utf8, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Write length (4 bytes) + string (non null-terminated)
|
||||
static size_t
|
||||
write_string(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||
size_t len = write_string_payload(buf + 4, utf8, max_len);
|
||||
sc_write32be(buf, len);
|
||||
memcpy(&buf[4], utf8, len);
|
||||
return 4 + len;
|
||||
}
|
||||
|
||||
// Write length (1 byte) + string (non null-terminated)
|
||||
static size_t
|
||||
write_string_tiny(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||
assert(max_len <= 0xFF);
|
||||
size_t len = write_string_payload(buf + 1, utf8, max_len);
|
||||
buf[0] = len;
|
||||
return 1 + len;
|
||||
}
|
||||
|
||||
size_t
|
||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
buf[0] = msg->type;
|
||||
switch (msg->type) {
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||
@@ -105,9 +111,8 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||
sc_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||
return 14;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||
size_t len =
|
||||
write_string(msg->inject_text.text,
|
||||
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
|
||||
size_t len = write_string(&buf[1], msg->inject_text.text,
|
||||
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||
return 1 + len;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||
@@ -122,10 +127,14 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||
return 32;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||
int16_t hscroll =
|
||||
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
|
||||
int16_t vscroll =
|
||||
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
|
||||
// Accept values in the range [-16, 16].
|
||||
// Normalize to [-1, 1] in order to use sc_float_to_i16fp().
|
||||
float hscroll_norm = msg->inject_scroll_event.hscroll / 16;
|
||||
hscroll_norm = CLAMP(hscroll_norm, -1, 1);
|
||||
float vscroll_norm = msg->inject_scroll_event.vscroll / 16;
|
||||
vscroll_norm = CLAMP(vscroll_norm, -1, 1);
|
||||
int16_t hscroll = sc_float_to_i16fp(hscroll_norm);
|
||||
int16_t vscroll = sc_float_to_i16fp(vscroll_norm);
|
||||
sc_write16be(&buf[13], (uint16_t) hscroll);
|
||||
sc_write16be(&buf[15], (uint16_t) vscroll);
|
||||
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
|
||||
@@ -139,17 +148,51 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
sc_write64be(&buf[1], msg->set_clipboard.sequence);
|
||||
buf[9] = !!msg->set_clipboard.paste;
|
||||
size_t len = write_string(msg->set_clipboard.text,
|
||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||
&buf[10]);
|
||||
size_t len = write_string(&buf[10], msg->set_clipboard.text,
|
||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
|
||||
return 10 + len;
|
||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
buf[1] = msg->set_screen_power_mode.mode;
|
||||
case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
|
||||
buf[1] = msg->set_display_power.on;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||
sc_write16be(&buf[3], msg->uhid_create.vendor_id);
|
||||
sc_write16be(&buf[5], msg->uhid_create.product_id);
|
||||
|
||||
size_t index = 7;
|
||||
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
||||
|
||||
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
||||
index += 2;
|
||||
|
||||
memcpy(&buf[index], msg->uhid_create.report_desc,
|
||||
msg->uhid_create.report_desc_size);
|
||||
index += msg->uhid_create.report_desc_size;
|
||||
|
||||
return index;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
||||
sc_write16be(&buf[1], msg->uhid_input.id);
|
||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
||||
return 5 + msg->uhid_input.size;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
|
||||
sc_write16be(&buf[1], msg->uhid_destroy.id);
|
||||
return 3;
|
||||
case SC_CONTROL_MSG_TYPE_START_APP: {
|
||||
size_t len = write_string_tiny(&buf[1], msg->start_app.name, 255);
|
||||
return 1 + len;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH:
|
||||
buf[1] = msg->camera_set_torch.on ? 1 : 0;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
|
||||
// no additional data
|
||||
return 1;
|
||||
default:
|
||||
@@ -226,9 +269,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
msg->set_clipboard.paste ? "paste" : "nopaste",
|
||||
msg->set_clipboard.text);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
LOG_CMSG("power mode %s",
|
||||
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
|
||||
case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
|
||||
LOG_CMSG("display power %s",
|
||||
msg->set_display_power.on ? "on" : "off");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
LOG_CMSG("expand notification panel");
|
||||
@@ -242,12 +285,70 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
LOG_CMSG("rotate device");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE: {
|
||||
// Quote only if name is not null
|
||||
const char *name = msg->uhid_create.name;
|
||||
const char *quote = name ? "\"" : "";
|
||||
LOG_CMSG("UHID create [%" PRIu16 "] %04" PRIx16 ":%04" PRIx16
|
||||
" name=%s%s%s report_desc_size=%" PRIu16,
|
||||
msg->uhid_create.id,
|
||||
msg->uhid_create.vendor_id,
|
||||
msg->uhid_create.product_id,
|
||||
quote, name, quote,
|
||||
msg->uhid_create.report_desc_size);
|
||||
break;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
||||
msg->uhid_input.size);
|
||||
if (hex) {
|
||||
LOG_CMSG("UHID input [%" PRIu16 "] %s",
|
||||
msg->uhid_input.id, hex);
|
||||
free(hex);
|
||||
} else {
|
||||
LOG_CMSG("UHID input [%" PRIu16 "] size=%" PRIu16,
|
||||
msg->uhid_input.id, msg->uhid_input.size);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
|
||||
LOG_CMSG("UHID destroy [%" PRIu16 "]", msg->uhid_destroy.id);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
LOG_CMSG("open hard keyboard settings");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_START_APP:
|
||||
LOG_CMSG("start app \"%s\"", msg->start_app.name);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
LOG_CMSG("reset video");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH:
|
||||
LOG_CMSG("camera set torch %s",
|
||||
msg->camera_set_torch.on ? "on" : "off");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN:
|
||||
LOG_CMSG("camera zoom in");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
|
||||
LOG_CMSG("camera zoom out");
|
||||
break;
|
||||
default:
|
||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_control_msg_is_droppable(const struct sc_control_msg *msg) {
|
||||
// Cannot drop UHID_CREATE messages, because it would cause all further
|
||||
// UHID_INPUT messages for this device to be invalid.
|
||||
// Cannot drop UHID_DESTROY messages either, because a further UHID_CREATE
|
||||
// with the same id may fail.
|
||||
return msg->type != SC_CONTROL_MSG_TYPE_UHID_CREATE
|
||||
&& msg->type != SC_CONTROL_MSG_TYPE_UHID_DESTROY;
|
||||
}
|
||||
|
||||
void
|
||||
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
||||
switch (msg->type) {
|
||||
@@ -257,6 +358,9 @@ sc_control_msg_destroy(struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||
free(msg->set_clipboard.text);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_START_APP:
|
||||
free(msg->start_app.name);
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "android/input.h"
|
||||
#include "android/keycodes.h"
|
||||
#include "coords.h"
|
||||
#include "hid/hid_event.h"
|
||||
|
||||
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||
|
||||
@@ -17,12 +18,11 @@
|
||||
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
||||
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
||||
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
||||
#define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
||||
#define SC_POINTER_ID_MOUSE UINT64_C(-1)
|
||||
#define SC_POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
||||
|
||||
// Used for injecting an additional virtual pointer for pinch-to-zoom
|
||||
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
|
||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
|
||||
#define SC_POINTER_ID_VIRTUAL_FINGER UINT64_C(-3)
|
||||
|
||||
enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||
@@ -35,14 +35,17 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||
SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
|
||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
};
|
||||
|
||||
enum sc_screen_power_mode {
|
||||
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
||||
SC_SCREEN_POWER_MODE_OFF = 0,
|
||||
SC_SCREEN_POWER_MODE_NORMAL = 2,
|
||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
SC_CONTROL_MSG_TYPE_START_APP,
|
||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT,
|
||||
};
|
||||
|
||||
enum sc_copy_key {
|
||||
@@ -90,19 +93,46 @@ struct sc_control_msg {
|
||||
bool paste;
|
||||
} set_clipboard;
|
||||
struct {
|
||||
enum sc_screen_power_mode mode;
|
||||
} set_screen_power_mode;
|
||||
bool on;
|
||||
} set_display_power;
|
||||
struct {
|
||||
uint16_t id;
|
||||
uint16_t vendor_id;
|
||||
uint16_t product_id;
|
||||
const char *name; // pointer to static data
|
||||
uint16_t report_desc_size;
|
||||
const uint8_t *report_desc; // pointer to static data
|
||||
} uhid_create;
|
||||
struct {
|
||||
uint16_t id;
|
||||
uint16_t size;
|
||||
uint8_t data[SC_HID_MAX_SIZE];
|
||||
} uhid_input;
|
||||
struct {
|
||||
uint16_t id;
|
||||
} uhid_destroy;
|
||||
struct {
|
||||
char *name;
|
||||
} start_app;
|
||||
struct {
|
||||
bool on;
|
||||
} camera_set_torch;
|
||||
};
|
||||
};
|
||||
|
||||
// buf size must be at least CONTROL_MSG_MAX_SIZE
|
||||
// return the number of bytes written
|
||||
size_t
|
||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
|
||||
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf);
|
||||
|
||||
void
|
||||
sc_control_msg_log(const struct sc_control_msg *msg);
|
||||
|
||||
// Even when the buffer is "full", some messages must absolutely not be dropped
|
||||
// to avoid inconsistencies.
|
||||
bool
|
||||
sc_control_msg_is_droppable(const struct sc_control_msg *msg);
|
||||
|
||||
void
|
||||
sc_control_msg_destroy(struct sc_control_msg *msg);
|
||||
|
||||
|
||||
@@ -4,19 +4,38 @@
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
||||
// Drop droppable events above this limit
|
||||
#define SC_CONTROL_MSG_QUEUE_LIMIT 60
|
||||
|
||||
static void
|
||||
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
|
||||
void *userdata) {
|
||||
(void) receiver;
|
||||
|
||||
struct sc_controller *controller = userdata;
|
||||
// Forward the event to the controller listener
|
||||
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
struct sc_acksync *acksync) {
|
||||
const struct sc_controller_callbacks *cbs,
|
||||
void *cbs_userdata) {
|
||||
sc_vecdeque_init(&controller->queue);
|
||||
|
||||
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
||||
// Add 4 to support 4 non-droppable events without re-allocation
|
||||
bool ok = sc_vecdeque_reserve(&controller->queue,
|
||||
SC_CONTROL_MSG_QUEUE_LIMIT + 4);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_receiver_init(&controller->receiver, control_socket, acksync);
|
||||
static const struct sc_receiver_callbacks receiver_cbs = {
|
||||
.on_ended = sc_controller_receiver_on_ended,
|
||||
};
|
||||
|
||||
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
|
||||
controller);
|
||||
if (!ok) {
|
||||
sc_vecdeque_destroy(&controller->queue);
|
||||
return false;
|
||||
@@ -40,9 +59,21 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
controller->control_socket = control_socket;
|
||||
controller->stopped = false;
|
||||
|
||||
assert(cbs && cbs->on_ended);
|
||||
controller->cbs = cbs;
|
||||
controller->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_controller_configure(struct sc_controller *controller,
|
||||
struct sc_acksync *acksync,
|
||||
struct sc_uhid_devices *uhid_devices) {
|
||||
controller->receiver.acksync = acksync;
|
||||
controller->receiver.uhid_devices = uhid_devices;
|
||||
}
|
||||
|
||||
void
|
||||
sc_controller_destroy(struct sc_controller *controller) {
|
||||
sc_cond_destroy(&controller->msg_cond);
|
||||
@@ -65,39 +96,59 @@ sc_controller_push_msg(struct sc_controller *controller,
|
||||
sc_control_msg_log(msg);
|
||||
}
|
||||
|
||||
bool pushed = false;
|
||||
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
bool full = sc_vecdeque_is_full(&controller->queue);
|
||||
if (!full) {
|
||||
size_t size = sc_vecdeque_size(&controller->queue);
|
||||
if (size < SC_CONTROL_MSG_QUEUE_LIMIT) {
|
||||
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
||||
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
||||
pushed = true;
|
||||
if (was_empty) {
|
||||
sc_cond_signal(&controller->msg_cond);
|
||||
}
|
||||
} else if (!sc_control_msg_is_droppable(msg)) {
|
||||
bool ok = sc_vecdeque_push(&controller->queue, *msg);
|
||||
if (ok) {
|
||||
pushed = true;
|
||||
} else {
|
||||
// A non-droppable event must be dropped anyway
|
||||
LOG_OOM();
|
||||
}
|
||||
}
|
||||
// Otherwise (if the queue is full), the msg is discarded
|
||||
// Otherwise, the msg is discarded
|
||||
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
return !full;
|
||||
return pushed;
|
||||
}
|
||||
|
||||
static bool
|
||||
process_msg(struct sc_controller *controller,
|
||||
const struct sc_control_msg *msg) {
|
||||
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
||||
const struct sc_control_msg *msg, bool *eos) {
|
||||
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t length = sc_control_msg_serialize(msg, serialized_msg);
|
||||
if (!length) {
|
||||
*eos = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t w =
|
||||
net_send_all(controller->control_socket, serialized_msg, length);
|
||||
return (size_t) w == length;
|
||||
if ((size_t) w != length) {
|
||||
*eos = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run_controller(void *data) {
|
||||
struct sc_controller *controller = data;
|
||||
|
||||
bool error = false;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
while (!controller->stopped
|
||||
@@ -107,6 +158,7 @@ run_controller(void *data) {
|
||||
if (controller->stopped) {
|
||||
// stop immediately, do not process further msgs
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
LOGD("Controller stopped");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -114,13 +166,20 @@ run_controller(void *data) {
|
||||
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
bool ok = process_msg(controller, &msg);
|
||||
bool eos;
|
||||
bool ok = process_msg(controller, &msg, &eos);
|
||||
sc_control_msg_destroy(&msg);
|
||||
if (!ok) {
|
||||
LOGD("Could not write msg to socket");
|
||||
if (eos) {
|
||||
LOGD("Controller stopped (socket closed)");
|
||||
} // else error already logged
|
||||
error = !eos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,25 @@ struct sc_controller {
|
||||
bool stopped;
|
||||
struct sc_control_msg_queue queue;
|
||||
struct sc_receiver receiver;
|
||||
|
||||
const struct sc_controller_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_controller_callbacks {
|
||||
void (*on_ended)(struct sc_controller *controller, bool error,
|
||||
void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
struct sc_acksync *acksync);
|
||||
const struct sc_controller_callbacks *cbs,
|
||||
void *cbs_userdata);
|
||||
|
||||
void
|
||||
sc_controller_configure(struct sc_controller *controller,
|
||||
struct sc_acksync *acksync,
|
||||
struct sc_uhid_devices *uhid_devices);
|
||||
|
||||
void
|
||||
sc_controller_destroy(struct sc_controller *controller);
|
||||
|
||||
@@ -1,31 +1,39 @@
|
||||
#include "decoder.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <errno.h>
|
||||
#include <libavcodec/packet.h>
|
||||
#include <libavutil/avutil.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast packet_sink to decoder */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
|
||||
|
||||
static bool
|
||||
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) {
|
||||
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
decoder->frame = av_frame_alloc();
|
||||
if (!decoder->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx, session)) {
|
||||
av_frame_free(&decoder->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
decoder->ctx = ctx;
|
||||
|
||||
// A video stream must have a session
|
||||
assert(session || ctx->codec_type != AVMEDIA_TYPE_VIDEO);
|
||||
|
||||
if (session) {
|
||||
decoder->session = *session;
|
||||
}
|
||||
|
||||
memset(&decoder->frame_size, 0, sizeof(decoder->frame_size));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -63,6 +71,32 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
// a frame was received
|
||||
|
||||
if (decoder->ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
assert(decoder->frame->width >= 0);
|
||||
assert(decoder->frame->height >= 0);
|
||||
struct sc_size frame_size = {
|
||||
.width = decoder->frame->width,
|
||||
.height = decoder->frame->height,
|
||||
};
|
||||
if (decoder->frame_size.width != frame_size.width
|
||||
|| decoder->frame_size.height != frame_size.height) {
|
||||
// The frame size has changed, check if it matches the session
|
||||
uint32_t sw = decoder->session.video.width;
|
||||
uint32_t sh = decoder->session.video.height;
|
||||
if (frame_size.width != sw || frame_size.height != sh) {
|
||||
LOGW("Unexpected video size: %" PRIu32 "x%" PRIu32
|
||||
" (expected %" PRIu32 "x%" PRIu32 ")",
|
||||
frame_size.width, frame_size.height, sw, sh);
|
||||
|
||||
LOGW("The encoder did not respect the requested size, "
|
||||
"please retry with a lower resolution (-m/--max-size)");
|
||||
}
|
||||
}
|
||||
|
||||
decoder->frame_size = frame_size;
|
||||
}
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
|
||||
decoder->frame);
|
||||
av_frame_unref(decoder->frame);
|
||||
@@ -76,9 +110,17 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) {
|
||||
sc_decoder_push_session(struct sc_decoder *decoder,
|
||||
const struct sc_stream_session *session) {
|
||||
decoder->session = *session;
|
||||
return sc_frame_source_sinks_push_session(&decoder->frame_source, session);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_open(decoder, ctx);
|
||||
return sc_decoder_open(decoder, ctx, session);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -94,6 +136,14 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
||||
return sc_decoder_push(decoder, packet);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_push_session(struct sc_packet_sink *sink,
|
||||
const struct sc_stream_session *session) {
|
||||
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_push_session(decoder, session);
|
||||
}
|
||||
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
decoder->name = name; // statically allocated
|
||||
@@ -103,6 +153,7 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
.open = sc_decoder_packet_sink_open,
|
||||
.close = sc_decoder_packet_sink_close,
|
||||
.push = sc_decoder_packet_sink_push,
|
||||
.push_session = sc_decoder_packet_sink_push_session,
|
||||
};
|
||||
|
||||
decoder->packet_sink.ops = &ops;
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "trait/frame_source.h"
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
struct sc_decoder {
|
||||
struct sc_packet_sink packet_sink; // packet sink trait
|
||||
struct sc_frame_source frame_source; // frame source trait
|
||||
@@ -18,6 +17,9 @@ struct sc_decoder {
|
||||
|
||||
AVCodecContext *ctx;
|
||||
AVFrame *frame;
|
||||
|
||||
struct sc_stream_session session; // only initialized for video stream
|
||||
struct sc_size frame_size;
|
||||
};
|
||||
|
||||
// The name must be statically allocated (e.g. a string literal)
|
||||
|
||||
@@ -2,28 +2,26 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_BUFFERING_NDEBUG // comment to debug
|
||||
|
||||
/** Downcast frame_sink to sc_delay_buffer */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
||||
|
||||
static bool
|
||||
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
dframe->frame = av_frame_alloc();
|
||||
if (!dframe->frame) {
|
||||
sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
|
||||
const AVFrame *frame) {
|
||||
dpacket->type = SC_DELAYED_PACKET_TYPE_FRAME;
|
||||
dpacket->frame = av_frame_alloc();
|
||||
if (!dpacket->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (av_frame_ref(dframe->frame, frame)) {
|
||||
if (av_frame_ref(dpacket->frame, frame)) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&dframe->frame);
|
||||
av_frame_free(&dpacket->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -31,9 +29,18 @@ sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
|
||||
av_frame_unref(dframe->frame);
|
||||
av_frame_free(&dframe->frame);
|
||||
sc_delayed_packet_init_session(struct sc_delayed_packet *dpacket,
|
||||
const struct sc_stream_session *session) {
|
||||
dpacket->type = SC_DELAYED_PACKET_TYPE_SESSION;
|
||||
dpacket->session = *session;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_packet_destroy(struct sc_delayed_packet *dpacket) {
|
||||
if (dpacket->type == SC_DELAYED_PACKET_TYPE_FRAME) {
|
||||
av_frame_unref(dpacket->frame);
|
||||
av_frame_free(&dpacket->frame);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -54,43 +61,52 @@ run_buffering(void *data) {
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
|
||||
struct sc_delayed_packet dpacket = sc_vecdeque_pop(&db->queue);
|
||||
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts);
|
||||
bool ok;
|
||||
if (dpacket.type == SC_DELAYED_PACKET_TYPE_FRAME) {
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_FROM_US(dpacket.frame->pts);
|
||||
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
if (stopped) {
|
||||
sc_delayed_packet_destroy(&dpacket);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
if (stopped) {
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
#endif
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
ok = sc_frame_source_sinks_push(&db->frame_source, dpacket.frame);
|
||||
} else {
|
||||
assert(dpacket.type == SC_DELAYED_PACKET_TYPE_SESSION);
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
ok = sc_frame_source_sinks_push_session(&db->frame_source,
|
||||
&dpacket.session);
|
||||
}
|
||||
|
||||
sc_delayed_packet_destroy(&dpacket);
|
||||
if (!ok) {
|
||||
LOGE("Delayed frame could not be pushed, stopping");
|
||||
LOGE("Delayed packet could not be pushed, stopping");
|
||||
sc_mutex_lock(&db->mutex);
|
||||
// Prevent to push any new frame
|
||||
// Prevent to push any new packet
|
||||
db->stopped = true;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
goto stopped;
|
||||
@@ -102,8 +118,8 @@ stopped:
|
||||
|
||||
// Flush queue
|
||||
while (!sc_vecdeque_is_empty(&db->queue)) {
|
||||
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_frame_destroy(dframe);
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_packet_destroy(dpacket);
|
||||
}
|
||||
|
||||
LOGD("Buffering thread ended");
|
||||
@@ -113,9 +129,11 @@ stopped:
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
bool ok = sc_mutex_init(&db->mutex);
|
||||
if (!ok) {
|
||||
@@ -134,8 +152,9 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
|
||||
sc_clock_init(&db->clock);
|
||||
sc_vecdeque_init(&db->queue);
|
||||
db->stopped = false;
|
||||
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx, session)) {
|
||||
goto error_destroy_wait_cond;
|
||||
}
|
||||
|
||||
@@ -199,24 +218,56 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
|
||||
return sc_frame_source_sinks_push(&db->frame_source, frame);
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe;
|
||||
bool ok = sc_delayed_frame_init(&dframe, frame);
|
||||
if (!ok) {
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
|
||||
if (!dpacket) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef SC_BUFFERING_NDEBUG
|
||||
dframe.push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
ok = sc_vecdeque_push(&db->queue, dframe);
|
||||
bool ok = sc_delayed_packet_init_frame(dpacket, frame);
|
||||
if (!ok) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dpacket->push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_push_session(struct sc_frame_sink *sink,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
|
||||
sc_mutex_lock(&db->mutex);
|
||||
|
||||
if (db->stopped) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
|
||||
if (!dpacket) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_delayed_packet_init_session(dpacket, session);
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dpacket->push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
@@ -238,6 +289,7 @@ sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
|
||||
.open = sc_delay_buffer_frame_sink_open,
|
||||
.close = sc_delay_buffer_frame_sink_close,
|
||||
.push = sc_delay_buffer_frame_sink_push,
|
||||
.push_session = sc_delay_buffer_frame_sink_push_session,
|
||||
};
|
||||
|
||||
db->frame_sink.ops = &ops;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavutil/frame.h>
|
||||
|
||||
#include "clock.h"
|
||||
#include "trait/frame_source.h"
|
||||
@@ -12,17 +13,28 @@
|
||||
#include "util/tick.h"
|
||||
#include "util/vecdeque.h"
|
||||
|
||||
//#define SC_BUFFERING_DEBUG // uncomment to debug
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
struct sc_delayed_frame {
|
||||
AVFrame *frame;
|
||||
#ifndef NDEBUG
|
||||
enum sc_delayed_packet_type {
|
||||
SC_DELAYED_PACKET_TYPE_FRAME,
|
||||
SC_DELAYED_PACKET_TYPE_SESSION,
|
||||
};
|
||||
|
||||
struct sc_delayed_packet {
|
||||
enum sc_delayed_packet_type type;
|
||||
union {
|
||||
AVFrame *frame;
|
||||
struct sc_stream_session session;
|
||||
};
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
sc_tick push_date;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
|
||||
struct sc_delayed_packet_queue SC_VECDEQUE(struct sc_delayed_packet);
|
||||
|
||||
struct sc_delay_buffer {
|
||||
struct sc_frame_source frame_source; // frame source trait
|
||||
@@ -37,7 +49,7 @@ struct sc_delay_buffer {
|
||||
sc_cond wait_cond;
|
||||
|
||||
struct sc_clock clock;
|
||||
struct sc_delayed_frame_queue queue;
|
||||
struct sc_delayed_packet_queue queue;
|
||||
bool stopped;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
#include "demuxer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "decoder.h"
|
||||
#include "events.h"
|
||||
#include "packet_merger.h"
|
||||
#include "recorder.h"
|
||||
#include "util/binary.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#define SC_PACKET_HEADER_SIZE 12
|
||||
|
||||
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
|
||||
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
|
||||
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 62)
|
||||
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 61)
|
||||
|
||||
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
||||
|
||||
@@ -25,7 +22,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) {
|
||||
#define SC_CODEC_ID_H265 UINT32_C(0x68323635) // "h265" in ASCII
|
||||
#define SC_CODEC_ID_AV1 UINT32_C(0x00617631) // "av1" in ASCII
|
||||
#define SC_CODEC_ID_OPUS UINT32_C(0x6f707573) // "opus" in ASCII
|
||||
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac in ASCII"
|
||||
#define SC_CODEC_ID_AAC UINT32_C(0x00616163) // "aac" in ASCII
|
||||
#define SC_CODEC_ID_FLAC UINT32_C(0x666c6163) // "flac" in ASCII
|
||||
#define SC_CODEC_ID_RAW UINT32_C(0x00726177) // "raw" in ASCII
|
||||
switch (codec_id) {
|
||||
case SC_CODEC_ID_H264:
|
||||
@@ -43,6 +41,8 @@ sc_demuxer_to_avcodec_id(uint32_t codec_id) {
|
||||
return AV_CODEC_ID_OPUS;
|
||||
case SC_CODEC_ID_AAC:
|
||||
return AV_CODEC_ID_AAC;
|
||||
case SC_CODEC_ID_FLAC:
|
||||
return AV_CODEC_ID_FLAC;
|
||||
case SC_CODEC_ID_RAW:
|
||||
return AV_CODEC_ID_PCM_S16LE;
|
||||
default:
|
||||
@@ -63,48 +63,75 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
|
||||
uint32_t *height) {
|
||||
uint8_t data[8];
|
||||
ssize_t r = net_recv_all(demuxer->socket, data, 8);
|
||||
if (r < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*width = sc_read32be(data);
|
||||
*height = sc_read32be(data + 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
static inline bool
|
||||
sc_demuxer_recv_header(struct sc_demuxer *demuxer,
|
||||
uint8_t buf[static SC_PACKET_HEADER_SIZE]) {
|
||||
// The video and audio streams contain a sequence of raw packets (as
|
||||
// provided by MediaCodec), each prefixed with a "meta" header.
|
||||
//
|
||||
// The "meta" header length is 12 bytes:
|
||||
// The "meta" header length is 12 bytes.
|
||||
//
|
||||
//
|
||||
// If the MSB is 1, then it is a session packet (for a video stream only),
|
||||
// which only contains a 12-byte header:
|
||||
//
|
||||
// byte 0 byte 1 byte 2 byte 3
|
||||
// 10000000 00000000 00000000 00000000
|
||||
// ^<-------------------------------->
|
||||
// | padding
|
||||
// `- session packet flag
|
||||
//
|
||||
// byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
|
||||
// ........ ........ ........ ........ ........ ........ ........ ........
|
||||
// <---------------------------------> <--------------------------------->
|
||||
// video width video height
|
||||
//
|
||||
//
|
||||
// If the MSB is 0, then it is a media packet, comprised of a 12-byte header
|
||||
// followed by <packet_size> bytes containing the packet/frame:
|
||||
//
|
||||
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
||||
// <-------------> <-----> <-----------------------------...
|
||||
// PTS packet raw packet
|
||||
// size
|
||||
//
|
||||
// It is followed by <packet_size> bytes containing the packet/frame.
|
||||
//
|
||||
// The most significant bits of the PTS are used for packet flags:
|
||||
//
|
||||
// byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0
|
||||
// CK...... ........ ........ ........ ........ ........ ........ ........
|
||||
// ^^<------------------------------------------------------------------->
|
||||
// || PTS
|
||||
// | `- key frame
|
||||
// `-- config packet
|
||||
// byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
|
||||
// 0CK..... ........ ........ ........ ........ ........ ........ ........
|
||||
// ^^^<------------------------------------------------------------------>
|
||||
// ||| PTS
|
||||
// || `- key frame
|
||||
// | `-- config packet
|
||||
// `--- media packet flag
|
||||
//
|
||||
// byte 8 byte 9 byte 10 byte 11
|
||||
// ........ ........ ........ ........ ........ ........ . . .
|
||||
// <---------------------------------> <---------------- . . .
|
||||
// packet size raw packet
|
||||
//
|
||||
ssize_t r = net_recv_all(demuxer->socket, buf, SC_PACKET_HEADER_SIZE);
|
||||
assert(r <= SC_PACKET_HEADER_SIZE);
|
||||
return r == SC_PACKET_HEADER_SIZE;
|
||||
}
|
||||
|
||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
|
||||
if (r < SC_PACKET_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
sc_demuxer_is_session(const uint8_t *header) {
|
||||
return header[0] & 0x80;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_demuxer_parse_session(const uint8_t *header,
|
||||
struct sc_stream_session *session) {
|
||||
assert(sc_demuxer_is_session(header));
|
||||
session->video.width = sc_read32be(&header[4]);
|
||||
session->video.height = sc_read32be(&header[8]);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
|
||||
AVPacket *packet) {
|
||||
assert(!sc_demuxer_is_session(header));
|
||||
uint64_t pts_flags = sc_read64be(header);
|
||||
uint32_t len = sc_read32be(&header[8]);
|
||||
assert(len);
|
||||
@@ -114,7 +141,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r = net_recv_all(demuxer->socket, packet->data, len);
|
||||
ssize_t r = net_recv_all(demuxer->socket, packet->data, len);
|
||||
if (r < 0 || ((uint32_t) r) < len) {
|
||||
av_packet_unref(packet);
|
||||
return false;
|
||||
@@ -187,17 +214,28 @@ run_demuxer(void *data) {
|
||||
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||
struct sc_stream_session session_data;
|
||||
|
||||
struct sc_stream_session *session = NULL;
|
||||
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
|
||||
bool ok = sc_demuxer_recv_header(demuxer, header);
|
||||
if (!ok) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
codec_ctx->width = width;
|
||||
codec_ctx->height = height;
|
||||
if (!sc_demuxer_is_session(header)) {
|
||||
LOGE("Unexpected packet (not a session header)");
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
session = &session_data;
|
||||
sc_demuxer_parse_session(header, session);
|
||||
|
||||
codec_ctx->width = session_data.video.width;
|
||||
codec_ctx->height = session_data.video.height;
|
||||
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
} else {
|
||||
// Hardcoded audio properties
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
@@ -207,6 +245,11 @@ run_demuxer(void *data) {
|
||||
codec_ctx->channels = 2;
|
||||
#endif
|
||||
codec_ctx->sample_rate = 48000;
|
||||
|
||||
if (raw_codec_id == SC_CODEC_ID_FLAC) {
|
||||
// The sample_fmt is not set by the FLAC decoder
|
||||
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
}
|
||||
}
|
||||
|
||||
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
||||
@@ -214,13 +257,15 @@ run_demuxer(void *data) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx,
|
||||
session)) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
// Config packets must be merged with the next non-config packet only for
|
||||
// video streams
|
||||
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO;
|
||||
// H.26x
|
||||
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|
||||
|| raw_codec_id == SC_CODEC_ID_H265;
|
||||
|
||||
struct sc_packet_merger merger;
|
||||
|
||||
@@ -235,27 +280,39 @@ run_demuxer(void *data) {
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
||||
bool ok = sc_demuxer_recv_header(demuxer, header);
|
||||
if (!ok) {
|
||||
// end of stream
|
||||
status = SC_DEMUXER_STATUS_EOS;
|
||||
break;
|
||||
}
|
||||
|
||||
if (must_merge_config_packet) {
|
||||
// Prepend any config packet to the next media packet
|
||||
ok = sc_packet_merger_merge(&merger, packet);
|
||||
if (sc_demuxer_is_session(header)) {
|
||||
sc_demuxer_parse_session(header, &session_data);
|
||||
ok = sc_packet_source_sinks_push_session(&demuxer->packet_source,
|
||||
&session_data);
|
||||
if (!ok) {
|
||||
av_packet_unref(packet);
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sc_demuxer_recv_packet(demuxer, header, packet);
|
||||
|
||||
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
if (must_merge_config_packet) {
|
||||
// Prepend any config packet to the next media packet
|
||||
ok = sc_packet_merger_merge(&merger, packet);
|
||||
if (!ok) {
|
||||
av_packet_unref(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +326,6 @@ run_demuxer(void *data) {
|
||||
finally_close_sinks:
|
||||
sc_packet_source_sinks_close(&demuxer->packet_source);
|
||||
finally_free_context:
|
||||
// This also calls avcodec_close() internally
|
||||
avcodec_free_context(&codec_ctx);
|
||||
end:
|
||||
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "trait/packet_source.h"
|
||||
#include "trait/packet_sink.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
|
||||
@@ -8,19 +8,22 @@
|
||||
#include "util/log.h"
|
||||
|
||||
ssize_t
|
||||
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
struct device_msg *msg) {
|
||||
if (len < 5) {
|
||||
// at least type + empty string length
|
||||
return 0; // not available
|
||||
sc_device_msg_deserialize(const uint8_t *buf, size_t len,
|
||||
struct sc_device_msg *msg) {
|
||||
if (!len) {
|
||||
return 0; // no message
|
||||
}
|
||||
|
||||
msg->type = buf[0];
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||
if (len < 5) {
|
||||
// at least type + empty string length
|
||||
return 0; // no complete message
|
||||
}
|
||||
size_t clipboard_len = sc_read32be(&buf[1]);
|
||||
if (clipboard_len > len - 5) {
|
||||
return 0; // not available
|
||||
return 0; // no complete message
|
||||
}
|
||||
char *text = malloc(clipboard_len + 1);
|
||||
if (!text) {
|
||||
@@ -36,10 +39,38 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
return 5 + clipboard_len;
|
||||
}
|
||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
|
||||
if (len < 9) {
|
||||
return 0; // no complete message
|
||||
}
|
||||
uint64_t sequence = sc_read64be(&buf[1]);
|
||||
msg->ack_clipboard.sequence = sequence;
|
||||
return 9;
|
||||
}
|
||||
case DEVICE_MSG_TYPE_UHID_OUTPUT: {
|
||||
if (len < 5) {
|
||||
// at least id + size
|
||||
return 0; // not available
|
||||
}
|
||||
uint16_t id = sc_read16be(&buf[1]);
|
||||
size_t size = sc_read16be(&buf[3]);
|
||||
if (size > len - 5) {
|
||||
return 0; // not available
|
||||
}
|
||||
uint8_t *data = malloc(size);
|
||||
if (!data) {
|
||||
LOG_OOM();
|
||||
return -1;
|
||||
}
|
||||
if (size) {
|
||||
memcpy(data, &buf[5], size);
|
||||
}
|
||||
|
||||
msg->uhid_output.id = id;
|
||||
msg->uhid_output.size = size;
|
||||
msg->uhid_output.data = data;
|
||||
|
||||
return 5 + size;
|
||||
}
|
||||
default:
|
||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||
return -1; // error, we cannot recover
|
||||
@@ -47,8 +78,16 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
}
|
||||
|
||||
void
|
||||
device_msg_destroy(struct device_msg *msg) {
|
||||
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
|
||||
free(msg->clipboard.text);
|
||||
sc_device_msg_destroy(struct sc_device_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD:
|
||||
free(msg->clipboard.text);
|
||||
break;
|
||||
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
||||
free(msg->uhid_output.data);
|
||||
break;
|
||||
default:
|
||||
// nothing to do
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,22 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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)
|
||||
|
||||
enum device_msg_type {
|
||||
enum sc_device_msg_type {
|
||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
|
||||
DEVICE_MSG_TYPE_UHID_OUTPUT,
|
||||
};
|
||||
|
||||
struct device_msg {
|
||||
enum device_msg_type type;
|
||||
struct sc_device_msg {
|
||||
enum sc_device_msg_type type;
|
||||
union {
|
||||
struct {
|
||||
char *text; // owned, to be freed by free()
|
||||
@@ -25,15 +26,20 @@ struct device_msg {
|
||||
struct {
|
||||
uint64_t sequence;
|
||||
} ack_clipboard;
|
||||
struct {
|
||||
uint16_t id;
|
||||
uint16_t size;
|
||||
uint8_t *data; // owned, to be freed by free()
|
||||
} uhid_output;
|
||||
};
|
||||
};
|
||||
|
||||
// return the number of bytes consumed (0 for no msg available, -1 on error)
|
||||
ssize_t
|
||||
device_msg_deserialize(const unsigned char *buf, size_t len,
|
||||
struct device_msg *msg);
|
||||
sc_device_msg_deserialize(const uint8_t *buf, size_t len,
|
||||
struct sc_device_msg *msg);
|
||||
|
||||
void
|
||||
device_msg_destroy(struct device_msg *msg);
|
||||
sc_device_msg_destroy(struct sc_device_msg *msg);
|
||||
|
||||
#endif
|
||||
|
||||
89
app/src/disconnect.c
Normal file
89
app/src/disconnect.c
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "disconnect.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "icon.h"
|
||||
#include "util/log.h"
|
||||
|
||||
static int
|
||||
run(void *userdata) {
|
||||
struct sc_disconnect *d = userdata;
|
||||
|
||||
SDL_Surface *icon = sc_icon_load(SC_ICON_FILENAME_DISCONNECTED);
|
||||
if (icon) {
|
||||
d->cbs->on_icon_loaded(d, icon, d->cbs_userdata);
|
||||
} else {
|
||||
LOGE("Could not load disconnected icon");
|
||||
}
|
||||
|
||||
if (d->deadline != SC_TICK_NONE) {
|
||||
sc_mutex_lock(&d->mutex);
|
||||
bool timed_out = false;
|
||||
while (!d->interrupted && !timed_out) {
|
||||
timed_out = !sc_cond_timedwait(&d->cond, &d->mutex, d->deadline);
|
||||
}
|
||||
sc_mutex_unlock(&d->mutex);
|
||||
|
||||
if (!d->interrupted) {
|
||||
d->cbs->on_timeout(d, d->cbs_userdata);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
|
||||
const struct sc_disconnect_callbacks *cbs,
|
||||
void *cbs_userdata) {
|
||||
bool ok = sc_mutex_init(&d->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&d->cond);
|
||||
if (!ok) {
|
||||
goto error_destroy_mutex;
|
||||
}
|
||||
|
||||
ok = sc_thread_create(&d->thread, run, "scrcpy-dis", d);
|
||||
if (!ok) {
|
||||
goto error_destroy_cond;
|
||||
}
|
||||
|
||||
d->deadline = deadline;
|
||||
d->interrupted = false;
|
||||
|
||||
assert(cbs && cbs->on_icon_loaded && cbs->on_timeout);
|
||||
d->cbs = cbs;
|
||||
d->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_mutex:
|
||||
sc_mutex_destroy(&d->mutex);
|
||||
error_destroy_cond:
|
||||
sc_cond_destroy(&d->cond);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
sc_disconnect_interrupt(struct sc_disconnect *d) {
|
||||
sc_mutex_lock(&d->mutex);
|
||||
d->interrupted = true;
|
||||
sc_mutex_unlock(&d->mutex);
|
||||
// wake up blocking wait
|
||||
sc_cond_signal(&d->cond);
|
||||
}
|
||||
|
||||
void
|
||||
sc_disconnect_join(struct sc_disconnect *d) {
|
||||
sc_thread_join(&d->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sc_disconnect_destroy(struct sc_disconnect *d) {
|
||||
sc_cond_destroy(&d->cond);
|
||||
sc_mutex_destroy(&d->mutex);
|
||||
}
|
||||
47
app/src/disconnect.h
Normal file
47
app/src/disconnect.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef SC_DISCONNECT
|
||||
#define SC_DISCONNECT
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "SDL3/SDL_surface.h"
|
||||
#include "util/tick.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
// Tool to handle loading the icon and signal timeout when the device is
|
||||
// unexpectedly disconnected
|
||||
struct sc_disconnect {
|
||||
sc_tick deadline;
|
||||
|
||||
struct sc_thread thread;
|
||||
struct sc_mutex mutex;
|
||||
struct sc_cond cond;
|
||||
bool interrupted;
|
||||
|
||||
const struct sc_disconnect_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_disconnect_callbacks {
|
||||
// Called when the disconnected icon is loaded
|
||||
void (*on_icon_loaded)(struct sc_disconnect *d, SDL_Surface *icon,
|
||||
void *userdata);
|
||||
|
||||
// Called when the timeout expired (the scrcpy window must be closed)
|
||||
void (*on_timeout)(struct sc_disconnect *d, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_disconnect_start(struct sc_disconnect *d, sc_tick deadline,
|
||||
const struct sc_disconnect_callbacks *cbs,
|
||||
void *cbs_userdata);
|
||||
|
||||
void
|
||||
sc_disconnect_interrupt(struct sc_disconnect *d);
|
||||
|
||||
void
|
||||
sc_disconnect_join(struct sc_disconnect *d);
|
||||
|
||||
void
|
||||
sc_disconnect_destroy(struct sc_disconnect *d);
|
||||
|
||||
#endif
|
||||
@@ -1,285 +0,0 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
|
||||
display->renderer =
|
||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (!display->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_RendererInfo renderer_info;
|
||||
int r = SDL_GetRendererInfo(display->renderer, &renderer_info);
|
||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
display->mipmaps = false;
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
if (!display->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
display->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer");
|
||||
}
|
||||
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display) {
|
||||
if (display->pending.frame) {
|
||||
av_frame_free(&display->pending.frame);
|
||||
}
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DeleteContext(display->gl_context);
|
||||
#endif
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_display_create_texture(struct sc_display *display,
|
||||
struct sc_size size) {
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
size.width, size.height);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
SDL_GL_BindTexture(texture, NULL, NULL);
|
||||
|
||||
// Enable trilinear filtering for downscaling
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR_MIPMAP_LINEAR);
|
||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||
|
||||
SDL_GL_UnbindTexture(texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
|
||||
assert(!display->texture);
|
||||
display->pending.size = size;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
if (!display->pending.frame) {
|
||||
display->pending.frame = av_frame_alloc();
|
||||
if (!display->pending.frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int r = av_frame_ref(display->pending.frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_apply_pending(struct sc_display *display) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
|
||||
assert(!display->texture);
|
||||
display->texture =
|
||||
sc_display_create_texture(display, display->pending.size);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
|
||||
}
|
||||
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
||||
assert(display->pending.frame);
|
||||
bool ok = sc_display_update_texture(display, display->pending.frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_texture_size_internal(struct sc_display *display,
|
||||
struct sc_size size) {
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
|
||||
display->texture = sc_display_create_texture(display, size);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
||||
bool ok = sc_display_set_texture_size_internal(display, size);
|
||||
if (!ok) {
|
||||
sc_display_set_pending_size(display, size);
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame) {
|
||||
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (ret) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
SDL_GL_BindTexture(display->texture, NULL, NULL);
|
||||
display->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||
SDL_GL_UnbindTexture(display->texture);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
bool ok = sc_display_update_texture_internal(display, frame);
|
||||
if (!ok) {
|
||||
ok = sc_display_set_pending_frame(display, frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pending frame");
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
unsigned rotation) {
|
||||
SDL_RenderClear(display->renderer);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
if (!ok) {
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = display->texture;
|
||||
|
||||
if (rotation == 0) {
|
||||
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
|
||||
if (ret) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
} else {
|
||||
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
||||
// counterclockwise (to be consistent with --lock-video-orientation)
|
||||
int cw_rotation = (4 - rotation) % 4;
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_Rect *dstrect = NULL;
|
||||
SDL_Rect rect;
|
||||
if (rotation & 1) {
|
||||
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
|
||||
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
|
||||
rect.w = geometry->h;
|
||||
rect.h = geometry->w;
|
||||
dstrect = ▭
|
||||
} else {
|
||||
assert(rotation == 2);
|
||||
dstrect = geometry;
|
||||
}
|
||||
|
||||
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, 0);
|
||||
if (ret) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(display->renderer);
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_display {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext *gl_context;
|
||||
#endif
|
||||
|
||||
bool mipmaps;
|
||||
|
||||
struct {
|
||||
#define SC_DISPLAY_PENDING_FLAG_SIZE 1
|
||||
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
|
||||
int8_t flags;
|
||||
struct sc_size size;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
SC_DISPLAY_RESULT_OK,
|
||||
SC_DISPLAY_RESULT_PENDING,
|
||||
SC_DISPLAY_RESULT_ERROR,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_set_texture_size(struct sc_display *display, struct sc_size size);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
unsigned rotation);
|
||||
|
||||
#endif
|
||||
60
app/src/events.c
Normal file
60
app/src/events.c
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "events.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
bool
|
||||
sc_push_event_impl(uint32_t type, void* ptr, const char *name) {
|
||||
SDL_Event event = {
|
||||
.user = {
|
||||
.type = type,
|
||||
.data1 = ptr,
|
||||
}
|
||||
};
|
||||
bool ok = SDL_PushEvent(&event);
|
||||
if (!ok) {
|
||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
|
||||
SDL_Event event = {
|
||||
.user = {
|
||||
.type = SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||
.data1 = run,
|
||||
.data2 = userdata,
|
||||
},
|
||||
};
|
||||
bool ok = SDL_PushEvent(&event);
|
||||
if (!ok) {
|
||||
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SDLCALL
|
||||
task_event_filter(void *userdata, SDL_Event *event) {
|
||||
(void) userdata;
|
||||
|
||||
if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) {
|
||||
// Reject this event type from now on
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_reject_new_runnables(void) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
SDL_SetEventFilter(task_event_filter, NULL);
|
||||
}
|
||||
@@ -1,9 +1,39 @@
|
||||
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
|
||||
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
|
||||
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
||||
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
||||
#ifndef SC_EVENTS_H
|
||||
#define SC_EVENTS_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
enum {
|
||||
SC_EVENT_NEW_FRAME = SDL_EVENT_USER,
|
||||
SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||
SC_EVENT_DEVICE_DISCONNECTED,
|
||||
SC_EVENT_SERVER_CONNECTION_FAILED,
|
||||
SC_EVENT_SERVER_CONNECTED,
|
||||
SC_EVENT_DEMUXER_ERROR,
|
||||
SC_EVENT_RECORDER_ERROR,
|
||||
SC_EVENT_TIME_LIMIT_REACHED,
|
||||
SC_EVENT_CONTROLLER_ERROR,
|
||||
SC_EVENT_AOA_OPEN_ERROR,
|
||||
SC_EVENT_DISCONNECTED_ICON_LOADED,
|
||||
SC_EVENT_DISCONNECTED_TIMEOUT,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_push_event_impl(uint32_t type, void* ptr, const char *name);
|
||||
|
||||
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, NULL, # TYPE)
|
||||
#define sc_push_event_with_data(TYPE, PTR) sc_push_event_impl(TYPE, PTR, # TYPE)
|
||||
|
||||
typedef void (*sc_runnable_fn)(void *userdata);
|
||||
|
||||
bool
|
||||
sc_post_to_main_thread(sc_runnable_fn run, void *userdata);
|
||||
|
||||
void
|
||||
sc_reject_new_runnables(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "file_pusher.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "adb/adb.h"
|
||||
#include "util/log.h"
|
||||
#include "util/process_intr.h"
|
||||
|
||||
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "fps_counter.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/thread.h"
|
||||
#include "util/tick.h"
|
||||
|
||||
struct sc_fps_counter {
|
||||
sc_thread thread;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "frame_buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <libavutil/frame.h>
|
||||
|
||||
#include "util/thread.h"
|
||||
|
||||
|
||||
27
app/src/hid/hid_event.h
Normal file
27
app/src/hid/hid_event.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef SC_HID_EVENT_H
|
||||
#define SC_HID_EVENT_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define SC_HID_MAX_SIZE 15
|
||||
|
||||
struct sc_hid_input {
|
||||
uint16_t hid_id;
|
||||
uint8_t data[SC_HID_MAX_SIZE];
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
struct sc_hid_open {
|
||||
uint16_t hid_id;
|
||||
const uint8_t *report_desc; // pointer to static memory
|
||||
size_t report_desc_size;
|
||||
};
|
||||
|
||||
struct sc_hid_close {
|
||||
uint16_t hid_id;
|
||||
};
|
||||
|
||||
#endif
|
||||
454
app/src/hid/hid_gamepad.c
Normal file
454
app/src/hid/hid_gamepad.c
Normal file
@@ -0,0 +1,454 @@
|
||||
#include "hid_gamepad.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "util/binary.h"
|
||||
#include "util/log.h"
|
||||
|
||||
// 2x2 bytes for left stick (X, Y)
|
||||
// 2x2 bytes for right stick (Z, Rz)
|
||||
// 2x2 bytes for L2/R2 triggers
|
||||
// 2 bytes for buttons + padding,
|
||||
// 1 byte for hat switch (dpad) + padding
|
||||
#define SC_HID_GAMEPAD_EVENT_SIZE 15
|
||||
|
||||
// The ->buttons field stores the state for all buttons, but only some of them
|
||||
// (the 16 LSB) must be transmitted "as is". The DPAD (hat switch) buttons are
|
||||
// stored locally in the MSB of this field, but not transmitted as is: they are
|
||||
// transformed to generate another specific byte.
|
||||
#define SC_HID_BUTTONS_MASK 0xFFFF
|
||||
|
||||
// outside SC_HID_BUTTONS_MASK
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_UP UINT32_C(0x10000)
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN UINT32_C(0x20000)
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT UINT32_C(0x40000)
|
||||
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT UINT32_C(0x80000)
|
||||
|
||||
/**
|
||||
* Gamepad descriptor manually crafted to transmit the input reports.
|
||||
*
|
||||
* The HID specification is available here:
|
||||
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||
*
|
||||
* The HID Usage Tables is also useful:
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
*/
|
||||
static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Gamepad)
|
||||
0x09, 0x05,
|
||||
|
||||
// Collection (Application)
|
||||
0xA1, 0x01,
|
||||
|
||||
// Collection (Physical)
|
||||
0xA1, 0x00,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (X) Left stick x
|
||||
0x09, 0x30,
|
||||
// Usage (Y) Left stick y
|
||||
0x09, 0x31,
|
||||
// Usage (Rx) Right stick x
|
||||
0x09, 0x33,
|
||||
// Usage (Ry) Right stick y
|
||||
0x09, 0x34,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (65535)
|
||||
// Cannot use 26 FF FF because 0xFFFF is interpreted as signed 16-bit
|
||||
0x27, 0xFF, 0xFF, 0x00, 0x00, // little-endian
|
||||
// Report Size (16)
|
||||
0x75, 0x10,
|
||||
// Report Count (4)
|
||||
0x95, 0x04,
|
||||
// Input (Data, Variable, Absolute): 4x2 bytes (X, Y, Z, Rz)
|
||||
0x81, 0x02,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Z)
|
||||
0x09, 0x32,
|
||||
// Usage (Rz)
|
||||
0x09, 0x35,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (32767)
|
||||
0x26, 0xFF, 0x7F,
|
||||
// Report Size (16)
|
||||
0x75, 0x10,
|
||||
// Report Count (2)
|
||||
0x95, 0x02,
|
||||
// Input (Data, Variable, Absolute): 2x2 bytes (L2, R2)
|
||||
0x81, 0x02,
|
||||
|
||||
// Usage Page (Buttons)
|
||||
0x05, 0x09,
|
||||
// Usage Minimum (1)
|
||||
0x19, 0x01,
|
||||
// Usage Maximum (16)
|
||||
0x29, 0x10,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (1)
|
||||
0x25, 0x01,
|
||||
// Report Count (16)
|
||||
0x95, 0x10,
|
||||
// Report Size (1)
|
||||
0x75, 0x01,
|
||||
// Input (Data, Variable, Absolute): 16 buttons bits
|
||||
0x81, 0x02,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Hat switch)
|
||||
0x09, 0x39,
|
||||
// Logical Minimum (1)
|
||||
0x15, 0x01,
|
||||
// Logical Maximum (8)
|
||||
0x25, 0x08,
|
||||
// Report Size (4)
|
||||
0x75, 0x04,
|
||||
// Report Count (1)
|
||||
0x95, 0x01,
|
||||
// Input (Data, Variable, Null State): 4-bit value
|
||||
0x81, 0x42,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
};
|
||||
|
||||
/**
|
||||
* A gamepad HID input report is 15 bytes long:
|
||||
* - bytes 0-3: left stick state
|
||||
* - bytes 4-7: right stick state
|
||||
* - bytes 8-11: L2/R2 triggers state
|
||||
* - bytes 12-13: buttons state
|
||||
* - bytes 14: hat switch position (dpad)
|
||||
*
|
||||
* +---------------+
|
||||
* byte 0: |. . . . . . . .|
|
||||
* | | left stick x (0-65535, little-endian)
|
||||
* byte 1: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 2: |. . . . . . . .|
|
||||
* | | left stick y (0-65535, little-endian)
|
||||
* byte 3: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 4: |. . . . . . . .|
|
||||
* | | right stick x (0-65535, little-endian)
|
||||
* byte 5: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 6: |. . . . . . . .|
|
||||
* | | right stick y (0-65535, little-endian)
|
||||
* byte 7: |. . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 8: |. . . . . . . .|
|
||||
* | | L2 trigger (0-32767, little-endian)
|
||||
* byte 9: |0 . . . . . . .|
|
||||
* +---------------+
|
||||
* byte 10: |. . . . . . . .|
|
||||
* | | R2 trigger (0-32767, little-endian)
|
||||
* byte 11: |0 . . . . . . .|
|
||||
* +---------------+
|
||||
*
|
||||
* ,--------------- SC_GAMEPAD_BUTTON_RIGHT_SHOULDER
|
||||
* | ,------------- SC_GAMEPAD_BUTTON_LEFT_SHOULDER
|
||||
* | |
|
||||
* | | ,--------- SC_GAMEPAD_BUTTON_NORTH
|
||||
* | | | ,------- SC_GAMEPAD_BUTTON_WEST
|
||||
* | | | |
|
||||
* | | | | ,--- SC_GAMEPAD_BUTTON_EAST
|
||||
* | | | | | ,- SC_GAMEPAD_BUTTON_SOUTH
|
||||
* v v v v v v
|
||||
* +---------------+
|
||||
* byte 12: |. . 0 . . 0 . .|
|
||||
* | | Buttons (16-bit little-endian)
|
||||
* byte 13: |0 . . . . . 0 0|
|
||||
* +---------------+
|
||||
* ^ ^ ^ ^ ^
|
||||
* | | | | |
|
||||
* | | | | |
|
||||
* | | | | `----- SC_GAMEPAD_BUTTON_BACK
|
||||
* | | | `------- SC_GAMEPAD_BUTTON_START
|
||||
* | | `--------- SC_GAMEPAD_BUTTON_GUIDE
|
||||
* | `----------- SC_GAMEPAD_BUTTON_LEFT_STICK
|
||||
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
||||
*
|
||||
* +---------------+
|
||||
* byte 14: |0 0 0 0 . . . .| hat switch (dpad) position (0-8)
|
||||
* +---------------+
|
||||
* 9 possible positions and their values:
|
||||
* 8 1 2
|
||||
* 7 0 3
|
||||
* 6 5 4
|
||||
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
||||
*/
|
||||
|
||||
// [-32768 to 32767] -> [0 to 65535]
|
||||
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
||||
|
||||
static void
|
||||
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
||||
uint32_t gamepad_id) {
|
||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||
slot->gamepad_id = gamepad_id;
|
||||
slot->buttons = 0;
|
||||
slot->axis_left_x = AXIS_RESCALE(0);
|
||||
slot->axis_left_y = AXIS_RESCALE(0);
|
||||
slot->axis_right_x = AXIS_RESCALE(0);
|
||||
slot->axis_right_y = AXIS_RESCALE(0);
|
||||
slot->axis_left_trigger = 0;
|
||||
slot->axis_right_trigger = 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sc_hid_gamepad_slot_find(struct sc_hid_gamepad *hid, uint32_t gamepad_id) {
|
||||
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
|
||||
if (gamepad_id == hid->slots[i].gamepad_id) {
|
||||
// found
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_gamepad_init(struct sc_hid_gamepad *hid) {
|
||||
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
|
||||
hid->slots[i].gamepad_id = SC_GAMEPAD_ID_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t
|
||||
sc_hid_gamepad_slot_get_id(size_t slot_idx) {
|
||||
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||
return SC_HID_ID_GAMEPAD_FIRST + slot_idx;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_open *hid_open,
|
||||
uint32_t gamepad_id) {
|
||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, SC_GAMEPAD_ID_INVALID);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("No gamepad slot available for new gamepad %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
hid_open->hid_id = hid_id;
|
||||
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_close *hid_close,
|
||||
uint32_t gamepad_id) {
|
||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("Unknown gamepad removed %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
hid->slots[slot_idx].gamepad_id = SC_GAMEPAD_ID_INVALID;
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
hid_close->hid_id = hid_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
sc_hid_gamepad_get_dpad_value(uint32_t buttons) {
|
||||
// Value depending on direction:
|
||||
// 8 1 2
|
||||
// 7 0 3
|
||||
// 6 5 4
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_UP) {
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||
return 8;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN) {
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||
return 6;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||
return 4;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||
return 7;
|
||||
}
|
||||
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_hid_gamepad_event_from_slot(uint16_t hid_id,
|
||||
const struct sc_hid_gamepad_slot *slot,
|
||||
struct sc_hid_input *hid_input) {
|
||||
hid_input->hid_id = hid_id;
|
||||
hid_input->size = SC_HID_GAMEPAD_EVENT_SIZE;
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
// Values must be written in little-endian
|
||||
sc_write16le(data, slot->axis_left_x);
|
||||
sc_write16le(data + 2, slot->axis_left_y);
|
||||
sc_write16le(data + 4, slot->axis_right_x);
|
||||
sc_write16le(data + 6, slot->axis_right_y);
|
||||
sc_write16le(data + 8, slot->axis_left_trigger);
|
||||
sc_write16le(data + 10, slot->axis_right_trigger);
|
||||
sc_write16le(data + 12, slot->buttons & SC_HID_BUTTONS_MASK);
|
||||
data[14] = sc_hid_gamepad_get_dpad_value(slot->buttons);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
sc_hid_gamepad_get_button_id(enum sc_gamepad_button button) {
|
||||
switch (button) {
|
||||
case SC_GAMEPAD_BUTTON_SOUTH:
|
||||
return 0x0001;
|
||||
case SC_GAMEPAD_BUTTON_EAST:
|
||||
return 0x0002;
|
||||
case SC_GAMEPAD_BUTTON_WEST:
|
||||
return 0x0008;
|
||||
case SC_GAMEPAD_BUTTON_NORTH:
|
||||
return 0x0010;
|
||||
case SC_GAMEPAD_BUTTON_BACK:
|
||||
return 0x0400;
|
||||
case SC_GAMEPAD_BUTTON_GUIDE:
|
||||
return 0x1000;
|
||||
case SC_GAMEPAD_BUTTON_START:
|
||||
return 0x0800;
|
||||
case SC_GAMEPAD_BUTTON_LEFT_STICK:
|
||||
return 0x2000;
|
||||
case SC_GAMEPAD_BUTTON_RIGHT_STICK:
|
||||
return 0x4000;
|
||||
case SC_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return 0x0040;
|
||||
case SC_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return 0x0080;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_UP;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT;
|
||||
case SC_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT;
|
||||
default:
|
||||
// unknown button, ignore
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_button_event *event) {
|
||||
if ((event->button < 0) || (event->button > 15)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t gamepad_id = event->gamepad_id;
|
||||
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("Axis event for unknown gamepad %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||
|
||||
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||
|
||||
uint32_t button = sc_hid_gamepad_get_button_id(event->button);
|
||||
if (!button) {
|
||||
// unknown button, ignore
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->action == SC_ACTION_DOWN) {
|
||||
slot->buttons |= button;
|
||||
} else {
|
||||
assert(event->action == SC_ACTION_UP);
|
||||
slot->buttons &= ~button;
|
||||
}
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_axis_event *event) {
|
||||
uint32_t gamepad_id = event->gamepad_id;
|
||||
|
||||
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||
if (slot_idx == -1) {
|
||||
LOGW("Button event for unknown gamepad %" PRIu32, gamepad_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||
|
||||
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||
|
||||
switch (event->axis) {
|
||||
case SC_GAMEPAD_AXIS_LEFTX:
|
||||
slot->axis_left_x = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_LEFTY:
|
||||
slot->axis_left_y = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_RIGHTX:
|
||||
slot->axis_right_x = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_RIGHTY:
|
||||
slot->axis_right_y = AXIS_RESCALE(event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_LEFT_TRIGGER:
|
||||
// Trigger is always positive between 0 and 32767
|
||||
slot->axis_left_trigger = MAX(0, event->value);
|
||||
break;
|
||||
case SC_GAMEPAD_AXIS_RIGHT_TRIGGER:
|
||||
// Trigger is always positive between 0 and 32767
|
||||
slot->axis_right_trigger = MAX(0, event->value);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
|
||||
|
||||
return true;
|
||||
}
|
||||
54
app/src/hid/hid_gamepad.h
Normal file
54
app/src/hid/hid_gamepad.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef SC_HID_GAMEPAD_H
|
||||
#define SC_HID_GAMEPAD_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
#define SC_MAX_GAMEPADS 8
|
||||
#define SC_HID_ID_GAMEPAD_FIRST 3
|
||||
#define SC_HID_ID_GAMEPAD_LAST (SC_HID_ID_GAMEPAD_FIRST + SC_MAX_GAMEPADS - 1)
|
||||
|
||||
struct sc_hid_gamepad_slot {
|
||||
uint32_t gamepad_id;
|
||||
uint32_t buttons;
|
||||
uint16_t axis_left_x;
|
||||
uint16_t axis_left_y;
|
||||
uint16_t axis_right_x;
|
||||
uint16_t axis_right_y;
|
||||
uint16_t axis_left_trigger;
|
||||
uint16_t axis_right_trigger;
|
||||
};
|
||||
|
||||
struct sc_hid_gamepad {
|
||||
struct sc_hid_gamepad_slot slots[SC_MAX_GAMEPADS];
|
||||
};
|
||||
|
||||
void
|
||||
sc_hid_gamepad_init(struct sc_hid_gamepad *hid);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_open *hid_open,
|
||||
uint32_t gamepad_id);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_close *hid_close,
|
||||
uint32_t gamepad_id);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_button_event *event);
|
||||
|
||||
bool
|
||||
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_gamepad_axis_event *event);
|
||||
|
||||
#endif
|
||||
@@ -1,48 +1,47 @@
|
||||
#include "hid_keyboard.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "input_events.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast key processor to hid_keyboard */
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor)
|
||||
#define SC_HID_MOD_NONE 0x00
|
||||
#define SC_HID_MOD_LEFT_CONTROL (1 << 0)
|
||||
#define SC_HID_MOD_LEFT_SHIFT (1 << 1)
|
||||
#define SC_HID_MOD_LEFT_ALT (1 << 2)
|
||||
#define SC_HID_MOD_LEFT_GUI (1 << 3)
|
||||
#define SC_HID_MOD_RIGHT_CONTROL (1 << 4)
|
||||
#define SC_HID_MOD_RIGHT_SHIFT (1 << 5)
|
||||
#define SC_HID_MOD_RIGHT_ALT (1 << 6)
|
||||
#define SC_HID_MOD_RIGHT_GUI (1 << 7)
|
||||
|
||||
#define HID_KEYBOARD_ACCESSORY_ID 1
|
||||
|
||||
#define HID_MODIFIER_NONE 0x00
|
||||
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
|
||||
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
|
||||
#define HID_MODIFIER_LEFT_ALT (1 << 2)
|
||||
#define HID_MODIFIER_LEFT_GUI (1 << 3)
|
||||
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
|
||||
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
|
||||
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
|
||||
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
|
||||
|
||||
#define HID_KEYBOARD_INDEX_MODIFIER 0
|
||||
#define HID_KEYBOARD_INDEX_KEYS 2
|
||||
#define SC_HID_KEYBOARD_INDEX_MODS 0
|
||||
#define SC_HID_KEYBOARD_INDEX_KEYS 2
|
||||
|
||||
// USB HID protocol says 6 keys in an event is the requirement for BIOS
|
||||
// keyboard support, though OS could support more keys via modifying the report
|
||||
// desc. 6 should be enough for scrcpy.
|
||||
#define HID_KEYBOARD_MAX_KEYS 6
|
||||
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
|
||||
#define SC_HID_KEYBOARD_MAX_KEYS 6
|
||||
#define SC_HID_KEYBOARD_INPUT_SIZE \
|
||||
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
|
||||
|
||||
#define HID_RESERVED 0x00
|
||||
#define HID_ERROR_ROLL_OVER 0x01
|
||||
#define SC_HID_RESERVED 0x00
|
||||
#define SC_HID_ERROR_ROLL_OVER 0x01
|
||||
|
||||
/**
|
||||
* For HID over AOAv2, only report descriptor is needed.
|
||||
* For HID, only report descriptor is needed.
|
||||
*
|
||||
* The specification is available here:
|
||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||
*
|
||||
* In particular, read:
|
||||
* - 6.2.2 Report Descriptor
|
||||
* - §6.2.2 Report Descriptor
|
||||
* - Appendix B.1 Protocol 1 (Keyboard)
|
||||
* - Appendix C: Keyboard Implementation
|
||||
*
|
||||
* The HID Usage Tables is also useful:
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
*
|
||||
* Normally a basic HID keyboard uses 8 bytes:
|
||||
* Modifier Reserved Key Key Key Key Key Key
|
||||
*
|
||||
@@ -52,7 +51,7 @@
|
||||
*
|
||||
* (change vid:pid' to your device's vendor ID and product ID).
|
||||
*/
|
||||
static const unsigned char keyboard_report_desc[] = {
|
||||
static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Keyboard)
|
||||
@@ -65,7 +64,7 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
0x05, 0x07,
|
||||
// Usage Minimum (224)
|
||||
0x19, 0xE0,
|
||||
// Usage Maximum (231)
|
||||
// Usage Maximum (231)
|
||||
0x29, 0xE7,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
@@ -118,7 +117,7 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
// Report Count (6)
|
||||
0x95, HID_KEYBOARD_MAX_KEYS,
|
||||
0x95, SC_HID_KEYBOARD_MAX_KEYS,
|
||||
// Input (Data, Array): Keys
|
||||
0x81, 0x00,
|
||||
|
||||
@@ -127,7 +126,7 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A keyboard HID event is 8 bytes long:
|
||||
* A keyboard HID input report is 8 bytes long:
|
||||
*
|
||||
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
|
||||
* - byte 1: reserved (always 0)
|
||||
@@ -200,51 +199,51 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
* +---------------+
|
||||
*/
|
||||
|
||||
static unsigned char
|
||||
sdl_keymod_to_hid_modifiers(uint16_t mod) {
|
||||
unsigned char modifiers = HID_MODIFIER_NONE;
|
||||
if (mod & SC_MOD_LCTRL) {
|
||||
modifiers |= HID_MODIFIER_LEFT_CONTROL;
|
||||
}
|
||||
if (mod & SC_MOD_LSHIFT) {
|
||||
modifiers |= HID_MODIFIER_LEFT_SHIFT;
|
||||
}
|
||||
if (mod & SC_MOD_LALT) {
|
||||
modifiers |= HID_MODIFIER_LEFT_ALT;
|
||||
}
|
||||
if (mod & SC_MOD_LGUI) {
|
||||
modifiers |= HID_MODIFIER_LEFT_GUI;
|
||||
}
|
||||
if (mod & SC_MOD_RCTRL) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
|
||||
}
|
||||
if (mod & SC_MOD_RSHIFT) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
|
||||
}
|
||||
if (mod & SC_MOD_RALT) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_ALT;
|
||||
}
|
||||
if (mod & SC_MOD_RGUI) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_GUI;
|
||||
}
|
||||
return modifiers;
|
||||
static void
|
||||
sc_hid_keyboard_input_init(struct sc_hid_input *hid_input) {
|
||||
hid_input->hid_id = SC_HID_ID_KEYBOARD;
|
||||
hid_input->size = SC_HID_KEYBOARD_INPUT_SIZE;
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
|
||||
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
|
||||
data[1] = SC_HID_RESERVED;
|
||||
memset(&data[SC_HID_KEYBOARD_INDEX_KEYS], 0, SC_HID_KEYBOARD_MAX_KEYS);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
||||
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
|
||||
if (!buffer) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
static uint16_t
|
||||
sc_hid_mod_from_sdl_keymod(uint16_t mod) {
|
||||
uint16_t mods = SC_HID_MOD_NONE;
|
||||
if (mod & SC_MOD_LCTRL) {
|
||||
mods |= SC_HID_MOD_LEFT_CONTROL;
|
||||
}
|
||||
if (mod & SC_MOD_LSHIFT) {
|
||||
mods |= SC_HID_MOD_LEFT_SHIFT;
|
||||
}
|
||||
if (mod & SC_MOD_LALT) {
|
||||
mods |= SC_HID_MOD_LEFT_ALT;
|
||||
}
|
||||
if (mod & SC_MOD_LGUI) {
|
||||
mods |= SC_HID_MOD_LEFT_GUI;
|
||||
}
|
||||
if (mod & SC_MOD_RCTRL) {
|
||||
mods |= SC_HID_MOD_RIGHT_CONTROL;
|
||||
}
|
||||
if (mod & SC_MOD_RSHIFT) {
|
||||
mods |= SC_HID_MOD_RIGHT_SHIFT;
|
||||
}
|
||||
if (mod & SC_MOD_RALT) {
|
||||
mods |= SC_HID_MOD_RIGHT_ALT;
|
||||
}
|
||||
if (mod & SC_MOD_RGUI) {
|
||||
mods |= SC_HID_MOD_RIGHT_GUI;
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
|
||||
buffer[1] = HID_RESERVED;
|
||||
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
|
||||
|
||||
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
|
||||
HID_KEYBOARD_EVENT_SIZE);
|
||||
return true;
|
||||
void
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *hid) {
|
||||
memset(hid->keys, false, SC_HID_KEYBOARD_KEYS);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -252,10 +251,10 @@ scancode_is_modifier(enum sc_scancode scancode) {
|
||||
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
||||
struct sc_hid_event *hid_event,
|
||||
const struct sc_key_event *event) {
|
||||
bool
|
||||
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_key_event *event) {
|
||||
enum sc_scancode scancode = event->scancode;
|
||||
assert(scancode >= 0);
|
||||
|
||||
@@ -267,39 +266,37 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_hid_keyboard_event_init(hid_event)) {
|
||||
LOGW("Could not initialize HID keyboard event");
|
||||
return false;
|
||||
}
|
||||
sc_hid_keyboard_input_init(hid_input);
|
||||
|
||||
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state);
|
||||
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
|
||||
|
||||
if (scancode < SC_HID_KEYBOARD_KEYS) {
|
||||
// Pressed is true and released is false
|
||||
kb->keys[scancode] = (event->action == SC_ACTION_DOWN);
|
||||
hid->keys[scancode] = (event->action == SC_ACTION_DOWN);
|
||||
LOGV("keys[%02x] = %s", scancode,
|
||||
kb->keys[scancode] ? "true" : "false");
|
||||
hid->keys[scancode] ? "true" : "false");
|
||||
}
|
||||
|
||||
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
|
||||
hid_input->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
|
||||
|
||||
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
|
||||
uint8_t *keys_data = &hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS];
|
||||
// Re-calculate pressed keys every time
|
||||
int keys_pressed_count = 0;
|
||||
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
||||
if (kb->keys[i]) {
|
||||
if (hid->keys[i]) {
|
||||
// USB HID protocol says that if keys exceeds report count, a
|
||||
// phantom state should be reported
|
||||
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
|
||||
if (keys_pressed_count >= SC_HID_KEYBOARD_MAX_KEYS) {
|
||||
// Phantom state:
|
||||
// - Modifiers
|
||||
// - Reserved
|
||||
// - ErrorRollOver * HID_MAX_KEYS
|
||||
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
|
||||
memset(keys_data, SC_HID_ERROR_ROLL_OVER,
|
||||
SC_HID_KEYBOARD_MAX_KEYS);
|
||||
goto end;
|
||||
}
|
||||
|
||||
keys_buffer[keys_pressed_count] = i;
|
||||
keys_data[keys_pressed_count] = i;
|
||||
++keys_pressed_count;
|
||||
}
|
||||
}
|
||||
@@ -307,124 +304,42 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
||||
end:
|
||||
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
|
||||
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
|
||||
event->scancode, modifiers);
|
||||
event->scancode, mods);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
|
||||
bool
|
||||
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||
uint16_t mods_state) {
|
||||
bool capslock = mods_state & SC_MOD_CAPS;
|
||||
bool numlock = mods_state & SC_MOD_NUM;
|
||||
if (!capslock && !numlock) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
if (!sc_hid_keyboard_event_init(&hid_event)) {
|
||||
LOGW("Could not initialize HID keyboard event");
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_keyboard_input_init(hid_input);
|
||||
|
||||
unsigned i = 0;
|
||||
if (capslock) {
|
||||
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||
++i;
|
||||
}
|
||||
if (numlock) {
|
||||
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
||||
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
||||
++i;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||
sc_hid_event_destroy(&hid_event);
|
||||
LOGW("Could not request HID event (mod lock state)");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGD("HID keyboard state synchronized");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
const struct sc_key_event *event,
|
||||
uint64_t ack_to_wait) {
|
||||
if (event->repeat) {
|
||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||
// just ignore key repeat here.
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_hid_keyboard *kb = DOWNCAST(kp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
// Not all keys are supported, just ignore unsupported keys
|
||||
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
|
||||
if (!kb->mod_lock_synchronized) {
|
||||
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
||||
// keyboard state
|
||||
if (push_mod_lock_state(kb, event->mods_state)) {
|
||||
kb->mod_lock_synchronized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ack_to_wait) {
|
||||
// Ctrl+v is pressed, so clipboard synchronization has been
|
||||
// requested. Wait until clipboard synchronization is acknowledged
|
||||
// by the server, otherwise it could paste the old clipboard
|
||||
// content.
|
||||
hid_event.ack_to_wait = ack_to_wait;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||
sc_hid_event_destroy(&hid_event);
|
||||
LOGW("Could not request HID event (key)");
|
||||
}
|
||||
}
|
||||
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
||||
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
|
||||
kb->aoa = aoa;
|
||||
|
||||
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||
keyboard_report_desc,
|
||||
ARRAY_LEN(keyboard_report_desc));
|
||||
if (!ok) {
|
||||
LOGW("Register HID keyboard failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset all states
|
||||
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
|
||||
|
||||
kb->mod_lock_synchronized = false;
|
||||
|
||||
static const struct sc_key_processor_ops ops = {
|
||||
.process_key = sc_key_processor_process_key,
|
||||
// Never forward text input via HID (all the keys are injected
|
||||
// separately)
|
||||
.process_text = NULL,
|
||||
};
|
||||
|
||||
// Clipboard synchronization is requested over the control socket, while HID
|
||||
// events are sent over AOA, so it must wait for clipboard synchronization
|
||||
// to be acknowledged by the device before injecting Ctrl+v.
|
||||
kb->key_processor.async_paste = true;
|
||||
kb->key_processor.ops = &ops;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) {
|
||||
// Unregister HID keyboard so the soft keyboard shows again on Android
|
||||
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
||||
if (!ok) {
|
||||
LOGW("Could not unregister HID keyboard");
|
||||
}
|
||||
void sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close) {
|
||||
hid_close->hid_id = SC_HID_ID_KEYBOARD;
|
||||
}
|
||||
@@ -4,16 +4,19 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "aoa_hid.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
// See "SDL2/SDL_scancode.h".
|
||||
// See "SDL3/SDL_scancode.h".
|
||||
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
||||
// HID protocol.
|
||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||
#define SC_HID_KEYBOARD_KEYS 0x66
|
||||
|
||||
#define SC_HID_ID_KEYBOARD 1
|
||||
|
||||
/**
|
||||
* HID keyboard events are sequence-based, every time keyboard state changes
|
||||
* it sends an array of currently pressed keys, the host is responsible for
|
||||
@@ -27,18 +30,25 @@
|
||||
* phantom state.
|
||||
*/
|
||||
struct sc_hid_keyboard {
|
||||
struct sc_key_processor key_processor; // key processor trait
|
||||
|
||||
struct sc_aoa *aoa;
|
||||
bool keys[SC_HID_KEYBOARD_KEYS];
|
||||
|
||||
bool mod_lock_synchronized;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
|
||||
void
|
||||
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
|
||||
|
||||
void
|
||||
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
|
||||
sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open);
|
||||
|
||||
void
|
||||
sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close);
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_key_event *event);
|
||||
|
||||
bool
|
||||
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||
uint16_t mods_state);
|
||||
|
||||
#endif
|
||||
243
app/src/hid/hid_mouse.c
Normal file
243
app/src/hid/hid_mouse.c
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "hid_mouse.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
||||
// 1 byte for wheel motion, 1 byte for hozizontal scrolling
|
||||
#define SC_HID_MOUSE_INPUT_SIZE 5
|
||||
|
||||
/**
|
||||
* Mouse descriptor from the specification:
|
||||
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||
*
|
||||
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
||||
*
|
||||
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
||||
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||
* §4 Generic Desktop Page (0x01) (p32)
|
||||
*/
|
||||
static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (Mouse)
|
||||
0x09, 0x02,
|
||||
|
||||
// Collection (Application)
|
||||
0xA1, 0x01,
|
||||
|
||||
// Usage (Pointer)
|
||||
0x09, 0x01,
|
||||
|
||||
// Collection (Physical)
|
||||
0xA1, 0x00,
|
||||
|
||||
// Usage Page (Buttons)
|
||||
0x05, 0x09,
|
||||
|
||||
// Usage Minimum (1)
|
||||
0x19, 0x01,
|
||||
// Usage Maximum (5)
|
||||
0x29, 0x05,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum (1)
|
||||
0x25, 0x01,
|
||||
// Report Count (5)
|
||||
0x95, 0x05,
|
||||
// Report Size (1)
|
||||
0x75, 0x01,
|
||||
// Input (Data, Variable, Absolute): 5 buttons bits
|
||||
0x81, 0x02,
|
||||
|
||||
// Report Count (1)
|
||||
0x95, 0x01,
|
||||
// Report Size (3)
|
||||
0x75, 0x03,
|
||||
// Input (Constant): 3 bits padding
|
||||
0x81, 0x01,
|
||||
|
||||
// Usage Page (Generic Desktop)
|
||||
0x05, 0x01,
|
||||
// Usage (X)
|
||||
0x09, 0x30,
|
||||
// Usage (Y)
|
||||
0x09, 0x31,
|
||||
// Usage (Wheel)
|
||||
0x09, 0x38,
|
||||
// Logical Minimum (-127)
|
||||
0x15, 0x81,
|
||||
// Logical Maximum (127)
|
||||
0x25, 0x7F,
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
// Report Count (3)
|
||||
0x95, 0x03,
|
||||
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
|
||||
0x81, 0x06,
|
||||
|
||||
// Usage Page (Consumer Page)
|
||||
0x05, 0x0C,
|
||||
// Usage(AC Pan)
|
||||
0x0A, 0x38, 0x02,
|
||||
// Logical Minimum (-127)
|
||||
0x15, 0x81,
|
||||
// Logical Maximum (127)
|
||||
0x25, 0x7F,
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
// Report Count (1)
|
||||
0x95, 0x01,
|
||||
// Input (Data, Variable, Relative): 1 byte (AC Pan)
|
||||
0x81, 0x06,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
};
|
||||
|
||||
/**
|
||||
* A mouse HID input report is 4 bytes long:
|
||||
*
|
||||
* - byte 0: buttons state
|
||||
* - byte 1: relative x motion (signed byte from -127 to 127)
|
||||
* - byte 2: relative y motion (signed byte from -127 to 127)
|
||||
* - byte 3: wheel motion (-1, 0 or 1)
|
||||
*
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* +---------------+
|
||||
* byte 0: |0 0 0 . . . . .| buttons state
|
||||
* +---------------+
|
||||
* ^ ^ ^ ^ ^
|
||||
* | | | | `- left button
|
||||
* | | | `--- right button
|
||||
* | | `----- middle button
|
||||
* | `------- button 4
|
||||
* `--------- button 5
|
||||
*
|
||||
* +---------------+
|
||||
* byte 1: |. . . . . . . .| relative x motion
|
||||
* +---------------+
|
||||
* byte 2: |. . . . . . . .| relative y motion
|
||||
* +---------------+
|
||||
* byte 3: |. . . . . . . .| wheel motion
|
||||
* +---------------+
|
||||
*
|
||||
* As an example, here is the report for a motion of (x=5, y=-4) with left
|
||||
* button pressed:
|
||||
*
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 0 0 1| left button pressed
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
|
||||
* +---------------+
|
||||
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
|
||||
* +---------------+
|
||||
* |0 0 0 0 0 0 0 0| wheel motion
|
||||
* +---------------+
|
||||
*/
|
||||
|
||||
static void
|
||||
sc_hid_mouse_input_init(struct sc_hid_input *hid_input) {
|
||||
hid_input->hid_id = SC_HID_ID_MOUSE;
|
||||
hid_input->size = SC_HID_MOUSE_INPUT_SIZE;
|
||||
// Leave ->data uninitialized, it will be fully initialized by callers
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
|
||||
uint8_t c = 0;
|
||||
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
|
||||
c |= 1 << 0;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
|
||||
c |= 1 << 1;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
|
||||
c |= 1 << 2;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_X1) {
|
||||
c |= 1 << 3;
|
||||
}
|
||||
if (buttons_state & SC_MOUSE_BUTTON_X2) {
|
||||
c |= 1 << 4;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_init(struct sc_hid_mouse *hid) {
|
||||
hid->residual_hscroll = 0;
|
||||
hid->residual_vscroll = 0;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||
data[1] = CLAMP(event->xrel, -127, 127);
|
||||
data[2] = CLAMP(event->yrel, -127, 127);
|
||||
data[3] = 0; // no vertical scrolling
|
||||
data[4] = 0; // no horizontal scrolling
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||
data[1] = 0; // no x motion
|
||||
data[2] = 0; // no y motion
|
||||
data[3] = 0; // no vertical scrolling
|
||||
data[4] = 0; // no horizontal scrolling
|
||||
}
|
||||
|
||||
static int8_t
|
||||
consume_scroll_integer(float *scroll) {
|
||||
float value = CLAMP(*scroll, -127, 127);
|
||||
int8_t consume = value; // truncate towards 0
|
||||
float residual = value - consume;
|
||||
*scroll = residual;
|
||||
return consume;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_mouse *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
hid->residual_hscroll += event->hscroll;
|
||||
hid->residual_vscroll += event->vscroll;
|
||||
int8_t hscroll = consume_scroll_integer(&hid->residual_hscroll);
|
||||
int8_t vscroll = consume_scroll_integer(&hid->residual_vscroll);
|
||||
|
||||
if (!hscroll && !vscroll) {
|
||||
// Not enough scrolling to inject a scroll event
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = 0; // buttons state irrelevant (and unknown)
|
||||
data[1] = 0; // no x motion
|
||||
data[2] = 0; // no y motion
|
||||
data[3] = vscroll;
|
||||
data[4] = hscroll;
|
||||
return true;
|
||||
}
|
||||
|
||||
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||
hid_open->hid_id = SC_HID_ID_MOUSE;
|
||||
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||
}
|
||||
|
||||
void sc_hid_mouse_generate_close(struct sc_hid_close *hid_close) {
|
||||
hid_close->hid_id = SC_HID_ID_MOUSE;
|
||||
}
|
||||
37
app/src/hid/hid_mouse.h
Normal file
37
app/src/hid/hid_mouse.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef SC_HID_MOUSE_H
|
||||
#define SC_HID_MOUSE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
#define SC_HID_ID_MOUSE 2
|
||||
|
||||
struct sc_hid_mouse {
|
||||
float residual_hscroll;
|
||||
float residual_vscroll;
|
||||
};
|
||||
|
||||
void sc_hid_mouse_init(struct sc_hid_mouse *hid);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_close(struct sc_hid_close *hid_close);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_motion_event *event);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_click_event *event);
|
||||
|
||||
bool
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_mouse *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_scroll_event *event);
|
||||
|
||||
#endif
|
||||
145
app/src/icon.c
145
app/src/icon.c
@@ -2,52 +2,49 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
#include "util/env.h"
|
||||
#include "util/file.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
|
||||
#define SCRCPY_DEFAULT_ICON_PATH \
|
||||
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
|
||||
#define SCRCPY_DEFAULT_ICON_DIR PREFIX "/share/icons/hicolor/256x256/apps"
|
||||
|
||||
static char *
|
||||
get_icon_path(void) {
|
||||
#ifdef __WINDOWS__
|
||||
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
|
||||
#else
|
||||
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
|
||||
#endif
|
||||
if (icon_path_env) {
|
||||
get_icon_path(const char *filename) {
|
||||
char *icon_path;
|
||||
|
||||
char *icon_dir = sc_get_env("SCRCPY_ICON_DIR");
|
||||
if (icon_dir) {
|
||||
// if the envvar is set, use it
|
||||
#ifdef __WINDOWS__
|
||||
char *icon_path = sc_str_from_wchars(icon_path_env);
|
||||
#else
|
||||
char *icon_path = strdup(icon_path_env);
|
||||
#endif
|
||||
icon_path = sc_file_build_path(icon_dir, filename);
|
||||
free(icon_dir);
|
||||
if (!icon_path) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
||||
LOGD("Using icon from SCRCPY_ICON_DIR: %s", icon_path);
|
||||
return icon_path;
|
||||
}
|
||||
|
||||
#ifndef PORTABLE
|
||||
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
|
||||
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
|
||||
icon_path = sc_file_build_path(SCRCPY_DEFAULT_ICON_DIR, filename);
|
||||
if (!icon_path) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
LOGD("Using icon: %s", icon_path);
|
||||
#else
|
||||
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
||||
icon_path = sc_file_get_local_path(filename);
|
||||
if (!icon_path) {
|
||||
LOGE("Could not get icon path");
|
||||
return NULL;
|
||||
@@ -78,7 +75,19 @@ decode_image(const char *path) {
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
||||
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2021-04-27 - 46dac8cf3d - lavf 59.0.100 - avformat.h
|
||||
// av_find_best_stream now uses a const AVCodec ** parameter
|
||||
// for the returned decoder.
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 0, 100)
|
||||
const AVCodec *codec;
|
||||
#else
|
||||
AVCodec *codec;
|
||||
#endif
|
||||
|
||||
int stream =
|
||||
av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
||||
if (stream < 0 ) {
|
||||
LOGE("Could not find best image stream");
|
||||
goto close_input;
|
||||
@@ -86,12 +95,6 @@ decode_image(const char *path) {
|
||||
|
||||
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
||||
|
||||
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
||||
if (!codec) {
|
||||
LOGE("Could not find image decoder");
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (!codec_ctx) {
|
||||
LOG_OOM();
|
||||
@@ -111,21 +114,21 @@ decode_image(const char *path) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
LOG_OOM();
|
||||
goto close_codec;
|
||||
goto free_codec_ctx;
|
||||
}
|
||||
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
if (!packet) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&frame);
|
||||
goto close_codec;
|
||||
goto free_codec_ctx;
|
||||
}
|
||||
|
||||
if (av_read_frame(ctx, packet) < 0) {
|
||||
LOGE("Could not read frame");
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
goto close_codec;
|
||||
goto free_codec_ctx;
|
||||
}
|
||||
|
||||
int ret;
|
||||
@@ -133,22 +136,20 @@ decode_image(const char *path) {
|
||||
LOGE("Could not send icon packet: %d", ret);
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
goto close_codec;
|
||||
goto free_codec_ctx;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
|
||||
LOGE("Could not receive icon frame: %d", ret);
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
goto close_codec;
|
||||
goto free_codec_ctx;
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
|
||||
result = frame;
|
||||
|
||||
close_codec:
|
||||
avcodec_close(codec_ctx);
|
||||
free_codec_ctx:
|
||||
avcodec_free_context(&codec_ctx);
|
||||
close_input:
|
||||
@@ -159,13 +160,7 @@ free_ctx:
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
||||
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
|
||||
// versions.
|
||||
typedef int SDL_PixelFormatEnum;
|
||||
#endif
|
||||
|
||||
static SDL_PixelFormatEnum
|
||||
static SDL_PixelFormat
|
||||
to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
||||
switch (fmt) {
|
||||
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
|
||||
@@ -175,20 +170,18 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
||||
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
|
||||
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
|
||||
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
|
||||
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
|
||||
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_XRGB1555;
|
||||
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
|
||||
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
|
||||
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 12)
|
||||
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
|
||||
#endif
|
||||
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_XBGR1555;
|
||||
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_XRGB4444;
|
||||
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_XBGR4444;
|
||||
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
|
||||
default: return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Surface *
|
||||
load_from_path(const char *path) {
|
||||
sc_icon_load_from_full_path(const char *path) {
|
||||
AVFrame *frame = decode_image(path);
|
||||
if (!frame) {
|
||||
return NULL;
|
||||
@@ -206,20 +199,16 @@ load_from_path(const char *path) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
|
||||
SDL_PixelFormat format = to_sdl_pixel_format(frame->format);
|
||||
if (format == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
|
||||
frame->format);
|
||||
goto error;
|
||||
}
|
||||
|
||||
int bits_per_pixel = av_get_bits_per_pixel(desc);
|
||||
SDL_Surface *surface =
|
||||
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
|
||||
frame->width, frame->height,
|
||||
bits_per_pixel,
|
||||
frame->linesize[0],
|
||||
format);
|
||||
SDL_CreateSurfaceFrom(frame->width, frame->height, format,
|
||||
frame->data[0], frame->linesize[0]);
|
||||
|
||||
if (!surface) {
|
||||
LOGE("Could not create icon surface");
|
||||
@@ -251,17 +240,35 @@ load_from_path(const char *path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_Palette *palette = surface->format->palette;
|
||||
assert(palette);
|
||||
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
|
||||
if (ret) {
|
||||
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
|
||||
if (!palette) {
|
||||
LOGE("Could not create palette");
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool ok = SDL_SetPaletteColors(palette, colors, 0, 256);
|
||||
if (!ok) {
|
||||
LOGE("Could not set palette colors");
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
surface->userdata = frame; // frame owns the data
|
||||
SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
|
||||
if (!props) {
|
||||
LOGE("Could not get surface properties: %s", SDL_GetError());
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// frame owns the data
|
||||
bool ok = SDL_SetPointerProperty(props, "sc_frame", frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pointer property: %s", SDL_GetError());
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return surface;
|
||||
|
||||
@@ -271,21 +278,23 @@ error:
|
||||
}
|
||||
|
||||
SDL_Surface *
|
||||
scrcpy_icon_load() {
|
||||
char *icon_path = get_icon_path();
|
||||
sc_icon_load(const char *filename) {
|
||||
char *icon_path = get_icon_path(filename);
|
||||
if (!icon_path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = load_from_path(icon_path);
|
||||
SDL_Surface *icon = sc_icon_load_from_full_path(icon_path);
|
||||
free(icon_path);
|
||||
return icon;
|
||||
}
|
||||
|
||||
void
|
||||
scrcpy_icon_destroy(SDL_Surface *icon) {
|
||||
AVFrame *frame = icon->userdata;
|
||||
sc_icon_destroy(SDL_Surface *icon) {
|
||||
SDL_PropertiesID props = SDL_GetSurfaceProperties(icon);
|
||||
assert(props);
|
||||
AVFrame *frame = SDL_GetPointerProperty(props, "sc_frame", NULL);
|
||||
assert(frame);
|
||||
av_frame_free(&frame);
|
||||
SDL_FreeSurface(icon);
|
||||
SDL_DestroySurface(icon);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
|
||||
#define SC_ICON_FILENAME_SCRCPY "scrcpy.png"
|
||||
#define SC_ICON_FILENAME_DISCONNECTED "disconnected.png"
|
||||
|
||||
SDL_Surface *
|
||||
scrcpy_icon_load(void);
|
||||
sc_icon_load(const char *filename);
|
||||
|
||||
void
|
||||
scrcpy_icon_destroy(SDL_Surface *icon);
|
||||
sc_icon_destroy(SDL_Surface *icon);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
#include "coords.h"
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* for simplicity.
|
||||
*
|
||||
* This scrcpy input events API is designed to be consumed by input event
|
||||
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
|
||||
* processors (sc_key_processor, sc_mouse_processor and sc_gamepad_processor,
|
||||
* see app/src/trait/).
|
||||
*
|
||||
* One major semantic difference between SDL input events and scrcpy input
|
||||
* events is their frame of reference (for mouse and touch events): SDL events
|
||||
@@ -43,17 +44,17 @@
|
||||
*/
|
||||
|
||||
enum sc_mod {
|
||||
SC_MOD_LSHIFT = KMOD_LSHIFT,
|
||||
SC_MOD_RSHIFT = KMOD_RSHIFT,
|
||||
SC_MOD_LCTRL = KMOD_LCTRL,
|
||||
SC_MOD_RCTRL = KMOD_RCTRL,
|
||||
SC_MOD_LALT = KMOD_LALT,
|
||||
SC_MOD_RALT = KMOD_RALT,
|
||||
SC_MOD_LGUI = KMOD_LGUI,
|
||||
SC_MOD_RGUI = KMOD_RGUI,
|
||||
SC_MOD_LSHIFT = SDL_KMOD_LSHIFT,
|
||||
SC_MOD_RSHIFT = SDL_KMOD_RSHIFT,
|
||||
SC_MOD_LCTRL = SDL_KMOD_LCTRL,
|
||||
SC_MOD_RCTRL = SDL_KMOD_RCTRL,
|
||||
SC_MOD_LALT = SDL_KMOD_LALT,
|
||||
SC_MOD_RALT = SDL_KMOD_RALT,
|
||||
SC_MOD_LGUI = SDL_KMOD_LGUI,
|
||||
SC_MOD_RGUI = SDL_KMOD_RGUI,
|
||||
|
||||
SC_MOD_NUM = KMOD_NUM,
|
||||
SC_MOD_CAPS = KMOD_CAPS,
|
||||
SC_MOD_NUM = SDL_KMOD_NUM,
|
||||
SC_MOD_CAPS = SDL_KMOD_CAPS,
|
||||
};
|
||||
|
||||
enum sc_action {
|
||||
@@ -70,12 +71,12 @@ enum sc_keycode {
|
||||
SC_KEYCODE_TAB = SDLK_TAB,
|
||||
SC_KEYCODE_SPACE = SDLK_SPACE,
|
||||
SC_KEYCODE_EXCLAIM = SDLK_EXCLAIM,
|
||||
SC_KEYCODE_QUOTEDBL = SDLK_QUOTEDBL,
|
||||
SC_KEYCODE_QUOTEDBL = SDLK_DBLAPOSTROPHE,
|
||||
SC_KEYCODE_HASH = SDLK_HASH,
|
||||
SC_KEYCODE_PERCENT = SDLK_PERCENT,
|
||||
SC_KEYCODE_DOLLAR = SDLK_DOLLAR,
|
||||
SC_KEYCODE_AMPERSAND = SDLK_AMPERSAND,
|
||||
SC_KEYCODE_QUOTE = SDLK_QUOTE,
|
||||
SC_KEYCODE_QUOTE = SDLK_APOSTROPHE,
|
||||
SC_KEYCODE_LEFTPAREN = SDLK_LEFTPAREN,
|
||||
SC_KEYCODE_RIGHTPAREN = SDLK_RIGHTPAREN,
|
||||
SC_KEYCODE_ASTERISK = SDLK_ASTERISK,
|
||||
@@ -107,33 +108,33 @@ enum sc_keycode {
|
||||
SC_KEYCODE_RIGHTBRACKET = SDLK_RIGHTBRACKET,
|
||||
SC_KEYCODE_CARET = SDLK_CARET,
|
||||
SC_KEYCODE_UNDERSCORE = SDLK_UNDERSCORE,
|
||||
SC_KEYCODE_BACKQUOTE = SDLK_BACKQUOTE,
|
||||
SC_KEYCODE_a = SDLK_a,
|
||||
SC_KEYCODE_b = SDLK_b,
|
||||
SC_KEYCODE_c = SDLK_c,
|
||||
SC_KEYCODE_d = SDLK_d,
|
||||
SC_KEYCODE_e = SDLK_e,
|
||||
SC_KEYCODE_f = SDLK_f,
|
||||
SC_KEYCODE_g = SDLK_g,
|
||||
SC_KEYCODE_h = SDLK_h,
|
||||
SC_KEYCODE_i = SDLK_i,
|
||||
SC_KEYCODE_j = SDLK_j,
|
||||
SC_KEYCODE_k = SDLK_k,
|
||||
SC_KEYCODE_l = SDLK_l,
|
||||
SC_KEYCODE_m = SDLK_m,
|
||||
SC_KEYCODE_n = SDLK_n,
|
||||
SC_KEYCODE_o = SDLK_o,
|
||||
SC_KEYCODE_p = SDLK_p,
|
||||
SC_KEYCODE_q = SDLK_q,
|
||||
SC_KEYCODE_r = SDLK_r,
|
||||
SC_KEYCODE_s = SDLK_s,
|
||||
SC_KEYCODE_t = SDLK_t,
|
||||
SC_KEYCODE_u = SDLK_u,
|
||||
SC_KEYCODE_v = SDLK_v,
|
||||
SC_KEYCODE_w = SDLK_w,
|
||||
SC_KEYCODE_x = SDLK_x,
|
||||
SC_KEYCODE_y = SDLK_y,
|
||||
SC_KEYCODE_z = SDLK_z,
|
||||
SC_KEYCODE_BACKQUOTE = SDLK_GRAVE,
|
||||
SC_KEYCODE_a = SDLK_A,
|
||||
SC_KEYCODE_b = SDLK_B,
|
||||
SC_KEYCODE_c = SDLK_C,
|
||||
SC_KEYCODE_d = SDLK_D,
|
||||
SC_KEYCODE_e = SDLK_E,
|
||||
SC_KEYCODE_f = SDLK_F,
|
||||
SC_KEYCODE_g = SDLK_G,
|
||||
SC_KEYCODE_h = SDLK_H,
|
||||
SC_KEYCODE_i = SDLK_I,
|
||||
SC_KEYCODE_j = SDLK_J,
|
||||
SC_KEYCODE_k = SDLK_K,
|
||||
SC_KEYCODE_l = SDLK_L,
|
||||
SC_KEYCODE_m = SDLK_M,
|
||||
SC_KEYCODE_n = SDLK_N,
|
||||
SC_KEYCODE_o = SDLK_O,
|
||||
SC_KEYCODE_p = SDLK_P,
|
||||
SC_KEYCODE_q = SDLK_Q,
|
||||
SC_KEYCODE_r = SDLK_R,
|
||||
SC_KEYCODE_s = SDLK_S,
|
||||
SC_KEYCODE_t = SDLK_T,
|
||||
SC_KEYCODE_u = SDLK_U,
|
||||
SC_KEYCODE_v = SDLK_V,
|
||||
SC_KEYCODE_w = SDLK_W,
|
||||
SC_KEYCODE_x = SDLK_X,
|
||||
SC_KEYCODE_y = SDLK_Y,
|
||||
SC_KEYCODE_z = SDLK_Z,
|
||||
|
||||
SC_KEYCODE_CAPSLOCK = SDLK_CAPSLOCK,
|
||||
|
||||
@@ -315,11 +316,40 @@ enum sc_scancode {
|
||||
// to avoid unnecessary conversions (and confusion).
|
||||
enum sc_mouse_button {
|
||||
SC_MOUSE_BUTTON_UNKNOWN = 0,
|
||||
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON(SDL_BUTTON_LEFT),
|
||||
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON(SDL_BUTTON_RIGHT),
|
||||
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON(SDL_BUTTON_MIDDLE),
|
||||
SC_MOUSE_BUTTON_X1 = SDL_BUTTON(SDL_BUTTON_X1),
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
|
||||
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON_MASK(SDL_BUTTON_LEFT),
|
||||
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON_MASK(SDL_BUTTON_RIGHT),
|
||||
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE),
|
||||
SC_MOUSE_BUTTON_X1 = SDL_BUTTON_MASK(SDL_BUTTON_X1),
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON_MASK(SDL_BUTTON_X2),
|
||||
};
|
||||
|
||||
enum sc_gamepad_axis {
|
||||
SC_GAMEPAD_AXIS_UNKNOWN = -1,
|
||||
SC_GAMEPAD_AXIS_LEFTX = SDL_GAMEPAD_AXIS_LEFTX,
|
||||
SC_GAMEPAD_AXIS_LEFTY = SDL_GAMEPAD_AXIS_LEFTY,
|
||||
SC_GAMEPAD_AXIS_RIGHTX = SDL_GAMEPAD_AXIS_RIGHTX,
|
||||
SC_GAMEPAD_AXIS_RIGHTY = SDL_GAMEPAD_AXIS_RIGHTY,
|
||||
SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
|
||||
SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
|
||||
};
|
||||
|
||||
enum sc_gamepad_button {
|
||||
SC_GAMEPAD_BUTTON_UNKNOWN = -1,
|
||||
SC_GAMEPAD_BUTTON_SOUTH = SDL_GAMEPAD_BUTTON_SOUTH,
|
||||
SC_GAMEPAD_BUTTON_EAST = SDL_GAMEPAD_BUTTON_EAST,
|
||||
SC_GAMEPAD_BUTTON_WEST = SDL_GAMEPAD_BUTTON_WEST,
|
||||
SC_GAMEPAD_BUTTON_NORTH = SDL_GAMEPAD_BUTTON_NORTH,
|
||||
SC_GAMEPAD_BUTTON_BACK = SDL_GAMEPAD_BUTTON_BACK,
|
||||
SC_GAMEPAD_BUTTON_GUIDE = SDL_GAMEPAD_BUTTON_GUIDE,
|
||||
SC_GAMEPAD_BUTTON_START = SDL_GAMEPAD_BUTTON_START,
|
||||
SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_GAMEPAD_BUTTON_LEFT_STICK,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_GAMEPAD_BUTTON_RIGHT_STICK,
|
||||
SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
|
||||
SC_GAMEPAD_BUTTON_DPAD_UP = SDL_GAMEPAD_BUTTON_DPAD_UP,
|
||||
SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_GAMEPAD_BUTTON_DPAD_DOWN,
|
||||
SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_GAMEPAD_BUTTON_DPAD_LEFT,
|
||||
SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
|
||||
};
|
||||
|
||||
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
|
||||
@@ -379,6 +409,26 @@ struct sc_touch_event {
|
||||
float pressure;
|
||||
};
|
||||
|
||||
// As documented in <https://wiki.libsdl.org/SDL3/SDL_JoystickID>:
|
||||
// The value 0 is an invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID 0
|
||||
|
||||
struct sc_gamepad_device_event {
|
||||
uint32_t gamepad_id;
|
||||
};
|
||||
|
||||
struct sc_gamepad_button_event {
|
||||
uint32_t gamepad_id;
|
||||
enum sc_action action;
|
||||
enum sc_gamepad_button button;
|
||||
};
|
||||
|
||||
struct sc_gamepad_axis_event {
|
||||
uint32_t gamepad_id;
|
||||
enum sc_gamepad_axis axis;
|
||||
int16_t value;
|
||||
};
|
||||
|
||||
static inline uint16_t
|
||||
sc_mods_state_from_sdl(uint16_t mods_state) {
|
||||
return mods_state;
|
||||
@@ -396,8 +446,8 @@ sc_scancode_from_sdl(SDL_Scancode scancode) {
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_keyboard_type(uint32_t type) {
|
||||
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
|
||||
if (type == SDL_KEYDOWN) {
|
||||
assert(type == SDL_EVENT_KEY_DOWN || type == SDL_EVENT_KEY_UP);
|
||||
if (type == SDL_EVENT_KEY_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
@@ -405,8 +455,8 @@ sc_action_from_sdl_keyboard_type(uint32_t type) {
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_mousebutton_type(uint32_t type) {
|
||||
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
|
||||
if (type == SDL_MOUSEBUTTONDOWN) {
|
||||
assert(type == SDL_EVENT_MOUSE_BUTTON_DOWN || type == SDL_EVENT_MOUSE_BUTTON_UP);
|
||||
if (type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
@@ -414,12 +464,12 @@ sc_action_from_sdl_mousebutton_type(uint32_t type) {
|
||||
|
||||
static inline enum sc_touch_action
|
||||
sc_touch_action_from_sdl(uint32_t type) {
|
||||
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
|
||||
type == SDL_FINGERUP);
|
||||
if (type == SDL_FINGERMOTION) {
|
||||
assert(type == SDL_EVENT_FINGER_MOTION || type == SDL_EVENT_FINGER_DOWN ||
|
||||
type == SDL_EVENT_FINGER_UP);
|
||||
if (type == SDL_EVENT_FINGER_MOTION) {
|
||||
return SC_TOUCH_ACTION_MOVE;
|
||||
}
|
||||
if (type == SDL_FINGERDOWN) {
|
||||
if (type == SDL_EVENT_FINGER_DOWN) {
|
||||
return SC_TOUCH_ACTION_DOWN;
|
||||
}
|
||||
return SC_TOUCH_ACTION_UP;
|
||||
@@ -429,26 +479,47 @@ static inline enum sc_mouse_button
|
||||
sc_mouse_button_from_sdl(uint8_t button) {
|
||||
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
|
||||
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
|
||||
return SDL_BUTTON(button);
|
||||
return SDL_BUTTON_MASK(button);
|
||||
}
|
||||
|
||||
return SC_MOUSE_BUTTON_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
|
||||
bool forward_all_clicks) {
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
||||
assert(buttons_state < 0x100); // fits in uint8_t
|
||||
|
||||
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
|
||||
if (forward_all_clicks) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT
|
||||
| SC_MOUSE_BUTTON_MIDDLE
|
||||
| SC_MOUSE_BUTTON_X1
|
||||
| SC_MOUSE_BUTTON_X2;
|
||||
}
|
||||
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
|
||||
return buttons_state;
|
||||
}
|
||||
|
||||
return buttons_state & mask;
|
||||
static inline enum sc_gamepad_axis
|
||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
if (axis <= SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
|
||||
// SC_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_GAMEPAD_AXIS_*
|
||||
return axis;
|
||||
}
|
||||
return SC_GAMEPAD_AXIS_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum sc_gamepad_button
|
||||
sc_gamepad_button_from_sdl(uint8_t button) {
|
||||
if (button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
|
||||
// SC_GAMEPAD_BUTTON_* constants are initialized from
|
||||
// SDL_GAMEPAD_BUTTON_*
|
||||
return button;
|
||||
}
|
||||
return SC_GAMEPAD_BUTTON_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_gamepad_button_type(uint32_t type) {
|
||||
assert(type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || type == SDL_EVENT_GAMEPAD_BUTTON_UP);
|
||||
if (type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,14 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_keycode.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "file_pusher.h"
|
||||
#include "fps_counter.h"
|
||||
#include "options.h"
|
||||
#include "trait/gamepad_processor.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
@@ -21,17 +22,21 @@ struct sc_input_manager {
|
||||
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
|
||||
bool forward_all_clicks;
|
||||
bool camera;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
|
||||
struct {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
} sdl_shortcut_mods;
|
||||
uint16_t sdl_shortcut_mods;
|
||||
|
||||
bool vfinger_down;
|
||||
bool vfinger_invert_x;
|
||||
bool vfinger_invert_y;
|
||||
|
||||
uint8_t mouse_buttons_state; // OR of enum sc_mouse_button values
|
||||
|
||||
// Tracks the number of identical consecutive shortcut key down events.
|
||||
// Not to be confused with event->repeat, which counts the number of
|
||||
@@ -49,11 +54,13 @@ struct sc_input_manager_params {
|
||||
struct sc_screen *screen;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
bool camera;
|
||||
|
||||
bool forward_all_clicks;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
#include "keyboard_inject.h"
|
||||
#include "keyboard_sdk.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "android/input.h"
|
||||
#include "android/keycodes.h"
|
||||
#include "control_msg.h"
|
||||
#include "controller.h"
|
||||
#include "input_events.h"
|
||||
#include "util/intmap.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast key processor to sc_keyboard_inject */
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
|
||||
/** Downcast key processor to sc_keyboard_sdk */
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_sdk, key_processor)
|
||||
|
||||
static enum android_keyevent_action
|
||||
convert_keycode_action(enum sc_action action) {
|
||||
@@ -45,6 +50,10 @@ convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
|
||||
{SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT},
|
||||
{SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT},
|
||||
{SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT},
|
||||
{SC_KEYCODE_LALT, AKEYCODE_ALT_LEFT},
|
||||
{SC_KEYCODE_RALT, AKEYCODE_ALT_RIGHT},
|
||||
{SC_KEYCODE_LGUI, AKEYCODE_META_LEFT},
|
||||
{SC_KEYCODE_RGUI, AKEYCODE_META_RIGHT},
|
||||
};
|
||||
|
||||
// Numpad navigation keys.
|
||||
@@ -166,11 +175,7 @@ convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if ALT and META are not pressed, also handle letters and space
|
||||
// Handle letters and space
|
||||
entry = SC_INTMAP_FIND_ENTRY(alphaspace_keys, from);
|
||||
if (entry) {
|
||||
*to = entry->value;
|
||||
@@ -271,20 +276,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
// is set before injecting Ctrl+v.
|
||||
(void) ack_to_wait;
|
||||
|
||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||
struct sc_keyboard_sdk *kb = DOWNCAST(kp);
|
||||
|
||||
if (event->repeat) {
|
||||
if (!ki->forward_key_repeat) {
|
||||
if (!kb->forward_key_repeat) {
|
||||
return;
|
||||
}
|
||||
++ki->repeat;
|
||||
++kb->repeat;
|
||||
} else {
|
||||
ki->repeat = 0;
|
||||
kb->repeat = 0;
|
||||
}
|
||||
|
||||
struct sc_control_msg msg;
|
||||
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
|
||||
if (!sc_controller_push_msg(ki->controller, &msg)) {
|
||||
if (convert_input_key(event, &msg, kb->key_inject_mode, kb->repeat)) {
|
||||
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||
LOGW("Could not request 'inject keycode'");
|
||||
}
|
||||
}
|
||||
@@ -293,14 +298,14 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
static void
|
||||
sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||
const struct sc_text_event *event) {
|
||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||
struct sc_keyboard_sdk *kb = DOWNCAST(kp);
|
||||
|
||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
||||
if (kb->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
||||
// Never inject text events
|
||||
return;
|
||||
}
|
||||
|
||||
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
|
||||
if (kb->key_inject_mode == SC_KEY_INJECT_MODE_MIXED) {
|
||||
char c = event->text[0];
|
||||
if (isalpha(c) || c == ' ') {
|
||||
assert(event->text[1] == '\0');
|
||||
@@ -316,22 +321,22 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||
LOGW("Could not strdup input text");
|
||||
return;
|
||||
}
|
||||
if (!sc_controller_push_msg(ki->controller, &msg)) {
|
||||
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||
free(msg.inject_text.text);
|
||||
LOGW("Could not request 'inject text'");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||
struct sc_controller *controller,
|
||||
enum sc_key_inject_mode key_inject_mode,
|
||||
bool forward_key_repeat) {
|
||||
ki->controller = controller;
|
||||
ki->key_inject_mode = key_inject_mode;
|
||||
ki->forward_key_repeat = forward_key_repeat;
|
||||
sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
|
||||
struct sc_controller *controller,
|
||||
enum sc_key_inject_mode key_inject_mode,
|
||||
bool forward_key_repeat) {
|
||||
kb->controller = controller;
|
||||
kb->key_inject_mode = key_inject_mode;
|
||||
kb->forward_key_repeat = forward_key_repeat;
|
||||
|
||||
ki->repeat = 0;
|
||||
kb->repeat = 0;
|
||||
|
||||
static const struct sc_key_processor_ops ops = {
|
||||
.process_key = sc_key_processor_process_key,
|
||||
@@ -339,6 +344,7 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||
};
|
||||
|
||||
// Key injection and clipboard synchronization are serialized
|
||||
ki->key_processor.async_paste = false;
|
||||
ki->key_processor.ops = &ops;
|
||||
kb->key_processor.async_paste = false;
|
||||
kb->key_processor.hid = false;
|
||||
kb->key_processor.ops = &ops;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SC_KEYBOARD_INJECT_H
|
||||
#define SC_KEYBOARD_INJECT_H
|
||||
#ifndef SC_KEYBOARD_SDK_H
|
||||
#define SC_KEYBOARD_SDK_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "options.h"
|
||||
#include "trait/key_processor.h"
|
||||
|
||||
struct sc_keyboard_inject {
|
||||
struct sc_keyboard_sdk {
|
||||
struct sc_key_processor key_processor; // key processor trait
|
||||
|
||||
struct sc_controller *controller;
|
||||
@@ -23,9 +23,9 @@ struct sc_keyboard_inject {
|
||||
};
|
||||
|
||||
void
|
||||
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
||||
struct sc_controller *controller,
|
||||
enum sc_key_inject_mode key_inject_mode,
|
||||
bool forward_key_repeat);
|
||||
sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
|
||||
struct sc_controller *controller,
|
||||
enum sc_key_inject_mode key_inject_mode,
|
||||
bool forward_key_repeat);
|
||||
|
||||
#endif
|
||||
@@ -1,21 +1,21 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <stdio.h>
|
||||
#ifdef HAVE_V4L2
|
||||
# include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "usb/scrcpy_otg.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/scrcpy_otg.h"
|
||||
#endif
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "util/str.h"
|
||||
#endif
|
||||
|
||||
int
|
||||
static int
|
||||
main_scrcpy(int argc, char *argv[]) {
|
||||
#ifdef _WIN32
|
||||
// disable buffering, we want logs immediately
|
||||
@@ -39,28 +39,37 @@ main_scrcpy(int argc, char *argv[]) {
|
||||
.opts = scrcpy_options_default,
|
||||
.help = false,
|
||||
.version = false,
|
||||
.pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
args.opts.log_level = SC_LOG_LEVEL_DEBUG;
|
||||
#endif
|
||||
|
||||
enum scrcpy_exit_code ret;
|
||||
|
||||
if (!scrcpy_parse_args(&args, argc, argv)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
ret = SCRCPY_EXIT_FAILURE;
|
||||
goto end;
|
||||
}
|
||||
|
||||
sc_set_log_level(args.opts.log_level);
|
||||
|
||||
if (args.help) {
|
||||
scrcpy_print_usage(argv[0]);
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
ret = SCRCPY_EXIT_SUCCESS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (args.version) {
|
||||
scrcpy_print_version();
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
ret = SCRCPY_EXIT_SUCCESS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
// The current thread is the main thread
|
||||
SC_MAIN_THREAD_ID = sc_thread_get_id();
|
||||
|
||||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||
av_register_all();
|
||||
#endif
|
||||
@@ -72,18 +81,26 @@ main_scrcpy(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
if (!net_init()) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
ret = SCRCPY_EXIT_FAILURE;
|
||||
goto end;
|
||||
}
|
||||
|
||||
sc_log_configure();
|
||||
|
||||
#ifdef HAVE_USB
|
||||
enum scrcpy_exit_code ret = args.opts.otg ? scrcpy_otg(&args.opts)
|
||||
: scrcpy(&args.opts);
|
||||
ret = args.opts.otg ? scrcpy_otg(&args.opts) : scrcpy(&args.opts);
|
||||
#else
|
||||
enum scrcpy_exit_code ret = scrcpy(&args.opts);
|
||||
ret = scrcpy(&args.opts);
|
||||
#endif
|
||||
|
||||
end:
|
||||
if (args.pause_on_exit == SC_PAUSE_ON_EXIT_TRUE ||
|
||||
(args.pause_on_exit == SC_PAUSE_ON_EXIT_IF_ERROR &&
|
||||
ret != SCRCPY_EXIT_SUCCESS)) {
|
||||
printf("Press Enter to continue...\n");
|
||||
getchar();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
100
app/src/mouse_capture.c
Normal file
100
app/src/mouse_capture.c
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "mouse_capture.h"
|
||||
|
||||
#include "shortcut_mod.h"
|
||||
#include "util/log.h"
|
||||
|
||||
void
|
||||
sc_mouse_capture_init(struct sc_mouse_capture *mc, SDL_Window *window,
|
||||
uint8_t shortcut_mods) {
|
||||
mc->window = window;
|
||||
mc->sdl_mouse_capture_keys = sc_shortcut_mods_to_sdl(shortcut_mods);
|
||||
mc->mouse_capture_key_pressed = SDLK_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
sc_mouse_capture_is_capture_key(struct sc_mouse_capture *mc, SDL_Keycode key) {
|
||||
return sc_shortcut_mods_is_shortcut_key(mc->sdl_mouse_capture_keys, key);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
const SDL_Event *event) {
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||
sc_mouse_capture_set_active(mc, false);
|
||||
return true;
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
SDL_Keycode key = event->key.key;
|
||||
if (sc_mouse_capture_is_capture_key(mc, key)) {
|
||||
if (!mc->mouse_capture_key_pressed) {
|
||||
mc->mouse_capture_key_pressed = key;
|
||||
} else {
|
||||
// Another mouse capture key has been pressed, cancel
|
||||
// mouse (un)capture
|
||||
mc->mouse_capture_key_pressed = 0;
|
||||
}
|
||||
// Mouse capture keys are never forwarded to the device
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
SDL_Keycode key = event->key.key;
|
||||
SDL_Keycode cap = mc->mouse_capture_key_pressed;
|
||||
mc->mouse_capture_key_pressed = 0;
|
||||
if (sc_mouse_capture_is_capture_key(mc, key)) {
|
||||
if (key == cap) {
|
||||
// A mouse capture key has been pressed then released:
|
||||
// toggle the capture mouse mode
|
||||
sc_mouse_capture_toggle(mc);
|
||||
}
|
||||
// Mouse capture keys are never forwarded to the device
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (!sc_mouse_capture_is_active(mc)) {
|
||||
// The mouse will be captured on SDL_EVENT_MOUSE_BUTTON_UP, so
|
||||
// consume the event
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
if (!sc_mouse_capture_is_active(mc)) {
|
||||
sc_mouse_capture_set_active(mc, true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
// Touch events are not compatible with relative mode
|
||||
// (coordinates are not relative), so consume the event
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
|
||||
bool ok = SDL_SetWindowRelativeMouseMode(mc->window, capture);
|
||||
if (!ok) {
|
||||
LOGE("Could not set relative mouse mode to %s: %s",
|
||||
capture ? "true" : "false", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_mouse_capture_is_active(struct sc_mouse_capture *mc) {
|
||||
return SDL_GetWindowRelativeMouseMode(mc->window);
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_capture_toggle(struct sc_mouse_capture *mc) {
|
||||
bool new_value = !sc_mouse_capture_is_active(mc);
|
||||
sc_mouse_capture_set_active(mc, new_value);
|
||||
}
|
||||
38
app/src/mouse_capture.h
Normal file
38
app/src/mouse_capture.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef SC_MOUSE_CAPTURE_H
|
||||
#define SC_MOUSE_CAPTURE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
struct sc_mouse_capture {
|
||||
SDL_Window *window;
|
||||
uint16_t sdl_mouse_capture_keys;
|
||||
|
||||
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
|
||||
// RGUI) must be pressed. This variable tracks the pressed capture key.
|
||||
SDL_Keycode mouse_capture_key_pressed;
|
||||
|
||||
};
|
||||
|
||||
void
|
||||
sc_mouse_capture_init(struct sc_mouse_capture *mc, SDL_Window *window,
|
||||
uint8_t shortcut_mods);
|
||||
|
||||
void
|
||||
sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture);
|
||||
|
||||
bool
|
||||
sc_mouse_capture_is_active(struct sc_mouse_capture *mc);
|
||||
|
||||
void
|
||||
sc_mouse_capture_toggle(struct sc_mouse_capture *mc);
|
||||
|
||||
// Return true if it consumed the event
|
||||
bool
|
||||
sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
const SDL_Event *event);
|
||||
|
||||
#endif
|
||||
@@ -1,16 +1,16 @@
|
||||
#include "mouse_inject.h"
|
||||
#include "mouse_sdk.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "android/input.h"
|
||||
#include "control_msg.h"
|
||||
#include "controller.h"
|
||||
#include "input_events.h"
|
||||
#include "util/intmap.h"
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast mouse processor to sc_mouse_inject */
|
||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor)
|
||||
/** Downcast mouse processor to sc_mouse_sdk */
|
||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_sdk, mouse_processor)
|
||||
|
||||
static enum android_motionevent_buttons
|
||||
convert_mouse_buttons(uint32_t state) {
|
||||
@@ -58,17 +58,18 @@ convert_touch_action(enum sc_touch_action action) {
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
if (!event->buttons_state) {
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
if (!m->mouse_hover && !event->buttons_state) {
|
||||
// Do not send motion events when no click is pressed
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
.inject_touch_event = {
|
||||
.action = AMOTION_EVENT_ACTION_MOVE,
|
||||
.action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE
|
||||
: AMOTION_EVENT_ACTION_HOVER_MOVE,
|
||||
.pointer_id = event->pointer_id,
|
||||
.position = event->position,
|
||||
.pressure = 1.f,
|
||||
@@ -76,7 +77,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
},
|
||||
};
|
||||
|
||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
||||
if (!sc_controller_push_msg(m->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse motion event'");
|
||||
}
|
||||
}
|
||||
@@ -84,7 +85,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_click_event *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
@@ -98,7 +99,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
},
|
||||
};
|
||||
|
||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
||||
if (!sc_controller_push_msg(m->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse click event'");
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||
@@ -118,7 +119,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
},
|
||||
};
|
||||
|
||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
||||
if (!sc_controller_push_msg(m->controller, &msg)) {
|
||||
LOGW("Could not request 'inject mouse scroll event'");
|
||||
}
|
||||
}
|
||||
@@ -126,7 +127,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
static void
|
||||
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||
const struct sc_touch_event *event) {
|
||||
struct sc_mouse_inject *mi = DOWNCAST(mp);
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
@@ -139,15 +140,16 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||
},
|
||||
};
|
||||
|
||||
if (!sc_controller_push_msg(mi->controller, &msg)) {
|
||||
if (!sc_controller_push_msg(m->controller, &msg)) {
|
||||
LOGW("Could not request 'inject touch event'");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
struct sc_controller *controller) {
|
||||
mi->controller = controller;
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||
bool mouse_hover) {
|
||||
m->controller = controller;
|
||||
m->mouse_hover = mouse_hover;
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
@@ -156,7 +158,7 @@ sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
.process_touch = sc_mouse_processor_process_touch,
|
||||
};
|
||||
|
||||
mi->mouse_processor.ops = &ops;
|
||||
m->mouse_processor.ops = &ops;
|
||||
|
||||
mi->mouse_processor.relative_mode = false;
|
||||
m->mouse_processor.relative_mode = false;
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
#ifndef SC_MOUSE_INJECT_H
|
||||
#define SC_MOUSE_INJECT_H
|
||||
#ifndef SC_MOUSE_SDK_H
|
||||
#define SC_MOUSE_SDK_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "screen.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
struct sc_mouse_inject {
|
||||
struct sc_mouse_sdk {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_controller *controller;
|
||||
bool mouse_hover;
|
||||
};
|
||||
|
||||
void
|
||||
sc_mouse_inject_init(struct sc_mouse_inject *mi,
|
||||
struct sc_controller *controller);
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||
bool mouse_hover);
|
||||
|
||||
#endif
|
||||
@@ -2,21 +2,30 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "SDL2/SDL.h"
|
||||
#include <string.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
void
|
||||
sc_opengl_init(struct sc_opengl *gl) {
|
||||
gl->GetString = SDL_GL_GetProcAddress("glGetString");
|
||||
gl->GetString = (const GLubyte *(*)(GLenum))
|
||||
SDL_GL_GetProcAddress("glGetString");
|
||||
assert(gl->GetString);
|
||||
|
||||
gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf");
|
||||
gl->BindTexture = (void (*)(GLenum, GLuint))
|
||||
SDL_GL_GetProcAddress("glBindTexture");
|
||||
assert(gl->BindTexture);
|
||||
|
||||
gl->TexParameterf = (void (*)(GLenum, GLenum, GLfloat))
|
||||
SDL_GL_GetProcAddress("glTexParameterf");
|
||||
assert(gl->TexParameterf);
|
||||
|
||||
gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri");
|
||||
gl->TexParameteri = (void (*)(GLenum, GLenum, GLint))
|
||||
SDL_GL_GetProcAddress("glTexParameteri");
|
||||
assert(gl->TexParameteri);
|
||||
|
||||
// optional
|
||||
gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap");
|
||||
gl->GenerateMipmap = (void (*)(GLenum))
|
||||
SDL_GL_GetProcAddress("glGenerateMipmap");
|
||||
|
||||
const char *version = (const char *) gl->GetString(GL_VERSION);
|
||||
assert(version);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
|
||||
struct sc_opengl {
|
||||
const char *version;
|
||||
@@ -15,6 +15,9 @@ struct sc_opengl {
|
||||
const GLubyte *
|
||||
(*GetString)(GLenum name);
|
||||
|
||||
void
|
||||
(*BindTexture)(GLenum target, GLuint texture);
|
||||
|
||||
void
|
||||
(*TexParameterf)(GLenum target, GLenum pname, GLfloat param);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "options.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
const struct scrcpy_options scrcpy_options_default = {
|
||||
.serial = NULL,
|
||||
.crop = NULL,
|
||||
@@ -11,38 +13,61 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.audio_codec_options = NULL,
|
||||
.video_encoder = NULL,
|
||||
.audio_encoder = NULL,
|
||||
.camera_id = NULL,
|
||||
.camera_size = NULL,
|
||||
.camera_ar = NULL,
|
||||
.camera_zoom = NULL,
|
||||
.camera_fps = 0,
|
||||
.log_level = SC_LOG_LEVEL_INFO,
|
||||
.video_codec = SC_CODEC_H264,
|
||||
.audio_codec = SC_CODEC_OPUS,
|
||||
.audio_source = SC_AUDIO_SOURCE_OUTPUT,
|
||||
.video_source = SC_VIDEO_SOURCE_DISPLAY,
|
||||
.audio_source = SC_AUDIO_SOURCE_AUTO,
|
||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
|
||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
.mouse_bindings = {
|
||||
.pri = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||
},
|
||||
.sec = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||
},
|
||||
},
|
||||
.camera_facing = SC_CAMERA_FACING_ANY,
|
||||
.port_range = {
|
||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
||||
},
|
||||
.tunnel_host = 0,
|
||||
.tunnel_port = 0,
|
||||
.shortcut_mods = {
|
||||
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
|
||||
.count = 2,
|
||||
},
|
||||
.shortcut_mods = SC_SHORTCUT_MOD_LALT | SC_SHORTCUT_MOD_LSUPER,
|
||||
.max_size = 0,
|
||||
.video_bit_rate = 0,
|
||||
.audio_bit_rate = 0,
|
||||
.max_fps = 0,
|
||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
||||
.rotation = 0,
|
||||
.max_fps = NULL,
|
||||
.capture_orientation = SC_ORIENTATION_0,
|
||||
.capture_orientation_lock = SC_ORIENTATION_UNLOCKED,
|
||||
.display_orientation = SC_ORIENTATION_0,
|
||||
.record_orientation = SC_ORIENTATION_0,
|
||||
.display_ime_policy = SC_DISPLAY_IME_POLICY_UNDEFINED,
|
||||
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
||||
.window_y = SC_WINDOW_POSITION_UNDEFINED,
|
||||
.window_width = 0,
|
||||
.window_height = 0,
|
||||
.display_id = 0,
|
||||
.display_buffer = 0,
|
||||
.audio_buffer = SC_TICK_FROM_MS(50),
|
||||
.video_buffer = 0,
|
||||
.audio_buffer = -1, // depends on the audio format,
|
||||
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||
.time_limit = 0,
|
||||
.screen_off_timeout = -1,
|
||||
#ifdef HAVE_V4L2
|
||||
.v4l2_device = NULL,
|
||||
.v4l2_buffer = 0,
|
||||
@@ -64,7 +89,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.force_adb_forward = false,
|
||||
.disable_screensaver = false,
|
||||
.forward_key_repeat = true,
|
||||
.forward_all_clicks = false,
|
||||
.legacy_paste = false,
|
||||
.power_off_on_close = false,
|
||||
.clipboard_autosync = true,
|
||||
@@ -79,7 +103,52 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.video = true,
|
||||
.audio = true,
|
||||
.require_audio = false,
|
||||
.list_encoders = false,
|
||||
.list_displays = false,
|
||||
.kill_adb_on_close = false,
|
||||
.camera_high_speed = false,
|
||||
.list = 0,
|
||||
.window = true,
|
||||
.mouse_hover = true,
|
||||
.audio_dup = false,
|
||||
.new_display = NULL,
|
||||
.start_app = NULL,
|
||||
.angle = NULL,
|
||||
.vd_destroy_content = true,
|
||||
.vd_system_decorations = true,
|
||||
.camera_torch = false,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform) {
|
||||
assert(!(src & ~7));
|
||||
assert(!(transform & ~7));
|
||||
|
||||
unsigned transform_hflip = transform & 4;
|
||||
unsigned transform_rotation = transform & 3;
|
||||
unsigned src_hflip = src & 4;
|
||||
unsigned src_rotation = src & 3;
|
||||
unsigned src_swap = src & 1;
|
||||
if (src_swap && transform_hflip) {
|
||||
// If the src is rotated by 90 or 270 degrees, applying a flipped
|
||||
// transformation requires an additional 180 degrees rotation to
|
||||
// compensate for the inversion of the order of multiplication:
|
||||
//
|
||||
// hflip1 × rotate1 × hflip2 × rotate2
|
||||
// `--------------' `--------------'
|
||||
// src transform
|
||||
//
|
||||
// In the final result, we want all the hflips then all the rotations,
|
||||
// so we must move hflip2 to the left:
|
||||
//
|
||||
// hflip1 × hflip2 × rotate1' × rotate2
|
||||
//
|
||||
// with rotate1' = | rotate1 if src is 0° or 180°
|
||||
// | rotate1 + 180° if src is 90° or 270°
|
||||
|
||||
src_rotation += 2;
|
||||
}
|
||||
|
||||
unsigned result_hflip = src_hflip ^ transform_hflip;
|
||||
unsigned result_rotation = (transform_rotation + src_rotation) % 4;
|
||||
enum sc_orientation result = result_hflip | result_rotation;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/tick.h"
|
||||
@@ -25,6 +25,8 @@ enum sc_record_format {
|
||||
SC_RECORD_FORMAT_MKA,
|
||||
SC_RECORD_FORMAT_OPUS,
|
||||
SC_RECORD_FORMAT_AAC,
|
||||
SC_RECORD_FORMAT_FLAC,
|
||||
SC_RECORD_FORMAT_WAV,
|
||||
};
|
||||
|
||||
static inline bool
|
||||
@@ -32,7 +34,9 @@ sc_record_format_is_audio_only(enum sc_record_format fmt) {
|
||||
return fmt == SC_RECORD_FORMAT_M4A
|
||||
|| fmt == SC_RECORD_FORMAT_MKA
|
||||
|| fmt == SC_RECORD_FORMAT_OPUS
|
||||
|| fmt == SC_RECORD_FORMAT_AAC;
|
||||
|| fmt == SC_RECORD_FORMAT_AAC
|
||||
|| fmt == SC_RECORD_FORMAT_FLAC
|
||||
|| fmt == SC_RECORD_FORMAT_WAV;
|
||||
}
|
||||
|
||||
enum sc_codec {
|
||||
@@ -41,32 +45,156 @@ enum sc_codec {
|
||||
SC_CODEC_AV1,
|
||||
SC_CODEC_OPUS,
|
||||
SC_CODEC_AAC,
|
||||
SC_CODEC_FLAC,
|
||||
SC_CODEC_RAW,
|
||||
};
|
||||
|
||||
enum sc_video_source {
|
||||
SC_VIDEO_SOURCE_DISPLAY,
|
||||
SC_VIDEO_SOURCE_CAMERA,
|
||||
};
|
||||
|
||||
enum sc_audio_source {
|
||||
SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA
|
||||
SC_AUDIO_SOURCE_OUTPUT,
|
||||
SC_AUDIO_SOURCE_MIC,
|
||||
SC_AUDIO_SOURCE_PLAYBACK,
|
||||
SC_AUDIO_SOURCE_MIC_UNPROCESSED,
|
||||
SC_AUDIO_SOURCE_MIC_CAMCORDER,
|
||||
SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION,
|
||||
SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL_UPLINK,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK,
|
||||
SC_AUDIO_SOURCE_VOICE_PERFORMANCE,
|
||||
};
|
||||
|
||||
enum sc_lock_video_orientation {
|
||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||
// lock the current orientation when scrcpy starts
|
||||
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||
SC_LOCK_VIDEO_ORIENTATION_1,
|
||||
SC_LOCK_VIDEO_ORIENTATION_2,
|
||||
SC_LOCK_VIDEO_ORIENTATION_3,
|
||||
enum sc_camera_facing {
|
||||
SC_CAMERA_FACING_ANY,
|
||||
SC_CAMERA_FACING_FRONT,
|
||||
SC_CAMERA_FACING_BACK,
|
||||
SC_CAMERA_FACING_EXTERNAL,
|
||||
};
|
||||
|
||||
// ,----- hflip (applied before the rotation)
|
||||
// | ,--- 180°
|
||||
// | | ,- 90° clockwise
|
||||
// | | |
|
||||
enum sc_orientation { // v v v
|
||||
SC_ORIENTATION_0, // 0 0 0
|
||||
SC_ORIENTATION_90, // 0 0 1
|
||||
SC_ORIENTATION_180, // 0 1 0
|
||||
SC_ORIENTATION_270, // 0 1 1
|
||||
SC_ORIENTATION_FLIP_0, // 1 0 0
|
||||
SC_ORIENTATION_FLIP_90, // 1 0 1
|
||||
SC_ORIENTATION_FLIP_180, // 1 1 0
|
||||
SC_ORIENTATION_FLIP_270, // 1 1 1
|
||||
};
|
||||
|
||||
enum sc_orientation_lock {
|
||||
SC_ORIENTATION_UNLOCKED,
|
||||
SC_ORIENTATION_LOCKED_VALUE, // lock to specified orientation
|
||||
SC_ORIENTATION_LOCKED_INITIAL, // lock to initial device orientation
|
||||
};
|
||||
|
||||
enum sc_display_ime_policy {
|
||||
SC_DISPLAY_IME_POLICY_UNDEFINED,
|
||||
SC_DISPLAY_IME_POLICY_LOCAL,
|
||||
SC_DISPLAY_IME_POLICY_FALLBACK,
|
||||
SC_DISPLAY_IME_POLICY_HIDE,
|
||||
};
|
||||
|
||||
static inline bool
|
||||
sc_orientation_is_mirror(enum sc_orientation orientation) {
|
||||
assert(!(orientation & ~7));
|
||||
return orientation & 4;
|
||||
}
|
||||
|
||||
// Does the orientation swap width and height?
|
||||
static inline bool
|
||||
sc_orientation_is_swap(enum sc_orientation orientation) {
|
||||
assert(!(orientation & ~7));
|
||||
return orientation & 1;
|
||||
}
|
||||
|
||||
static inline enum sc_orientation
|
||||
sc_orientation_get_rotation(enum sc_orientation orientation) {
|
||||
assert(!(orientation & ~7));
|
||||
return orientation & 3;
|
||||
}
|
||||
|
||||
enum sc_orientation
|
||||
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform);
|
||||
|
||||
static inline const char *
|
||||
sc_orientation_get_name(enum sc_orientation orientation) {
|
||||
switch (orientation) {
|
||||
case SC_ORIENTATION_0:
|
||||
return "0";
|
||||
case SC_ORIENTATION_90:
|
||||
return "90";
|
||||
case SC_ORIENTATION_180:
|
||||
return "180";
|
||||
case SC_ORIENTATION_270:
|
||||
return "270";
|
||||
case SC_ORIENTATION_FLIP_0:
|
||||
return "flip0";
|
||||
case SC_ORIENTATION_FLIP_90:
|
||||
return "flip90";
|
||||
case SC_ORIENTATION_FLIP_180:
|
||||
return "flip180";
|
||||
case SC_ORIENTATION_FLIP_270:
|
||||
return "flip270";
|
||||
default:
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_INJECT,
|
||||
SC_KEYBOARD_INPUT_MODE_HID,
|
||||
SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_KEYBOARD_INPUT_MODE_DISABLED,
|
||||
SC_KEYBOARD_INPUT_MODE_SDK,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID,
|
||||
SC_KEYBOARD_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_INJECT,
|
||||
SC_MOUSE_INPUT_MODE_HID,
|
||||
SC_MOUSE_INPUT_MODE_AUTO,
|
||||
SC_MOUSE_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_MOUSE_INPUT_MODE_DISABLED,
|
||||
SC_MOUSE_INPUT_MODE_SDK,
|
||||
SC_MOUSE_INPUT_MODE_UHID,
|
||||
SC_MOUSE_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_gamepad_input_mode {
|
||||
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_mouse_binding {
|
||||
SC_MOUSE_BINDING_AUTO,
|
||||
SC_MOUSE_BINDING_DISABLED,
|
||||
SC_MOUSE_BINDING_CLICK,
|
||||
SC_MOUSE_BINDING_BACK,
|
||||
SC_MOUSE_BINDING_HOME,
|
||||
SC_MOUSE_BINDING_APP_SWITCH,
|
||||
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||
};
|
||||
|
||||
struct sc_mouse_binding_set {
|
||||
enum sc_mouse_binding right_click;
|
||||
enum sc_mouse_binding middle_click;
|
||||
enum sc_mouse_binding click4;
|
||||
enum sc_mouse_binding click5;
|
||||
};
|
||||
|
||||
struct sc_mouse_bindings {
|
||||
struct sc_mouse_binding_set pri;
|
||||
struct sc_mouse_binding_set sec; // When Shift is pressed
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
@@ -83,8 +211,6 @@ enum sc_key_inject_mode {
|
||||
SC_KEY_INJECT_MODE_RAW,
|
||||
};
|
||||
|
||||
#define SC_MAX_SHORTCUT_MODS 8
|
||||
|
||||
enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
|
||||
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
|
||||
@@ -94,11 +220,6 @@ enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
|
||||
};
|
||||
|
||||
struct sc_shortcut_mods {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
struct sc_port_range {
|
||||
uint16_t first;
|
||||
uint16_t last;
|
||||
@@ -117,32 +238,46 @@ struct scrcpy_options {
|
||||
const char *audio_codec_options;
|
||||
const char *video_encoder;
|
||||
const char *audio_encoder;
|
||||
const char *camera_id;
|
||||
const char *camera_size;
|
||||
const char *camera_ar;
|
||||
const char *camera_zoom;
|
||||
uint16_t camera_fps;
|
||||
enum sc_log_level log_level;
|
||||
enum sc_codec video_codec;
|
||||
enum sc_codec audio_codec;
|
||||
enum sc_video_source video_source;
|
||||
enum sc_audio_source audio_source;
|
||||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
enum sc_gamepad_input_mode gamepad_input_mode;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
enum sc_camera_facing camera_facing;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
struct sc_shortcut_mods shortcut_mods;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
uint16_t max_size;
|
||||
uint32_t video_bit_rate;
|
||||
uint32_t audio_bit_rate;
|
||||
uint16_t max_fps;
|
||||
enum sc_lock_video_orientation lock_video_orientation;
|
||||
uint8_t rotation;
|
||||
const char *max_fps; // float to be parsed by the server
|
||||
const char *angle; // float to be parsed by the server
|
||||
enum sc_orientation capture_orientation;
|
||||
enum sc_orientation_lock capture_orientation_lock;
|
||||
enum sc_orientation display_orientation;
|
||||
enum sc_orientation record_orientation;
|
||||
enum sc_display_ime_policy display_ime_policy;
|
||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
uint32_t display_id;
|
||||
sc_tick display_buffer;
|
||||
sc_tick video_buffer;
|
||||
sc_tick audio_buffer;
|
||||
sc_tick audio_output_buffer;
|
||||
sc_tick time_limit;
|
||||
sc_tick screen_off_timeout;
|
||||
#ifdef HAVE_V4L2
|
||||
const char *v4l2_device;
|
||||
sc_tick v4l2_buffer;
|
||||
@@ -164,7 +299,6 @@ struct scrcpy_options {
|
||||
bool force_adb_forward;
|
||||
bool disable_screensaver;
|
||||
bool forward_key_repeat;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool power_off_on_close;
|
||||
bool clipboard_autosync;
|
||||
@@ -179,9 +313,22 @@ struct scrcpy_options {
|
||||
bool video;
|
||||
bool audio;
|
||||
bool require_audio;
|
||||
bool list_encoders;
|
||||
bool list_displays;
|
||||
bool kill_adb_on_close;
|
||||
bool camera_high_speed;
|
||||
#define SC_OPTION_LIST_ENCODERS 0x1
|
||||
#define SC_OPTION_LIST_DISPLAYS 0x2
|
||||
#define SC_OPTION_LIST_CAMERAS 0x4
|
||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||
#define SC_OPTION_LIST_APPS 0x10
|
||||
uint8_t list;
|
||||
bool window;
|
||||
bool mouse_hover;
|
||||
bool audio_dup;
|
||||
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
|
||||
const char *start_app;
|
||||
bool vd_destroy_content;
|
||||
bool vd_system_decorations;
|
||||
bool camera_torch;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "packet_merger.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/avutil.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
void
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/packet.h>
|
||||
|
||||
/**
|
||||
* Config packets (containing the SPS/PPS) are sent in-band. A new config
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
#include "receiver.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_clipboard.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL3/SDL_clipboard.h>
|
||||
|
||||
#include "device_msg.h"
|
||||
#include "events.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
struct sc_uhid_output_task_data {
|
||||
struct sc_uhid_devices *uhid_devices;
|
||||
uint16_t id;
|
||||
uint16_t size;
|
||||
uint8_t *data;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
struct sc_acksync *acksync) {
|
||||
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = sc_mutex_init(&receiver->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
receiver->control_socket = control_socket;
|
||||
receiver->acksync = acksync;
|
||||
receiver->acksync = NULL;
|
||||
receiver->uhid_devices = NULL;
|
||||
|
||||
assert(cbs && cbs->on_ended);
|
||||
receiver->cbs = cbs;
|
||||
receiver->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -26,36 +43,127 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
|
||||
}
|
||||
|
||||
static void
|
||||
process_msg(struct sc_receiver *receiver, struct device_msg *msg) {
|
||||
task_set_clipboard(void *userdata) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
char *text = userdata;
|
||||
|
||||
char *current = SDL_GetClipboardText();
|
||||
bool same = current && !strcmp(current, text);
|
||||
SDL_free(current);
|
||||
if (same) {
|
||||
LOGD("Computer clipboard unchanged");
|
||||
} else {
|
||||
bool ok = SDL_SetClipboardText(text);
|
||||
if (ok) {
|
||||
LOGI("Device clipboard copied");
|
||||
} else {
|
||||
LOGE("Could not set clipboard: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
free(text);
|
||||
}
|
||||
|
||||
static void
|
||||
task_uhid_output(void *userdata) {
|
||||
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||
|
||||
struct sc_uhid_output_task_data *data = userdata;
|
||||
|
||||
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
|
||||
data->size);
|
||||
|
||||
free(data->data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void
|
||||
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||
switch (msg->type) {
|
||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||
char *current = SDL_GetClipboardText();
|
||||
bool same = current && !strcmp(current, msg->clipboard.text);
|
||||
SDL_free(current);
|
||||
if (same) {
|
||||
LOGD("Computer clipboard unchanged");
|
||||
// Take ownership of the text (do not destroy the msg)
|
||||
char *text = msg->clipboard.text;
|
||||
|
||||
bool ok = sc_post_to_main_thread(task_set_clipboard, text);
|
||||
if (!ok) {
|
||||
LOGW("Could not post clipboard to main thread");
|
||||
free(text);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI("Device clipboard copied");
|
||||
SDL_SetClipboardText(msg->clipboard.text);
|
||||
break;
|
||||
}
|
||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
||||
assert(receiver->acksync);
|
||||
LOGD("Ack device clipboard sequence=%" PRIu64_,
|
||||
msg->ack_clipboard.sequence);
|
||||
|
||||
// This is a programming error to receive this message if there is
|
||||
// no ACK synchronization mechanism
|
||||
assert(receiver->acksync);
|
||||
|
||||
// Also check at runtime (do not trust the server)
|
||||
if (!receiver->acksync) {
|
||||
LOGE("Received unexpected ack");
|
||||
return;
|
||||
}
|
||||
|
||||
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
||||
// No allocation to free in the msg
|
||||
break;
|
||||
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
char *hex = sc_str_to_hex_string(msg->uhid_output.data,
|
||||
msg->uhid_output.size);
|
||||
if (hex) {
|
||||
LOGV("UHID output [%" PRIu16 "] %s",
|
||||
msg->uhid_output.id, hex);
|
||||
free(hex);
|
||||
} else {
|
||||
LOGV("UHID output [%" PRIu16 "] size=%" PRIu16,
|
||||
msg->uhid_output.id, msg->uhid_output.size);
|
||||
}
|
||||
}
|
||||
|
||||
if (!receiver->uhid_devices) {
|
||||
LOGE("Received unexpected HID output message");
|
||||
sc_device_msg_destroy(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
|
||||
if (!data) {
|
||||
LOG_OOM();
|
||||
return;
|
||||
}
|
||||
|
||||
// It is guaranteed that these pointers will still be valid when
|
||||
// the main thread will process them (the main thread will stop
|
||||
// processing SC_EVENT_RUN_ON_MAIN_THREAD on exit, when everything
|
||||
// gets deinitialized)
|
||||
data->uhid_devices = receiver->uhid_devices;
|
||||
data->id = msg->uhid_output.id;
|
||||
data->data = msg->uhid_output.data; // take ownership
|
||||
data->size = msg->uhid_output.size;
|
||||
|
||||
bool ok = sc_post_to_main_thread(task_uhid_output, data);
|
||||
if (!ok) {
|
||||
LOGW("Could not post UHID output to main thread");
|
||||
free(data->data);
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len) {
|
||||
process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
|
||||
size_t head = 0;
|
||||
for (;;) {
|
||||
struct device_msg msg;
|
||||
ssize_t r = device_msg_deserialize(&buf[head], len - head, &msg);
|
||||
struct sc_device_msg msg;
|
||||
ssize_t r = sc_device_msg_deserialize(&buf[head], len - head, &msg);
|
||||
if (r == -1) {
|
||||
return -1;
|
||||
}
|
||||
@@ -64,7 +172,7 @@ process_msgs(struct sc_receiver *receiver, const unsigned char *buf, size_t len)
|
||||
}
|
||||
|
||||
process_msg(receiver, &msg);
|
||||
device_msg_destroy(&msg);
|
||||
// the device msg must be destroyed by process_msg()
|
||||
|
||||
head += r;
|
||||
assert(head <= len);
|
||||
@@ -78,15 +186,18 @@ static int
|
||||
run_receiver(void *data) {
|
||||
struct sc_receiver *receiver = data;
|
||||
|
||||
static unsigned char buf[DEVICE_MSG_MAX_SIZE];
|
||||
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
|
||||
size_t head = 0;
|
||||
|
||||
bool error = false;
|
||||
|
||||
for (;;) {
|
||||
assert(head < DEVICE_MSG_MAX_SIZE);
|
||||
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
||||
DEVICE_MSG_MAX_SIZE - head);
|
||||
if (r <= 0) {
|
||||
LOGD("Receiver stopped");
|
||||
// device disconnected: keep error=false
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -94,6 +205,7 @@ run_receiver(void *data) {
|
||||
ssize_t consumed = process_msgs(receiver, buf, head);
|
||||
if (consumed == -1) {
|
||||
// an error occurred
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -104,6 +216,8 @@ run_receiver(void *data) {
|
||||
}
|
||||
}
|
||||
|
||||
receiver->cbs->on_ended(receiver, error, receiver->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "uhid/uhid_output.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
@@ -17,11 +18,19 @@ struct sc_receiver {
|
||||
sc_mutex mutex;
|
||||
|
||||
struct sc_acksync *acksync;
|
||||
struct sc_uhid_devices *uhid_devices;
|
||||
|
||||
const struct sc_receiver_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_receiver_callbacks {
|
||||
void (*on_ended)(struct sc_receiver *receiver, bool error, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
struct sc_acksync *acksync);
|
||||
const struct sc_receiver_callbacks *cbs, void *cbs_userdata);
|
||||
|
||||
void
|
||||
sc_receiver_destroy(struct sc_receiver *receiver);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user