mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-18 00:29:48 +00:00
Compare commits
704 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39a09ca565 | ||
|
|
5649d1ad95 | ||
|
|
1ba1108099 | ||
|
|
9b688909b0 | ||
|
|
270631f1b9 | ||
|
|
d86014e5ff | ||
|
|
1a407a67b1 | ||
|
|
98a327e99e | ||
|
|
db16efe68b | ||
|
|
800f063a1d | ||
|
|
e01666b6ad | ||
|
|
30a888711b | ||
|
|
62b27760ea | ||
|
|
328f9078ee | ||
|
|
5774e21965 | ||
|
|
2c909f0af7 | ||
|
|
f65aa6e089 | ||
|
|
b447b78b17 | ||
|
|
a450e0f8f5 | ||
|
|
0c9ecdfcb5 | ||
|
|
8e98f863b6 | ||
|
|
cc2104c699 | ||
|
|
253b0e2a7a | ||
|
|
c6af5be1dc | ||
|
|
06f6a96b56 | ||
|
|
89b73512ad | ||
|
|
b45f7a6733 | ||
|
|
1d99c821eb | ||
|
|
2993f7ae7d | ||
|
|
5454053d96 | ||
|
|
17d423db06 | ||
|
|
5ac53362a3 | ||
|
|
17b0e2cb22 | ||
|
|
96dc8c602c | ||
|
|
ead8069dae | ||
|
|
4e765b063a | ||
|
|
5dce97264b | ||
|
|
a00a6429d3 | ||
|
|
aafdec02df | ||
|
|
4e1c0cc0d0 | ||
|
|
33fed48277 | ||
|
|
b0f9d2f713 | ||
|
|
543d660ccc | ||
|
|
ecebcc4c35 | ||
|
|
af2dafbdac | ||
|
|
a56e363e39 | ||
|
|
06af101bf9 | ||
|
|
973806dd9c | ||
|
|
740dad943b | ||
|
|
4dfe4b8e2b | ||
|
|
cc521eab90 | ||
|
|
7d2c9ec447 | ||
|
|
8919d2718f | ||
|
|
cf3e816603 | ||
|
|
b8bf980a29 | ||
|
|
5dad69675b | ||
|
|
d0d1b31c10 | ||
|
|
6206d5dec4 | ||
|
|
265370b0f5 | ||
|
|
081d76268a | ||
|
|
8559b354ae | ||
|
|
9f0b99dac0 | ||
|
|
f4c1927f56 | ||
|
|
cfa9171465 | ||
|
|
2eac3dcb56 | ||
|
|
2d1e3c8022 | ||
|
|
f8ac860fde | ||
|
|
6f4a116942 | ||
|
|
ca5c3938e4 | ||
|
|
7ff5da4d62 | ||
|
|
37b3a26b9c | ||
|
|
e18f7d3365 | ||
|
|
3d03699cc8 | ||
|
|
9674421ce4 | ||
|
|
dbb18a6ecb | ||
|
|
6b1e310343 | ||
|
|
bf583290a4 | ||
|
|
6f1c19b3b0 | ||
|
|
2973319bff | ||
|
|
ec921d7f39 | ||
|
|
637a7625d2 | ||
|
|
32d8a47cd9 | ||
|
|
b4787fcfd1 | ||
|
|
e6ebcec689 | ||
|
|
ff0a859ceb | ||
|
|
1b48ac842a | ||
|
|
a702c912ae | ||
|
|
acc3298344 | ||
|
|
25e74301be | ||
|
|
327d472d64 | ||
|
|
e951aaad2d | ||
|
|
bbfe5aea37 | ||
|
|
0d28ea160e | ||
|
|
4fbaf18c89 | ||
|
|
c91b7f647d | ||
|
|
1761ea2b9b | ||
|
|
fb916cbac1 | ||
|
|
b97130cf20 | ||
|
|
05f2305fa0 | ||
|
|
b76fedeb67 | ||
|
|
6b5842d2ff | ||
|
|
7e15ec5e66 | ||
|
|
1808adc2de | ||
|
|
e2e49bce13 | ||
|
|
0d7be70b56 | ||
|
|
6b0699e664 | ||
|
|
9e96156912 | ||
|
|
837858c214 | ||
|
|
3783a25211 | ||
|
|
941c651fad | ||
|
|
f9ec32b255 | ||
|
|
8caf951c41 | ||
|
|
ef54e1be7f | ||
|
|
4c1893fe20 | ||
|
|
086f73721d | ||
|
|
202739c5be | ||
|
|
88b15cb3fe | ||
|
|
6990d7f7e3 | ||
|
|
9941a4bb83 | ||
|
|
d610aaf2cf | ||
|
|
908aa84599 | ||
|
|
185c7764ba | ||
|
|
4113294d30 | ||
|
|
aa92a7a90d | ||
|
|
c83243f22c | ||
|
|
04774d9cd6 | ||
|
|
6b8161972d | ||
|
|
9965a4a3a6 | ||
|
|
98ea8b0bb8 | ||
|
|
23e883f60f | ||
|
|
8778827a42 | ||
|
|
536df254e0 | ||
|
|
dcd0cb7d8e | ||
|
|
ef4df571f0 | ||
|
|
e926bad759 | ||
|
|
ad9e84eaaa | ||
|
|
29ea8ecf6b | ||
|
|
afb0146d33 | ||
|
|
3385438095 | ||
|
|
ffa72c7992 | ||
|
|
428b498cca | ||
|
|
6077dcc123 | ||
|
|
115c226113 | ||
|
|
e758f88519 | ||
|
|
3bccd9c45e | ||
|
|
947ba9bfe3 | ||
|
|
4ca4fd35ad | ||
|
|
8fa2b5f368 | ||
|
|
8cb0cbb91d | ||
|
|
d6f39d66bf | ||
|
|
ab79dae0b8 | ||
|
|
ee8c883201 | ||
|
|
d1043e590a | ||
|
|
5789a7efc0 | ||
|
|
f883c630f6 | ||
|
|
ac3333c0d2 | ||
|
|
da65c47245 | ||
|
|
31ea93dd0d | ||
|
|
6d162cf92d | ||
|
|
8f07744c98 | ||
|
|
dacc573650 | ||
|
|
555891face | ||
|
|
8e604667f9 | ||
|
|
3774d2bfe9 | ||
|
|
31eafee468 | ||
|
|
2bfcfa36df | ||
|
|
819562d906 | ||
|
|
0c8ce9daba | ||
|
|
c667322f25 | ||
|
|
07c13a9d43 | ||
|
|
0bd1bb5075 | ||
|
|
f9faa0542b | ||
|
|
b87004c597 | ||
|
|
2f11024db8 | ||
|
|
44a949f5c6 | ||
|
|
bf5602b062 | ||
|
|
6005006dd4 | ||
|
|
b2ac2980d5 | ||
|
|
1da24af6ee | ||
|
|
16f88a5285 | ||
|
|
85ee6737d5 | ||
|
|
dfe327301d | ||
|
|
b92e547d91 | ||
|
|
083deff489 | ||
|
|
0451ec237e | ||
|
|
d2458ff5d3 | ||
|
|
8a1578230f | ||
|
|
733bbf5153 | ||
|
|
ff1dc32efe | ||
|
|
3935acf8a5 | ||
|
|
04908c3290 | ||
|
|
7ae487057f | ||
|
|
1f943fbbab | ||
|
|
36b70779b9 | ||
|
|
7c9b273f70 | ||
|
|
cac454d9cf | ||
|
|
7355c196ba | ||
|
|
14cc57071c | ||
|
|
f5587b6b6b | ||
|
|
842bb59955 | ||
|
|
e7132f757e | ||
|
|
d77da1ffc7 | ||
|
|
d22124519e | ||
|
|
bab7eba7f0 | ||
|
|
41a0cfe516 | ||
|
|
a1069ddffa | ||
|
|
e106f2096b | ||
|
|
570abeda52 | ||
|
|
31c42e3676 | ||
|
|
89a6686349 | ||
|
|
076ed1180f | ||
|
|
95e1b48f83 | ||
|
|
e23d536af5 | ||
|
|
76182bbeb8 | ||
|
|
f86800cc3a | ||
|
|
c69b86ab6e | ||
|
|
f1033fa4bb | ||
|
|
d926319230 | ||
|
|
56c80a15e6 | ||
|
|
2a69a19dbd | ||
|
|
fe835b98d5 | ||
|
|
c5c43d99f3 | ||
|
|
2738e822a4 | ||
|
|
078a151e4f | ||
|
|
0690eacc9b | ||
|
|
165c2c3566 | ||
|
|
bb37a880f0 | ||
|
|
701dfd5694 | ||
|
|
cebc728a67 | ||
|
|
ca5fc80af5 | ||
|
|
9f74bb785e | ||
|
|
bad25c409c | ||
|
|
ef678bab1d | ||
|
|
a4a042e90d | ||
|
|
f18f07deaf | ||
|
|
dc3d07302f | ||
|
|
48f002992a | ||
|
|
27a38294ea | ||
|
|
062c4e32cb | ||
|
|
4858bb5899 | ||
|
|
c67bacbf5b | ||
|
|
a20930e5b6 | ||
|
|
8f27789d25 | ||
|
|
e401513552 | ||
|
|
81561a242f | ||
|
|
789f21ccb3 | ||
|
|
cbeae46c0b | ||
|
|
72c86d7125 | ||
|
|
c40a81ddf4 | ||
|
|
323aab8ec2 | ||
|
|
22920acc88 | ||
|
|
65009dcedc | ||
|
|
25d6d88adb | ||
|
|
8217d5efa5 | ||
|
|
d670913fd2 | ||
|
|
afa277f8ee | ||
|
|
a55b5e4b06 | ||
|
|
e467db64d8 | ||
|
|
dd2d69fa37 | ||
|
|
ed9e3d253b | ||
|
|
2723b4b7c0 | ||
|
|
69a4dffddc | ||
|
|
ec69ae261f | ||
|
|
b8178b3e7d | ||
|
|
bfc7b43758 | ||
|
|
78cb65a6a4 | ||
|
|
369c49cdcf | ||
|
|
5f20ee46a8 | ||
|
|
0495f5de26 | ||
|
|
5538a31f6b | ||
|
|
a46a3a2668 | ||
|
|
dc17492750 | ||
|
|
17691f889b | ||
|
|
02421ef269 | ||
|
|
50c934db5a | ||
|
|
c650690bcc | ||
|
|
fd009c6392 | ||
|
|
96c10c2c2d | ||
|
|
2aa2ec31ef | ||
|
|
bf223158d0 | ||
|
|
2627381021 | ||
|
|
48941cb9c4 | ||
|
|
fb7ee16f7b | ||
|
|
c73d50f56a | ||
|
|
2bfd066833 | ||
|
|
c58f33f5ab | ||
|
|
a205515d91 | ||
|
|
8f4e0f6b50 | ||
|
|
95205ca967 | ||
|
|
ab96f77d9e | ||
|
|
1f01eec3a2 | ||
|
|
24a4de6d65 | ||
|
|
b58176fcdb | ||
|
|
c21f502414 | ||
|
|
1040a7c168 | ||
|
|
a97332025c | ||
|
|
3a29d1cf03 | ||
|
|
8365419262 | ||
|
|
78bd41716a | ||
|
|
790c2b39ad | ||
|
|
d68d82e5f7 | ||
|
|
ceab5f597b | ||
|
|
fc0d82d490 | ||
|
|
8466e57468 | ||
|
|
76ed75f871 | ||
|
|
8982493239 | ||
|
|
4051cc6f93 | ||
|
|
1727c7726b | ||
|
|
0b890ed1ac | ||
|
|
fa1deafd58 | ||
|
|
176cc394d1 | ||
|
|
18f9d936c6 | ||
|
|
4334912e01 | ||
|
|
19db67cfe5 | ||
|
|
21ba14f629 | ||
|
|
c9f41ea69e | ||
|
|
f47c8cb806 | ||
|
|
5e9cfb9033 | ||
|
|
318759f54d | ||
|
|
1a4ac4c109 | ||
|
|
579be87597 | ||
|
|
ce96c77098 | ||
|
|
1c016ac0cd | ||
|
|
86ca1bbbd6 | ||
|
|
3ac178a305 | ||
|
|
40c3c38681 | ||
|
|
d2a4f8f346 | ||
|
|
59ea957d0d | ||
|
|
f352463d19 | ||
|
|
2789e73296 | ||
|
|
6c8eba5f54 | ||
|
|
4b13e590e1 | ||
|
|
6030d2f189 | ||
|
|
2788394631 | ||
|
|
b0f2a2e39f | ||
|
|
526572c9c9 | ||
|
|
c99561c2ac | ||
|
|
62f59ce50d | ||
|
|
f85b6418b8 | ||
|
|
fb9cf6cfbc | ||
|
|
974b409e91 | ||
|
|
27a5a0811b | ||
|
|
d6bb518992 | ||
|
|
026251cfd9 | ||
|
|
9b309db964 | ||
|
|
271276a0a9 | ||
|
|
67022d664f | ||
|
|
09e02b0613 | ||
|
|
e70f585cfc | ||
|
|
c2ad9666bb | ||
|
|
d2d6ecd1c1 | ||
|
|
6f99280fe3 | ||
|
|
18e84c88a0 | ||
|
|
25d370ef22 | ||
|
|
6c12990d26 | ||
|
|
12c83e82bb | ||
|
|
a172d79f66 | ||
|
|
7e4d323427 | ||
|
|
0bd5f0b2f1 | ||
|
|
523accf348 | ||
|
|
53ae0ea9f1 | ||
|
|
4c31cef709 | ||
|
|
8fd08cdd79 | ||
|
|
33b117e732 | ||
|
|
d775ed1ddb | ||
|
|
d997f0d18c | ||
|
|
78b1f64a61 | ||
|
|
1ca5e439c1 | ||
|
|
3b0a98ede2 | ||
|
|
5d5b7b3d3c | ||
|
|
3016f0c53e | ||
|
|
4bceaf5505 | ||
|
|
bc2f26b86d | ||
|
|
012ac950ac | ||
|
|
f7d4efe1c1 | ||
|
|
1195a76368 | ||
|
|
43e3999a95 | ||
|
|
bfc492421c | ||
|
|
2bc767430c | ||
|
|
183d06f90c | ||
|
|
f7d7fbdb73 | ||
|
|
6878eee40a | ||
|
|
9c941239ea | ||
|
|
1313858889 | ||
|
|
c08aa8ece7 | ||
|
|
5323d9833a | ||
|
|
3b580af194 | ||
|
|
29a6365107 | ||
|
|
9f495863cd | ||
|
|
cd06fc251f | ||
|
|
029640f1b3 | ||
|
|
5064a4ecdd | ||
|
|
8ae39fd346 | ||
|
|
d018781537 | ||
|
|
393a879c0b | ||
|
|
d01d9db9bf | ||
|
|
fabb5bd4a9 | ||
|
|
5cc4f5454f | ||
|
|
32c797e60a | ||
|
|
b66715b042 | ||
|
|
57a5488ac2 | ||
|
|
9f787777b5 | ||
|
|
bf1eba15d1 | ||
|
|
1e4e582f67 | ||
|
|
678ba0f484 | ||
|
|
2c2008c981 | ||
|
|
38198b1477 | ||
|
|
5802bfb5eb | ||
|
|
d61d7699e5 | ||
|
|
80d911f040 | ||
|
|
1a8dfe1cc0 | ||
|
|
c0a3b85580 | ||
|
|
265b4544ef | ||
|
|
c5befbba0e | ||
|
|
3df23d6b73 | ||
|
|
e57f084c93 | ||
|
|
d700e19a32 | ||
|
|
69b8c4b4eb | ||
|
|
018dae691a | ||
|
|
5c50efd074 | ||
|
|
aaf449442a | ||
|
|
301ba45f0f | ||
|
|
806ff934b2 | ||
|
|
bbcaaccdcc | ||
|
|
dea7177d29 | ||
|
|
58dd352def | ||
|
|
f36c674791 | ||
|
|
4a823d0e4f | ||
|
|
db51acdd8a | ||
|
|
b942085e6c | ||
|
|
cd4dfd7252 | ||
|
|
12da2fc0b7 | ||
|
|
36726bb349 | ||
|
|
dd7e0ea8c6 | ||
|
|
ed95f8863d | ||
|
|
8ace686df4 | ||
|
|
87a2fc2c9e | ||
|
|
604b6bec9a | ||
|
|
42ef9964de | ||
|
|
4c14797319 | ||
|
|
42fef7a98d | ||
|
|
0badf2a84c | ||
|
|
c0acfd1228 | ||
|
|
3de2641d92 | ||
|
|
fd2801a670 | ||
|
|
267fa6e389 | ||
|
|
6799d518a5 | ||
|
|
c8f740c34e | ||
|
|
4f9544d61d | ||
|
|
0bf73d862d | ||
|
|
5522e93fb9 | ||
|
|
0efe7dc63c | ||
|
|
5081c3ea88 | ||
|
|
3284431785 | ||
|
|
d42581027c | ||
|
|
a70858aea0 | ||
|
|
e02ccd7c6f | ||
|
|
b13904ec59 | ||
|
|
59011b7bcb | ||
|
|
9d0ae23f9f | ||
|
|
9ff1859dc1 | ||
|
|
7a7e1d006b | ||
|
|
0684ff401f | ||
|
|
757a90a643 | ||
|
|
46df25bb80 | ||
|
|
38b05cda50 | ||
|
|
58ba76a27f | ||
|
|
7a49f75d95 | ||
|
|
b2961c7939 | ||
|
|
6650e58a4a | ||
|
|
90d1ed64e4 | ||
|
|
a78d9c2b90 | ||
|
|
919b77df71 | ||
|
|
98e0e5fc0b | ||
|
|
8a9f004ff6 | ||
|
|
9c6bd888fd | ||
|
|
4f40ce4b40 | ||
|
|
eb343ca82e | ||
|
|
e2f6621de9 | ||
|
|
66df00cee2 | ||
|
|
a15de57e58 | ||
|
|
4d8a116849 | ||
|
|
a94d3734c2 | ||
|
|
e1d7752165 | ||
|
|
e6c88a4af3 | ||
|
|
76710ef201 | ||
|
|
e20c8a5cc7 | ||
|
|
4f4d2dbf42 | ||
|
|
8692e9af80 | ||
|
|
7d2b39058c | ||
|
|
6927dbecd2 | ||
|
|
f9b6dcc986 | ||
|
|
5c912e3c27 | ||
|
|
7e362050f7 | ||
|
|
10fbdeb294 | ||
|
|
72d70e8322 | ||
|
|
c66a339bbc | ||
|
|
1c7961daeb | ||
|
|
cdc3384883 | ||
|
|
969effedde | ||
|
|
dc4d1d49fa | ||
|
|
4e1f947a09 | ||
|
|
15d1a74291 | ||
|
|
7dba6b9b08 | ||
|
|
a5ad531004 | ||
|
|
c119b3dcca | ||
|
|
e2f2437ef4 | ||
|
|
b2980fea63 | ||
|
|
2b518690b8 | ||
|
|
92aca75792 | ||
|
|
64fdb8b7bb | ||
|
|
431ae3fc55 | ||
|
|
380b5df9f9 | ||
|
|
c7330167cf | ||
|
|
ca02e1aba9 | ||
|
|
ca4b1f5592 | ||
|
|
0cf1e27709 | ||
|
|
045932ce77 | ||
|
|
bf5481446b | ||
|
|
e3f97e384b | ||
|
|
76e119f8ad | ||
|
|
bfb12c74fb | ||
|
|
fa50b7824c | ||
|
|
da8b2d0cec | ||
|
|
74649ddb96 | ||
|
|
4619ddef5d | ||
|
|
ea74ee6e25 | ||
|
|
ecd73aa670 | ||
|
|
10d9678b3d | ||
|
|
e08d3afdbc | ||
|
|
9a6b598438 | ||
|
|
d9a80b16f0 | ||
|
|
90d0cd873d | ||
|
|
82e0b7b6ab | ||
|
|
2e1b0f2550 | ||
|
|
3302d353cf | ||
|
|
1899d9f1da | ||
|
|
fb9b772db0 | ||
|
|
302b988524 | ||
|
|
19c2fe9b5e | ||
|
|
88d25ee98c | ||
|
|
0f2ecdf5f1 | ||
|
|
3511fb8d59 | ||
|
|
1d6d640b6e | ||
|
|
977d7b277d | ||
|
|
be7820303f | ||
|
|
43503222c7 | ||
|
|
85b8c12abf | ||
|
|
7af053497e | ||
|
|
9e3a42cb62 | ||
|
|
aa32c5ffad | ||
|
|
62d1bd1ea2 | ||
|
|
2329e993ee | ||
|
|
da655b86c3 | ||
|
|
c5ff8bd4ce | ||
|
|
06aee158de | ||
|
|
bd42445ea7 | ||
|
|
ede96fa486 | ||
|
|
67dec216d2 | ||
|
|
fcbdf7ba4f | ||
|
|
e8c949c1e7 | ||
|
|
28c93ef5ac | ||
|
|
d7921c5d5f | ||
|
|
6d296f2b44 | ||
|
|
553e2830bb | ||
|
|
667ab981ba | ||
|
|
bc7871f630 | ||
|
|
d579705b10 | ||
|
|
94d383a8c1 | ||
|
|
08062e3fc3 | ||
|
|
4441427943 | ||
|
|
f5da432d38 | ||
|
|
60f665a65c | ||
|
|
9b6174793a | ||
|
|
dedab38b99 | ||
|
|
4580b18b04 | ||
|
|
88dad36449 | ||
|
|
075c82b32c | ||
|
|
ae2ffd0a28 | ||
|
|
26eea64689 | ||
|
|
c9ff1e1949 | ||
|
|
e31f38eadc | ||
|
|
756b57400b | ||
|
|
01bfd2e090 | ||
|
|
dc3e89e65c | ||
|
|
240d0ff263 | ||
|
|
3b47a4113f | ||
|
|
a6d6a49f82 | ||
|
|
f8ff3faf78 | ||
|
|
d899c26617 | ||
|
|
73ba325072 | ||
|
|
aff19e13c7 | ||
|
|
007122df43 | ||
|
|
06f8911ee1 | ||
|
|
f96f0fecda | ||
|
|
21987cb423 | ||
|
|
18cc8d7cab | ||
|
|
fc0dbd8782 | ||
|
|
b7ca3d7e37 | ||
|
|
c4bf992c0c | ||
|
|
dcce288a98 | ||
|
|
cfd8126e5d | ||
|
|
7a96642498 | ||
|
|
8d5a42c233 | ||
|
|
00a41be413 | ||
|
|
fdb9a9cca8 | ||
|
|
e7f088ef52 | ||
|
|
243efcd51a | ||
|
|
e3cbdd18a0 | ||
|
|
b9cdaf8e19 | ||
|
|
4758caa772 | ||
|
|
4058522f68 | ||
|
|
80437c564d | ||
|
|
503fc7c312 | ||
|
|
f6691a90c0 | ||
|
|
ead09ed110 | ||
|
|
ac1ecd2e7b | ||
|
|
3538e7f6f4 | ||
|
|
75bc038144 | ||
|
|
7018a3e737 | ||
|
|
d3836d4548 | ||
|
|
dbd7db7787 | ||
|
|
1222fd40b7 | ||
|
|
b5f4c639fd | ||
|
|
cddeeff3fc | ||
|
|
94a35a6558 | ||
|
|
b953b2b807 | ||
|
|
367a73d033 | ||
|
|
1ac13658e1 | ||
|
|
2440272307 | ||
|
|
582ed6b5d1 | ||
|
|
e2adbaa5c1 | ||
|
|
4acf800ace | ||
|
|
7cc305c2f5 | ||
|
|
95f5962186 | ||
|
|
f4c2996a3a | ||
|
|
10c4037694 | ||
|
|
52be6deccf | ||
|
|
0d736efc88 | ||
|
|
9cc21c2a62 | ||
|
|
0b7f422d5d | ||
|
|
0ca760fad6 | ||
|
|
3feed7ba07 | ||
|
|
57f1f2d1fe | ||
|
|
b0f9f15a60 | ||
|
|
dc4d820666 | ||
|
|
e30b54ddb2 | ||
|
|
939bb07603 | ||
|
|
cc2c49644d | ||
|
|
29f221d547 | ||
|
|
2e32ceb6e0 | ||
|
|
2cbc9b6426 | ||
|
|
3f3a8f898d | ||
|
|
6e62ea5364 | ||
|
|
5d39b6160a | ||
|
|
a9e8187f28 | ||
|
|
228f5bfdff | ||
|
|
29e5f193f0 | ||
|
|
8f8ebab712 | ||
|
|
418149c9a6 | ||
|
|
e30e5da75a | ||
|
|
fc6681306e | ||
|
|
60acc3ef44 | ||
|
|
9958e557b7 | ||
|
|
8dbc1daaf4 | ||
|
|
5a23d048bd | ||
|
|
b658ea6459 | ||
|
|
dc91a0d807 | ||
|
|
c1fd6552d2 | ||
|
|
6b2e78acdf | ||
|
|
7b11ab04c6 | ||
|
|
bced5f95ff | ||
|
|
9d7f773b9c | ||
|
|
fea0a98b9e | ||
|
|
8745858bcf | ||
|
|
2885c73a9a | ||
|
|
893b23f3cd | ||
|
|
d860d6b891 | ||
|
|
dcc9625803 | ||
|
|
b7e4426002 | ||
|
|
b4cf8f76c8 | ||
|
|
687eddcc63 | ||
|
|
9d6d137b50 | ||
|
|
a75b95694b | ||
|
|
c7aa8871e4 | ||
|
|
f9d919bdbb | ||
|
|
4d0f019ad5 | ||
|
|
e6154e685f | ||
|
|
2c59b5f557 | ||
|
|
4746c89227 | ||
|
|
278d851c7c | ||
|
|
406e22a681 | ||
|
|
17e05c6fd5 | ||
|
|
9846762991 | ||
|
|
17df1ebc6b | ||
|
|
ad8a8b52be | ||
|
|
0d29527758 | ||
|
|
7a96c9fe24 | ||
|
|
c71e5c63ca | ||
|
|
f82a164d75 | ||
|
|
5d4e9b1ead | ||
|
|
788f885759 | ||
|
|
6aeafc6651 | ||
|
|
1aadf91901 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: gnif
|
||||
patreon: gnif
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: lookingglass
|
||||
|
||||
71
.github/issue_template.md
vendored
71
.github/issue_template.md
vendored
@@ -1,11 +1,66 @@
|
||||
### Required information
|
||||
### Issues are for Bug Reports and Feature Requests Only!
|
||||
|
||||
Host CPU:
|
||||
Host GPU:
|
||||
Guest GPU:
|
||||
Host Kernel version:
|
||||
Host QEMU version:
|
||||
If you are looking for help or support please use one of the following methods
|
||||
|
||||
Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
|
||||
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
|
||||
* https://forum.level1techs.com/c/software/lookingGlass/142
|
||||
|
||||
**Reports that do no include this information will be ignored and closed**
|
||||
Ask for help in the Looking Glass discord server
|
||||
* https://discord.gg/52SMupxkvt
|
||||
|
||||
*Issues that are not bug reports or feature requests will be closed & ignored*
|
||||
|
||||
### Errors that are not bugs
|
||||
|
||||
Some errors generated by the LG client are not bugs, but rather issues with your
|
||||
system's configuration and/or timing. Please do not report these, but rather use
|
||||
one of the above resources to ask for advice/help.
|
||||
|
||||
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
|
||||
using `isolcpus` and CPU pinning may result in this message, especially if you
|
||||
are over-taxing your CPU.
|
||||
|
||||
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
|
||||
issue with Looking Glass but rather a more substantial issue with your system,
|
||||
such as missing hardware support for the RGBA32 pixmap format, or missing
|
||||
required OpenGL EGL features.
|
||||
|
||||
* `The host application is not compatible with this client` - The Looking Glass
|
||||
Host application in Windows is the incorrect version and is not compatible,
|
||||
you need to make sure you run matching versions of both the host and client
|
||||
applications.
|
||||
|
||||
### Bug Report Required Information
|
||||
|
||||
The entire (not truncated) output from the client application (if applicable).
|
||||
To obtain this run `looking-glass-client` in a terminal.
|
||||
|
||||
```
|
||||
PASTE CLIENT OUTPUT HERE
|
||||
```
|
||||
|
||||
The entire (not truncated) log file from the host application (if applicable).
|
||||
Normally, this is found on the guest system at:
|
||||
|
||||
%ProgramData%\Looking Glass (host)\looking-glass-host.txt
|
||||
|
||||
This log may be quite long, please delete the file first and then proceed to
|
||||
launch the host and reproduce the issue so that the log only contains the
|
||||
pertinent information.
|
||||
|
||||
|
||||
```
|
||||
PASTE HOST LOG FILE CONTENTS HERE
|
||||
```
|
||||
|
||||
If the client is unexpectedly exiting without a backtrace, please provide one via
|
||||
gdb with the command `thread apply all bt`. If you are unsure how to do this
|
||||
please watch the video below on how to perform a Debug build and generate this
|
||||
backtrace.
|
||||
|
||||
https://www.youtube.com/watch?v=EqxxJK9Yo64
|
||||
|
||||
|
||||
```
|
||||
PASTE FULL BACKTRACE HERE
|
||||
```
|
||||
|
||||
136
.github/workflows/build.yml
vendored
Normal file
136
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
client:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install client dependencies
|
||||
run: |
|
||||
sudo apt-get install \
|
||||
binutils-dev \
|
||||
libsdl2-dev libsdl2-ttf-dev \
|
||||
libspice-protocol-dev nettle-dev \
|
||||
libx11-dev libxss-dev libxi-dev \
|
||||
wayland-protocols
|
||||
- name: Configure client
|
||||
run: |
|
||||
mkdir client/build
|
||||
cd client/build
|
||||
CC=/usr/bin/${{ matrix.cc }} cmake ..
|
||||
- name: Build client
|
||||
run: |
|
||||
cd client/build
|
||||
make -j$(nproc)
|
||||
|
||||
module:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build kernel module
|
||||
run: |
|
||||
cd module
|
||||
make
|
||||
|
||||
host-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install Linux host dependencies
|
||||
run: |
|
||||
sudo apt-get install binutils-dev libgl1-mesa-dev
|
||||
- name: Configure Linux host
|
||||
run: |
|
||||
mkdir host/build
|
||||
cd host/build
|
||||
cmake ..
|
||||
- name: Build Linux host
|
||||
run: |
|
||||
cd host/build
|
||||
make -j$(nproc)
|
||||
|
||||
host-windows-cross:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install Windows host cross-compile dependencies
|
||||
run: |
|
||||
sudo apt-get install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 nsis
|
||||
- name: Configure Windows host for cross-compile
|
||||
run: |
|
||||
mkdir host/build
|
||||
cd host/build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
||||
- name: Cross-compile Windows host
|
||||
run: |
|
||||
cd host/build
|
||||
make -j$(nproc)
|
||||
- name: Build Windows host installer
|
||||
run: |
|
||||
cd host/build
|
||||
makensis platform/Windows/installer.nsi
|
||||
|
||||
host-windows-native:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Configure Windows host for native MinGW-w64
|
||||
run: |
|
||||
mkdir host\build
|
||||
cd host\build
|
||||
cmake -G "MinGW Makefiles" ..
|
||||
- name: Build Windows host on native MinGW-w64
|
||||
run: |
|
||||
cd host\build
|
||||
mingw32-make "-j$([Environment]::ProcessorCount)"
|
||||
- name: Build Windows host installer
|
||||
run: |
|
||||
cd host\build
|
||||
makensis platform\Windows\installer.nsi
|
||||
|
||||
obs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install obs plugin dependencies
|
||||
run: |
|
||||
sudo apt-get install binutils-dev libobs-dev libgl1-mesa-dev
|
||||
- name: Configure obs plugin
|
||||
run: |
|
||||
mkdir obs/build
|
||||
cd obs/build
|
||||
CC=/usr/bin/${{ matrix.cc }} cmake ..
|
||||
- name: Build obs plugin
|
||||
run: |
|
||||
cd obs/build
|
||||
make -j$(nproc)
|
||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
||||
[submodule "LGMP"]
|
||||
path = LGMP
|
||||
path = repos/LGMP
|
||||
url = https://github.com/gnif/LGMP.git
|
||||
[submodule "repos/PureSpice"]
|
||||
path = repos/PureSpice
|
||||
url = https://github.com/gnif/PureSpice
|
||||
|
||||
1
LGMP
1
LGMP
Submodule LGMP deleted from 26d9f9a59e
41
README.md
41
README.md
@@ -1,14 +1,20 @@
|
||||
# Looking Glass
|
||||
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
||||
VGA PCI Passthrough.
|
||||
|
||||
* Project Website: https://looking-glass.hostfission.com
|
||||
* Project Website: https://looking-glass.io
|
||||
* Getting Started: https://looking-glass.io/wiki/Installation
|
||||
|
||||
## Donations
|
||||
|
||||
I (Geoffrey McRae) am the primary developer behind this project and I have invested thousands of hours of development time into it.
|
||||
If you like this project and find it useful and would like to help out you can support me directly using the following platforms.
|
||||
I (Geoffrey McRae) am the primary developer behind this project and I have
|
||||
invested thousands of hours of development time into it.
|
||||
|
||||
If you like this project and find it useful and would like to help out you can
|
||||
support me directly using the following platforms.
|
||||
|
||||
* [GitHub](https://github.com/sponsors/gnif)
|
||||
* [Ko-Fi](https://ko-fi.com/lookingglass)
|
||||
* [Patreon](https://www.patreon.com/gnif)
|
||||
* [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY)
|
||||
@@ -16,32 +22,41 @@ If you like this project and find it useful and would like to help out you can s
|
||||
|
||||
## Documentation
|
||||
|
||||
** IMPORTANT **
|
||||
This project contains submodules that must be checked out if building from the
|
||||
git repository! If you are not a developer and just want to compile Looking
|
||||
Glass please download the source archive from the website instead:
|
||||
|
||||
https://looking-glass.io/downloads
|
||||
|
||||
Please also be sure to see the following files for more information
|
||||
Note: The `README.md` files are slowly being deprecated from this project in
|
||||
favor of the wiki at https://looking-glass.io/wiki, and as such the
|
||||
information in these files may be dated.
|
||||
|
||||
* [client/README.md](client/README.md)
|
||||
* [c-host/README.md](c-host/README.md)
|
||||
* [host/README.md](host/README.md)
|
||||
* [module/README.md](module/README.md)
|
||||
|
||||
## Obtaining and using Looking Glass
|
||||
|
||||
Please see https://looking-glass.hostfission.com/quickstart
|
||||
|
||||
## Latest Version
|
||||
|
||||
If you would like to use the latest bleeding edge version of Looking Glass please be aware there will be no support at this time.
|
||||
If you would like to use the latest bleeding edge version of Looking Glass please
|
||||
be aware there will be no support at this time.
|
||||
|
||||
Latest bleeding edge builds of the Windows host application can be obtained from:
|
||||
|
||||
https://looking-glass.hostfission.com/downloads
|
||||
https://looking-glass.io/downloads
|
||||
|
||||
# Help and support
|
||||
|
||||
## Web
|
||||
|
||||
https://forum.level1techs.com/t/looking-glass-triage/130952
|
||||
https://forum.level1techs.com/c/software/lookingglass/142
|
||||
|
||||
## Discord
|
||||
|
||||
https://discord.gg/4ahCn4c
|
||||
* Looking Glass: https://discord.gg/52SMupxkvt
|
||||
* VFIO: https://discord.gg/4ahCn4c
|
||||
|
||||
## IRC
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
theme: jekyll-theme-cayman
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "windows/mousehook.h"
|
||||
#include "common/windebug.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mouseHook
|
||||
{
|
||||
bool installed;
|
||||
HHOOK hook;
|
||||
MouseHookFn callback;
|
||||
};
|
||||
|
||||
static struct mouseHook mouseHook = { 0 };
|
||||
|
||||
// forwards
|
||||
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
void mouseHook_install(MouseHookFn callback)
|
||||
{
|
||||
struct MSG_CALL_FUNCTION cf;
|
||||
cf.fn = msg_callback;
|
||||
cf.wParam = 1;
|
||||
cf.lParam = (LPARAM)callback;
|
||||
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
|
||||
}
|
||||
|
||||
void mouseHook_remove()
|
||||
{
|
||||
struct MSG_CALL_FUNCTION cf;
|
||||
cf.fn = msg_callback;
|
||||
cf.wParam = 0;
|
||||
cf.lParam = 0;
|
||||
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
|
||||
}
|
||||
|
||||
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (wParam)
|
||||
{
|
||||
if (mouseHook.installed)
|
||||
{
|
||||
DEBUG_WARN("Mouse hook already installed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mouseHook.hook = SetWindowsHookEx(WH_MOUSE_LL, mouseHook_hook, NULL, 0);
|
||||
if (!mouseHook.hook)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to install the mouse hook", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
mouseHook.installed = true;
|
||||
mouseHook.callback = (MouseHookFn)lParam;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mouseHook.installed)
|
||||
return 0;
|
||||
|
||||
UnhookWindowsHookEx(mouseHook.hook);
|
||||
mouseHook.installed = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE)
|
||||
{
|
||||
MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam;
|
||||
mouseHook.callback(msg->pt.x, msg->pt.y);
|
||||
}
|
||||
return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam);
|
||||
}
|
||||
537
c-host/src/app.c
537
c-host/src/app.c
@@ -1,537 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/platform.h"
|
||||
#include "interface/capture.h"
|
||||
#include "dynamic/capture.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/crash.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/ivshmem.h"
|
||||
|
||||
#include <lgmp/host.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
|
||||
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
|
||||
|
||||
#define LGMP_Q_FRAME_LEN 2
|
||||
#define LGMP_Q_POINTER_LEN 20
|
||||
|
||||
static const struct LGMPQueueConfig FRAME_QUEUE_CONFIG =
|
||||
{
|
||||
.queueID = LGMP_Q_FRAME,
|
||||
.numMessages = LGMP_Q_FRAME_LEN,
|
||||
.subTimeout = 1000
|
||||
};
|
||||
|
||||
static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
|
||||
{
|
||||
.queueID = LGMP_Q_POINTER,
|
||||
.numMessages = LGMP_Q_POINTER_LEN,
|
||||
.subTimeout = 1000
|
||||
};
|
||||
|
||||
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (128 * 128 * 4))
|
||||
|
||||
struct app
|
||||
{
|
||||
PLGMPHost lgmp;
|
||||
|
||||
PLGMPHostQueue pointerQueue;
|
||||
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
|
||||
PLGMPMemory pointerShape;
|
||||
bool pointerShapeValid;
|
||||
unsigned int pointerIndex;
|
||||
|
||||
size_t maxFrameSize;
|
||||
PLGMPHostQueue frameQueue;
|
||||
PLGMPMemory frameMemory[LGMP_Q_FRAME_LEN];
|
||||
unsigned int frameIndex;
|
||||
|
||||
CaptureInterface * iface;
|
||||
|
||||
bool running;
|
||||
bool reinit;
|
||||
LGThread * lgmpThread;
|
||||
LGThread * frameThread;
|
||||
};
|
||||
|
||||
static struct app app;
|
||||
|
||||
static int lgmpThread(void * opaque)
|
||||
{
|
||||
LGMP_STATUS status;
|
||||
while(app.running)
|
||||
{
|
||||
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
|
||||
break;
|
||||
}
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
app.running = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int frameThread(void * opaque)
|
||||
{
|
||||
DEBUG_INFO("Frame thread started");
|
||||
|
||||
bool frameValid = false;
|
||||
bool repeatFrame = false;
|
||||
int frameIndex = 0;
|
||||
CaptureFrame frame = { 0 };
|
||||
|
||||
(void)frameIndex;
|
||||
(void)repeatFrame;
|
||||
|
||||
while(app.running)
|
||||
{
|
||||
switch(app.iface->waitFrame(&frame))
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
repeatFrame = false;
|
||||
break;
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
{
|
||||
app.reinit = true;
|
||||
DEBUG_INFO("Frame thread reinit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the frame");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
{
|
||||
if (frameValid && lgmpHostQueueNewSubs(app.frameQueue) > 0)
|
||||
{
|
||||
// resend the last frame
|
||||
repeatFrame = true;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//wait until there is room in the queue
|
||||
if (lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
|
||||
{
|
||||
if (!app.running)
|
||||
break;
|
||||
}
|
||||
|
||||
// if we are repeating a frame just send the last frame again
|
||||
if (repeatFrame)
|
||||
{
|
||||
lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// we increment the index first so that if we need to repeat a frame
|
||||
// the index still points to the latest valid frame
|
||||
if (frameIndex++ == LGMP_Q_FRAME_LEN)
|
||||
frameIndex = 0;
|
||||
|
||||
KVMFRFrame * fi = lgmpHostMemPtr(app.frameMemory[app.frameIndex]);
|
||||
switch(frame.format)
|
||||
{
|
||||
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
|
||||
case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break;
|
||||
case CAPTURE_FMT_RGBA10: fi->type = FRAME_TYPE_RGBA10; break;
|
||||
case CAPTURE_FMT_YUV420: fi->type = FRAME_TYPE_YUV420; break;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format);
|
||||
continue;
|
||||
}
|
||||
|
||||
fi->width = frame.width;
|
||||
fi->height = frame.height;
|
||||
fi->stride = frame.stride;
|
||||
fi->pitch = frame.pitch;
|
||||
frameValid = true;
|
||||
|
||||
FrameBuffer fb = (FrameBuffer)(fi + 1);
|
||||
framebuffer_prepare(fb);
|
||||
|
||||
/* we post and then get the frame, this is intentional! */
|
||||
lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex]);
|
||||
app.iface->getFrame(fb);
|
||||
}
|
||||
DEBUG_INFO("Frame thread stopped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool startThreads()
|
||||
{
|
||||
app.running = true;
|
||||
if (!lgCreateThread("LGMPThread", lgmpThread, NULL, &app.lgmpThread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the LGMP thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool stopThreads()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
app.running = false;
|
||||
app.iface->stop();
|
||||
|
||||
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
|
||||
{
|
||||
DEBUG_WARN("Failed to join the frame thread");
|
||||
ok = false;
|
||||
}
|
||||
app.frameThread = NULL;
|
||||
|
||||
if (app.lgmpThread && !lgJoinThread(app.lgmpThread, NULL))
|
||||
{
|
||||
DEBUG_WARN("Failed to join the LGMP thread");
|
||||
ok = false;
|
||||
}
|
||||
app.lgmpThread = NULL;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool captureStart()
|
||||
{
|
||||
DEBUG_INFO("Using : %s", app.iface->getName());
|
||||
|
||||
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
|
||||
if (maxFrameSize > app.maxFrameSize)
|
||||
{
|
||||
DEBUG_ERROR("Maximum frame size of %d bytes excceds maximum space available", maxFrameSize);
|
||||
return false;
|
||||
}
|
||||
DEBUG_INFO("Capture Size : %u MiB (%u)", maxFrameSize / 1048576, maxFrameSize);
|
||||
|
||||
DEBUG_INFO("==== [ Capture Start ] ====");
|
||||
return startThreads();
|
||||
}
|
||||
|
||||
static bool captureRestart()
|
||||
{
|
||||
DEBUG_INFO("==== [ Capture Restart ] ====");
|
||||
if (!stopThreads())
|
||||
return false;
|
||||
|
||||
if (!app.iface->deinit() || !app.iface->init())
|
||||
{
|
||||
DEBUG_ERROR("Failed to reinitialize the capture device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!captureStart())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
||||
{
|
||||
// spin until there is room
|
||||
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
|
||||
{
|
||||
DEBUG_INFO("pending");
|
||||
if (!app.running)
|
||||
return false;
|
||||
}
|
||||
|
||||
PLGMPMemory mem = app.pointerMemory[app.pointerIndex];
|
||||
*data = ((uint8_t*)lgmpHostMemPtr(mem)) + sizeof(KVMFRCursor);
|
||||
*size = MAX_POINTER_SIZE - sizeof(KVMFRCursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void capturePostPointerBuffer(CapturePointer pointer)
|
||||
{
|
||||
PLGMPMemory mem;
|
||||
const bool newClient = lgmpHostQueueNewSubs(app.pointerQueue) > 0;
|
||||
|
||||
if (pointer.shapeUpdate || newClient)
|
||||
{
|
||||
if (pointer.shapeUpdate)
|
||||
{
|
||||
// swap the latest shape buffer out of rotation
|
||||
PLGMPMemory tmp = app.pointerShape;
|
||||
app.pointerShape = app.pointerMemory[app.pointerIndex];
|
||||
app.pointerMemory[app.pointerIndex] = tmp;
|
||||
}
|
||||
|
||||
// use the last known shape buffer
|
||||
mem = app.pointerShape;
|
||||
}
|
||||
else
|
||||
{
|
||||
mem = app.pointerMemory[app.pointerIndex];
|
||||
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
|
||||
app.pointerIndex = 0;
|
||||
}
|
||||
|
||||
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
|
||||
cursor->x = pointer.x;
|
||||
cursor->y = pointer.y;
|
||||
cursor->visible = pointer.visible;
|
||||
|
||||
if (pointer.shapeUpdate)
|
||||
{
|
||||
// remember which slot has the latest shape
|
||||
cursor->width = pointer.width;
|
||||
cursor->height = pointer.height;
|
||||
cursor->pitch = pointer.pitch;
|
||||
switch(pointer.format)
|
||||
{
|
||||
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
|
||||
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
|
||||
case CAPTURE_FMT_MASKED: cursor->type = CURSOR_TYPE_MASKED_COLOR; break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Invalid pointer type");
|
||||
return;
|
||||
}
|
||||
|
||||
app.pointerShapeValid = true;
|
||||
}
|
||||
|
||||
const uint32_t sendShape =
|
||||
((pointer.shapeUpdate || newClient) && app.pointerShapeValid) ? 1 : 0;
|
||||
|
||||
LGMP_STATUS status;
|
||||
while ((status = lgmpHostQueuePost(app.pointerQueue, sendShape, mem)) != LGMP_OK)
|
||||
{
|
||||
if (status == LGMP_ERR_QUEUE_FULL)
|
||||
continue;
|
||||
|
||||
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// this is called from the platform specific startup routine
|
||||
int app_main(int argc, char * argv[])
|
||||
{
|
||||
if (!installCrashHandler(os_getExecutable()))
|
||||
DEBUG_WARN("Failed to install the crash handler");
|
||||
|
||||
ivshmemOptionsInit();
|
||||
|
||||
// register capture interface options
|
||||
for(int i = 0; CaptureInterfaces[i]; ++i)
|
||||
if (CaptureInterfaces[i]->initOptions)
|
||||
CaptureInterfaces[i]->initOptions();
|
||||
|
||||
// try load values from a config file
|
||||
option_load("looking-glass-host.ini");
|
||||
|
||||
// parse the command line arguments
|
||||
if (!option_parse(argc, argv))
|
||||
{
|
||||
option_free();
|
||||
DEBUG_ERROR("Failure to parse the command line");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!option_validate())
|
||||
{
|
||||
option_free();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// perform platform specific initialization
|
||||
if (!app_init())
|
||||
return -1;
|
||||
|
||||
DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")");
|
||||
|
||||
struct IVSHMEM shmDev;
|
||||
if (!ivshmemOpen(&shmDev))
|
||||
{
|
||||
DEBUG_ERROR("Failed to open the IVSHMEM device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int exitcode = 0;
|
||||
DEBUG_INFO("IVSHMEM Size : %u MiB", shmDev.size / 1048576);
|
||||
DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmDev.mem);
|
||||
|
||||
LGMP_STATUS status;
|
||||
if ((status = lgmpHostInit(shmDev.mem, shmDev.size, &app.lgmp)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostInit Failed: %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((status = lgmpHostQueueNew(app.lgmp, FRAME_QUEUE_CONFIG, &app.frameQueue)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame): %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((status = lgmpHostQueueNew(app.lgmp, POINTER_QUEUE_CONFIG, &app.pointerQueue)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostQueueNew Failed (Pointer): %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for(int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
|
||||
{
|
||||
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerMemory[i])) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
app.pointerShapeValid = false;
|
||||
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerShape)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shape): %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
app.maxFrameSize = ALIGN_DN(lgmpHostMemAvail(app.lgmp) / LGMP_Q_FRAME_LEN);
|
||||
for(int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||
{
|
||||
if ((status = lgmpHostMemAlloc(app.lgmp, app.maxFrameSize, &app.frameMemory[i])) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostMemAlloc Failed (Frame): %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO("Max Pointer Size : %u KiB", (unsigned int)MAX_POINTER_SIZE / 1024);
|
||||
DEBUG_INFO("Max Frame Size : %u MiB", (unsigned int)(app.maxFrameSize / 1048576LL));
|
||||
|
||||
CaptureInterface * iface = NULL;
|
||||
for(int i = 0; CaptureInterfaces[i]; ++i)
|
||||
{
|
||||
iface = CaptureInterfaces[i];
|
||||
DEBUG_INFO("Trying : %s", iface->getName());
|
||||
|
||||
if (!iface->create(captureGetPointerBuffer, capturePostPointerBuffer))
|
||||
{
|
||||
iface = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iface->init())
|
||||
break;
|
||||
|
||||
iface->free();
|
||||
iface = NULL;
|
||||
}
|
||||
|
||||
if (!iface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to find a supported capture interface");
|
||||
exitcode = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
app.iface = iface;
|
||||
|
||||
if (!captureStart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
while(app.running)
|
||||
{
|
||||
if (app.reinit && !captureRestart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
app.reinit = false;
|
||||
|
||||
switch(iface->capture())
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
break;
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
if (!captureRestart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
app.reinit = false;
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
DEBUG_ERROR("Capture interface reported a fatal error");
|
||||
exitcode = -1;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
stopThreads();
|
||||
exit:
|
||||
|
||||
iface->deinit();
|
||||
iface->free();
|
||||
fail:
|
||||
|
||||
for(int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||
lgmpHostMemFree(&app.frameMemory[i]);
|
||||
for(int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
|
||||
lgmpHostMemFree(&app.pointerMemory[i]);
|
||||
lgmpHostMemFree(&app.pointerShape);
|
||||
lgmpHostFree(&app.lgmp);
|
||||
|
||||
ivshmemClose(&shmDev);
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
void app_quit()
|
||||
{
|
||||
app.running = false;
|
||||
}
|
||||
3
client/.gitignore
vendored
3
client/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
bin/
|
||||
build/
|
||||
*.swp
|
||||
@@ -1,10 +0,0 @@
|
||||
packadd termdebug
|
||||
|
||||
function Debug()
|
||||
!cd build && make
|
||||
if v:shell_error == 0
|
||||
TermdebugCommand build/looking-glass-client
|
||||
endif
|
||||
endfunction
|
||||
|
||||
command Debug call Debug()
|
||||
@@ -21,14 +21,27 @@ add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
||||
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
||||
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
|
||||
|
||||
option(ENABLE_CB_X11 "Enable X11 clipboard integration" ON)
|
||||
add_feature_info(ENABLE_CB_X11 ENABLE_CB_X11 "X11 Clipboard Integration.")
|
||||
|
||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
||||
|
||||
option(ENABLE_ASAN "Build with AddressSanitizer" OFF)
|
||||
add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.")
|
||||
|
||||
option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF)
|
||||
add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.")
|
||||
|
||||
option(ENABLE_X11 "Build with X11 support" ON)
|
||||
add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.")
|
||||
|
||||
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
||||
add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
|
||||
|
||||
add_compile_options(
|
||||
"-Wall"
|
||||
"-Wextra"
|
||||
"-Wno-sign-compare"
|
||||
"-Wno-unused-parameter"
|
||||
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
"-ffast-math"
|
||||
@@ -37,38 +50,54 @@ add_compile_options(
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
|
||||
set(EXE_FLAGS "-Wl,--gc-sections")
|
||||
set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
add_definitions(-D ENABLE_OPENGL)
|
||||
endif()
|
||||
|
||||
if (ENABLE_EGL)
|
||||
add_definitions(-D ENABLE_EGL)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ASAN)
|
||||
add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address")
|
||||
set(EXE_FLAGS "${EXE_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
||||
endif()
|
||||
|
||||
if(ENABLE_UBSAN)
|
||||
add_compile_options("-fsanitize=undefined")
|
||||
set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined")
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PKGCONFIG REQUIRED
|
||||
sdl2
|
||||
x11
|
||||
)
|
||||
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
find_package(GMP)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
add_definitions(-D ATOMIC_LOCKING)
|
||||
add_definitions(-D GL_GLEXT_PROTOTYPES)
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/version.c
|
||||
${CMAKE_BINARY_DIR}/_version.c
|
||||
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
|
||||
${PROJECT_TOP}/version.cmake
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PKGCONFIG_INCLUDE_DIRS}
|
||||
${PKGCONFIG_INCLUDE_DIRS} ${PKGCONFIG_OPT_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
${PKGCONFIG_LIBRARIES}
|
||||
${PKGCONFIG_LIBRARIES} ${PKGCONFIG_OPT_LIBRARIES}
|
||||
${GMP_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
rt
|
||||
@@ -76,32 +105,37 @@ link_libraries(
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
${CMAKE_BINARY_DIR}/version.c
|
||||
src/main.c
|
||||
src/core.c
|
||||
src/app.c
|
||||
src/config.c
|
||||
src/keybind.c
|
||||
src/lg-renderer.c
|
||||
src/ll.c
|
||||
src/utils.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/kb.c
|
||||
src/egl_dynprocs.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
|
||||
|
||||
add_subdirectory(spice)
|
||||
add_subdirectory(displayservers)
|
||||
add_subdirectory(renderers)
|
||||
add_subdirectory(clipboards)
|
||||
add_subdirectory(fonts)
|
||||
add_subdirectory(decoders)
|
||||
|
||||
add_executable(looking-glass-client ${SOURCES})
|
||||
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
|
||||
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER} ${PKGCONFIG_OPT_CFLAGS_OTHER})
|
||||
target_link_libraries(looking-glass-client
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
displayservers
|
||||
lgmp
|
||||
spice
|
||||
purespice
|
||||
renderers
|
||||
clipboards
|
||||
fonts
|
||||
)
|
||||
|
||||
|
||||
137
client/README.md
137
client/README.md
@@ -17,10 +17,12 @@ This is the Looking Glass client application that is designed to work in tandem
|
||||
* libfontconfig1-dev
|
||||
* libx11-dev
|
||||
* nettle-dev
|
||||
* libxss-dev
|
||||
* libxi-dev
|
||||
|
||||
#### Debian (and maybe Ubuntu)
|
||||
|
||||
apt-get install binutils-dev cmake fonts-freefont-ttf libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libfontconfig1-dev libx11-dev nettle-dev
|
||||
apt-get install binutils-dev cmake fonts-freefont-ttf libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libfontconfig1-dev libx11-dev nettle-dev libxss-dev libxi-dev
|
||||
|
||||
### Building
|
||||
|
||||
@@ -31,6 +33,19 @@ This is the Looking Glass client application that is designed to work in tandem
|
||||
|
||||
Should this all go well you should be left with the file `looking-glass-client`
|
||||
|
||||
### Removing Wayland or X11 support
|
||||
|
||||
Wayland and/or X11 support can be disabled with the compile options
|
||||
`ENABLE_WAYLAND` and `ENABLE_X11`, if both are specified only `SDL2` will remain
|
||||
and the client will fallback to using it.
|
||||
|
||||
cmake ../ -DENABLE_WAYLAND=OFF
|
||||
|
||||
At this time, X11 is the perferred and best supported interface. Wayland is not
|
||||
far behind, however it lacks some of the seamless interaction features that X11
|
||||
has due to the lack of cursor warp (programmatic movement of the local cusror) on
|
||||
Wayland.
|
||||
|
||||
---
|
||||
|
||||
## Usage Tips
|
||||
@@ -44,8 +59,11 @@ Below are a list of current key bindings:
|
||||
|-|-|
|
||||
| <kbd>ScrLk</kbd> | Toggle cursor screen capture |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen toggle |
|
||||
| <kbd>ScrLk</kbd>+<kbd>V</kbd> | Video stream toggle |
|
||||
| <kbd>ScrLk</kbd>+<kbd>I</kbd> | Spice keyboard & mouse enable toggle |
|
||||
| <kbd>ScrLk</kbd>+<kbd>N</kbd> | Toggle night vision mode (EGL renderer only!) |
|
||||
| <kbd>ScrLk</kbd>+<kbd>R</kbd> | Rotate the output clockwise by 90 degree increments |
|
||||
| <kbd>ScrLk</kbd>+<kbd>Q</kbd> | Quit |
|
||||
| <kbd>ScrLk</kbd>+<kbd>Insert</kbd> | Increase mouse sensitivity (in capture mode only) |
|
||||
| <kbd>ScrLk</kbd>+<kbd>Del</kbd> | Decrease mouse sensitivity (in capture mode only) |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest |
|
||||
@@ -60,6 +78,8 @@ Below are a list of current key bindings:
|
||||
| <kbd>ScrLk</kbd>+<kbd>F10</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F10</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F11</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F11</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F12</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F12</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>LWin</kbd> | Send <kbd>LWin</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>RWin</kbd> | Send <kbd>RWin</kbd> to the guest |
|
||||
|
||||
|
||||
|
||||
@@ -89,46 +109,58 @@ Command line arguments will override any options loaded from the config files.
|
||||
### Supported options
|
||||
|
||||
```
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| app:configFile | -C | NULL | A file to read additional configuration from |
|
||||
| app:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file |
|
||||
| app:shmSize | -L | 0 | Specify the size in MB of the shared memory file (0 = detect) |
|
||||
| app:renderer | -g | auto | Specify the renderer to use |
|
||||
| app:license | -l | no | Show the license for this application and then terminate |
|
||||
| app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds |
|
||||
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| app:configFile | -C | NULL | A file to read additional configuration from |
|
||||
| app:renderer | -g | auto | Specify the renderer to use |
|
||||
| app:license | -l | no | Show the license for this application and then terminate |
|
||||
| app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds |
|
||||
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
|
||||
| app:allowDMA | | yes | Allow direct DMA transfers if possible (VM-VM only for now) |
|
||||
| app:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file, or the name of the kvmfr device to use, ie: kvmfr0 |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| win:title | | Looking Glass (client) | The window title |
|
||||
| win:position | | center | Initial window position at startup |
|
||||
| win:size | | 1024x768 | Initial window size at startup |
|
||||
| win:autoResize | -a | no | Auto resize the window to the guest |
|
||||
| win:allowResize | -n | yes | Aallow the window to be manually resized |
|
||||
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
|
||||
| win:borderless | -d | no | Borderless mode |
|
||||
| win:fullScreen | -F | no | Launch in fullscreen borderless mode |
|
||||
| win:maximize | -T | no | Launch window maximized |
|
||||
| win:minimizeOnFocusLoss | | yes | Minimize window on focus loss |
|
||||
| win:fpsLimit | -K | 200 | Frame rate limit (0 = disable - not recommended) |
|
||||
| win:showFPS | -k | no | Enable the FPS & UPS display |
|
||||
| win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) |
|
||||
| win:noScreensaver | -S | no | Prevent the screensaver from starting |
|
||||
| win:alerts | -q | yes | Show on screen alert messages |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
| win:title | | Looking Glass (client) | The window title |
|
||||
| win:position | | center | Initial window position at startup |
|
||||
| win:size | | 1024x768 | Initial window size at startup |
|
||||
| win:autoResize | -a | no | Auto resize the window to the guest |
|
||||
| win:allowResize | -n | yes | Allow the window to be manually resized |
|
||||
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
|
||||
| win:forceAspect | | yes | Force the window to maintain the aspect ratio |
|
||||
| win:dontUpscale | | no | Never try to upscale the window |
|
||||
| win:borderless | -d | no | Borderless mode |
|
||||
| win:fullScreen | -F | no | Launch in fullscreen borderless mode |
|
||||
| win:maximize | -T | no | Launch window maximized |
|
||||
| win:minimizeOnFocusLoss | | yes | Minimize window on focus loss |
|
||||
| win:fpsMin | -K | -1 | Frame rate minimum (0 = disable - not recommended, -1 = auto detect) |
|
||||
| win:showFPS | -k | no | Enable the FPS & UPS display |
|
||||
| win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) |
|
||||
| win:noScreensaver | -S | no | Prevent the screensaver from starting |
|
||||
| win:alerts | -q | yes | Show on screen alert messages |
|
||||
| win:quickSplash | | no | Skip fading out the splash screen when a connection is established |
|
||||
| win:rotate | | 0 | Rotate the displayed image (0, 90, 180, 270) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | yes | Grab the keyboard in capture mode |
|
||||
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | no | Grab the keyboard in capture mode |
|
||||
| input:grabKeyboardOnFocus | | no | Grab the keyboard when focused |
|
||||
| input:releaseKeysOnFocusLoss | | yes | On focus loss, send key up events to guest for all held keys |
|
||||
| input:escapeKey | -m | 70 = KEY_SCROLLLOCK | Specify the escape key, see <linux/input-event-codes.h> for valid values |
|
||||
| input:ignoreWindowsKeys | | no | Do not pass events for the windows keys to the guest |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
| input:mouseSmoothing | | yes | Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing) |
|
||||
| input:rawMouse | | no | Use RAW mouse input when in capture mode (good for gaming) |
|
||||
| input:mouseRedraw | | yes | Mouse movements trigger redraws (ignores FPS minimum) |
|
||||
| input:autoCapture | | no | Try to keep the mouse captured when needed |
|
||||
| input:captureOnly | | no | Only enable input via SPICE if in capture mode |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
@@ -141,22 +173,33 @@ Command line arguments will override any options loaded from the config files.
|
||||
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
|
||||
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
|
||||
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
||||
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|
||||
| spice:alwaysShowCursor | | no | Always show host cursor |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|--------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|--------------------------------------------------------------------------|
|
||||
| egl:vsync | | no | Enable vsync |
|
||||
| egl:nvGainMax | | 1 | The maximum night vision gain |
|
||||
| egl:nvGain | | 0 | The initial night vision gain at startup |
|
||||
|--------------------------------------------------------------------------|
|
||||
|--------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|--------------------------------------------------------------------------------------------------------------|
|
||||
| egl:vsync | | no | Enable vsync |
|
||||
| egl:doubleBuffer | | no | Enable double buffering |
|
||||
| egl:multisample | | yes | Enable Multisampling |
|
||||
| egl:nvGainMax | | 1 | The maximum night vision gain |
|
||||
| egl:nvGain | | 0 | The initial night vision gain at startup |
|
||||
| egl:cbMode | | 0 | Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope) |
|
||||
|--------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------|
|
||||
| opengl:mipmap | | yes | Enable mipmapping |
|
||||
| opengl:vsync | | yes | Enable vsync |
|
||||
| opengl:vsync | | no | Enable vsync |
|
||||
| opengl:preventBuffer | | yes | Prevent the driver from buffering frames |
|
||||
| opengl:amdPinnedMem | | yes | Use GL_AMD_pinned_memory if it is available |
|
||||
|------------------------------------------------------------------------------------|
|
||||
|
||||
|-------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------|
|
||||
| wayland:warpSupport | | yes | Enable cursor warping |
|
||||
|-------------------------------------------------------------|
|
||||
```
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboards LANGUAGES C)
|
||||
|
||||
set(CLIPBOARD_H "${CMAKE_BINARY_DIR}/include/dynamic/clipboards.h")
|
||||
set(CLIPBOARD_C "${CMAKE_BINARY_DIR}/src/clipboards.c")
|
||||
|
||||
file(WRITE ${CLIPBOARD_H} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_H} "extern LG_Clipboard * LG_Clipboards[];\n\n")
|
||||
|
||||
file(WRITE ${CLIPBOARD_C} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(CLIPBOARDS "_")
|
||||
set(CLIPBOARDS_LINK "_")
|
||||
function(add_clipboard name)
|
||||
set(CLIPBOARDS "${CLIPBOARDS};${name}" PARENT_SCOPE)
|
||||
set(CLIPBOARDS_LINK "${CLIPBOARDS_LINK};clipboard_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove clipboards here!
|
||||
if (ENABLE_CB_X11)
|
||||
add_clipboard(X11)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT CLIPBOARDS 0)
|
||||
list(REMOVE_AT CLIPBOARDS_LINK 0)
|
||||
|
||||
list(LENGTH CLIPBOARDS CLIPBOARD_COUNT)
|
||||
file(APPEND ${CLIPBOARD_H} "#define LG_CLIPBOARD_COUNT ${CLIPBOARD_COUNT}\n")
|
||||
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} "extern LG_Clipboard LGC_${clipboard};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${CLIPBOARD_C} "\nconst LG_Clipboard * LG_Clipboards[] =\n{\n")
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} " &LGC_${clipboard},\n")
|
||||
endforeach()
|
||||
file(APPEND ${CLIPBOARD_C} " NULL\n};\n\n")
|
||||
|
||||
add_library(clipboards STATIC ${CLIPBOARD_C})
|
||||
target_link_libraries(clipboards ${CLIPBOARDS_LINK})
|
||||
@@ -1,26 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboard_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xfixes
|
||||
)
|
||||
|
||||
add_library(clipboard_X11 STATIC
|
||||
src/x11.c
|
||||
)
|
||||
|
||||
target_link_libraries(clipboard_X11
|
||||
${CLIPBOARD_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(clipboard_X11
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,359 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/clipboard.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
struct state
|
||||
{
|
||||
Display * display;
|
||||
Window window;
|
||||
Atom aSelection;
|
||||
Atom aCurSelection;
|
||||
Atom aTargets;
|
||||
Atom aSelData;
|
||||
Atom aIncr;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_ClipboardReleaseFn releaseFn;
|
||||
LG_ClipboardRequestFn requestFn;
|
||||
LG_ClipboardNotifyFn notifyFn;
|
||||
LG_ClipboardDataFn dataFn;
|
||||
LG_ClipboardData type;
|
||||
|
||||
// XFixes vars
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
};
|
||||
|
||||
static struct state * this = NULL;
|
||||
|
||||
static const char * atomTypes[] =
|
||||
{
|
||||
"UTF8_STRING",
|
||||
"image/png",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/jpeg"
|
||||
};
|
||||
|
||||
static const char * x11_cb_getName()
|
||||
{
|
||||
return "X11";
|
||||
}
|
||||
|
||||
static bool x11_cb_init(
|
||||
SDL_SysWMinfo * wminfo,
|
||||
LG_ClipboardReleaseFn releaseFn,
|
||||
LG_ClipboardNotifyFn notifyFn,
|
||||
LG_ClipboardDataFn dataFn)
|
||||
{
|
||||
// final sanity check
|
||||
if (wminfo->subsystem != SDL_SYSWM_X11)
|
||||
{
|
||||
DEBUG_ERROR("wrong subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this = (struct state *)malloc(sizeof(struct state));
|
||||
memset(this, 0, sizeof(struct state));
|
||||
|
||||
this->display = wminfo->info.x11.display;
|
||||
this->window = wminfo->info.x11.window;
|
||||
this->aSelection = XInternAtom(this->display, "CLIPBOARD", False);
|
||||
this->aTargets = XInternAtom(this->display, "TARGETS" , False);
|
||||
this->aSelData = XInternAtom(this->display, "SEL_DATA" , False);
|
||||
this->aIncr = XInternAtom(this->display, "INCR" , False);
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn = releaseFn;
|
||||
this->notifyFn = notifyFn;
|
||||
this->dataFn = dataFn;
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
{
|
||||
this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False);
|
||||
if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue)
|
||||
{
|
||||
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we need the raw X events
|
||||
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
||||
|
||||
// use xfixes to get clipboard change notifications
|
||||
if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase))
|
||||
{
|
||||
DEBUG_ERROR("failed to initialize xfixes");
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(this->display, this->window, XA_PRIMARY , XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(this->display, this->window, this->aSelection, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_cb_free()
|
||||
{
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size)
|
||||
{
|
||||
XEvent *s = (XEvent *)opaque;
|
||||
|
||||
XChangeProperty(
|
||||
this->display ,
|
||||
s->xselection.requestor,
|
||||
s->xselection.property ,
|
||||
s->xselection.target ,
|
||||
8,
|
||||
PropModeReplace,
|
||||
data,
|
||||
size);
|
||||
|
||||
XSendEvent(this->display, s->xselection.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void x11_cb_wmevent(SDL_SysWMmsg * msg)
|
||||
{
|
||||
XEvent e = msg->msg.x11.event;
|
||||
|
||||
if (e.type == SelectionRequest)
|
||||
{
|
||||
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.xselectionrequest.requestor;
|
||||
s->xselection.selection = e.xselectionrequest.selection;
|
||||
s->xselection.target = e.xselectionrequest.target;
|
||||
s->xselection.property = e.xselectionrequest.property;
|
||||
s->xselection.time = e.xselectionrequest.time;
|
||||
|
||||
if (!this->requestFn)
|
||||
{
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// target list requested
|
||||
if (e.xselectionrequest.target == this->aTargets)
|
||||
{
|
||||
Atom targets[2];
|
||||
targets[0] = this->aTargets;
|
||||
targets[1] = this->aTypes[this->type];
|
||||
|
||||
XChangeProperty(
|
||||
e.xselectionrequest.display,
|
||||
e.xselectionrequest.requestor,
|
||||
e.xselectionrequest.property,
|
||||
XA_ATOM,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)targets,
|
||||
sizeof(targets) / sizeof(Atom));
|
||||
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == e.xselectionrequest.target && this->type == i)
|
||||
{
|
||||
// request the data
|
||||
this->requestFn(x11_cb_reply_fn, s);
|
||||
return;
|
||||
}
|
||||
|
||||
// report no data
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
if (e.type == SelectionClear && (
|
||||
e.xselectionclear.selection == XA_PRIMARY ||
|
||||
e.xselectionclear.selection == this->aSelection)
|
||||
)
|
||||
{
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// if someone selected data
|
||||
if (e.type == this->eventBase + XFixesSelectionNotify)
|
||||
{
|
||||
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e;
|
||||
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if (
|
||||
(sne->selection != XA_PRIMARY && sne->selection != this->aSelection) ||
|
||||
sne->owner == this->window ||
|
||||
sne->owner == 0
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remember which selection we are working with
|
||||
this->aCurSelection = sne->selection;
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
sne->selection,
|
||||
this->aTargets,
|
||||
this->aTargets,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type == SelectionNotify)
|
||||
{
|
||||
if (e.xselection.property == None)
|
||||
return;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
|
||||
XGetWindowProperty(
|
||||
this->display,
|
||||
this->window,
|
||||
e.xselection.property,
|
||||
0, ~0L, // start and length
|
||||
True , // delete the property
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data);
|
||||
|
||||
// the target list
|
||||
if (e.xselection.property == this->aTargets)
|
||||
{
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
{
|
||||
if (data)
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const uint64_t * targets = (const uint64_t *)data;
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
{
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
if (this->aTypes[n] == targets[i])
|
||||
{
|
||||
// we have a match, so send the notification
|
||||
this->notifyFn(n);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no matches
|
||||
this->notifyFn(LG_CLIPBOARD_DATA_NONE);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format == this->aIncr)
|
||||
{
|
||||
DEBUG_WARN("fixme: large paste buffers are not yet supported");
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == type)
|
||||
{
|
||||
this->dataFn(i, data, itemCount);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type));
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type)
|
||||
{
|
||||
this->requestFn = requestFn;
|
||||
this->type = type;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_release()
|
||||
{
|
||||
this->requestFn = NULL;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_request(LG_ClipboardData type)
|
||||
{
|
||||
if (this->aCurSelection == BadValue)
|
||||
return;
|
||||
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
this->aCurSelection,
|
||||
this->aTypes[type],
|
||||
this->aSelData,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
const LG_Clipboard LGC_X11 =
|
||||
{
|
||||
.getName = x11_cb_getName,
|
||||
.init = x11_cb_init,
|
||||
.free = x11_cb_free,
|
||||
.wmevent = x11_cb_wmevent,
|
||||
.notice = x11_cb_notice,
|
||||
.release = x11_cb_release,
|
||||
.request = x11_cb_request
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(decoders LANGUAGES C)
|
||||
|
||||
#find_package(PkgConfig)
|
||||
#pkg_check_modules(DECODERS_PKGCONFIG REQUIRED
|
||||
#)
|
||||
|
||||
add_library(decoders STATIC
|
||||
src/null.c
|
||||
src/yuv420.c
|
||||
)
|
||||
|
||||
target_link_libraries(decoders
|
||||
lg_common
|
||||
${DECODERS_PKGCONFIG_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(decoders
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${DECODERS_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,981 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "lg-decoder.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "parsers/nal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <va/va_glx.h>
|
||||
|
||||
#define SURFACE_NUM 3
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
SDL_Window * window;
|
||||
VADisplay vaDisplay;
|
||||
int vaMajorVer, vaMinorVer;
|
||||
VASurfaceID vaSurfaceID[SURFACE_NUM];
|
||||
VAConfigID vaConfigID;
|
||||
VAContextID vaContextID;
|
||||
int lastSID;
|
||||
int currentSID;
|
||||
VAPictureH264 curPic;
|
||||
VAPictureH264 oldPic;
|
||||
int frameNum;
|
||||
int fieldCount;
|
||||
VABufferID picBufferID[SURFACE_NUM];
|
||||
VABufferID matBufferID[SURFACE_NUM];
|
||||
VABufferID sliBufferID[SURFACE_NUM];
|
||||
VABufferID datBufferID[SURFACE_NUM];
|
||||
bool t2First;
|
||||
int sliceType;
|
||||
|
||||
NAL nal;
|
||||
};
|
||||
|
||||
static const unsigned char MatrixBufferH264[] = {
|
||||
//ScalingList4x4[6][16]
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
//ScalingList8x8[2][64]
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
};
|
||||
|
||||
static bool lgd_h264_create (void ** opaque);
|
||||
static void lgd_h264_destroy (void * opaque);
|
||||
static bool lgd_h264_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_h264_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_h264_get_out_format (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque);
|
||||
static bool lgd_h264_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static bool lgd_h264_get_buffer (void * opaque, uint8_t * dst, size_t dstSize);
|
||||
|
||||
static bool lgd_h264_init_gl_texture (void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
static void lgd_h264_free_gl_texture (void * opaque, void * ref);
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref);
|
||||
|
||||
#define check_surface(x, y, z) _check_surface(__LINE__, x, y, z)
|
||||
static bool _check_surface(const unsigned int line, struct Inst * this, unsigned int sid, VASurfaceStatus *out)
|
||||
{
|
||||
VASurfaceStatus surfStatus;
|
||||
VAStatus status = vaQuerySurfaceStatus(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[sid],
|
||||
&surfStatus
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQuerySurfaceStatus: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
DEBUG_INFO("L%d: surface %u status: %d", line, sid, surfStatus);
|
||||
#endif
|
||||
if (out)
|
||||
*out = surfStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
|
||||
if (!nal_initialize(&this->nal))
|
||||
{
|
||||
DEBUG_INFO("Failed to initialize NAL parser");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
lgd_h264_deinitialize(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_destroy(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
nal_deinitialize(this->nal);
|
||||
lgd_h264_deinitialize(this);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool lgd_h264_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
lgd_h264_deinitialize(this);
|
||||
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->window = window;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("Failed to get SDL window WM Info");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
this->vaDisplay = vaGetDisplayGLX(wminfo.info.x11.display);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported window subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAStatus status;
|
||||
status = vaInitialize(this->vaDisplay, &this->vaMajorVer, &this->vaMinorVer);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaInitialize Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Vendor: %s", vaQueryVendorString(this->vaDisplay));
|
||||
|
||||
VAEntrypoint entryPoints[5];
|
||||
int entryPointCount;
|
||||
|
||||
status = vaQueryConfigEntrypoints(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
entryPoints,
|
||||
&entryPointCount
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQueryConfigEntrypoints Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ep;
|
||||
for(ep = 0; ep < entryPointCount; ++ep)
|
||||
if (entryPoints[ep] == VAEntrypointVLD)
|
||||
break;
|
||||
|
||||
if (ep == entryPointCount)
|
||||
{
|
||||
DEBUG_ERROR("Failed to find VAEntrypointVLD index");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAConfigAttrib attrib;
|
||||
attrib.type = VAConfigAttribRTFormat;
|
||||
vaGetConfigAttributes(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1);
|
||||
|
||||
if (!(attrib.value & VA_RT_FORMAT_YUV420))
|
||||
{
|
||||
DEBUG_ERROR("Failed to find desired YUV420 RT format");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateConfig(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1,
|
||||
&this->vaConfigID);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateConfig");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateSurfaces(
|
||||
this->vaDisplay,
|
||||
VA_RT_FORMAT_YUV420,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateSurfaces");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
if (!check_surface(this, i, NULL))
|
||||
return false;
|
||||
|
||||
status = vaCreateContext(
|
||||
this->vaDisplay,
|
||||
this->vaConfigID,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
VA_PROGRESSIVE,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
&this->vaContextID
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateContext");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->currentSID = 0;
|
||||
this->sliceType = 2;
|
||||
this->t2First = true;
|
||||
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[0]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
{
|
||||
if (this->picBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->picBufferID[i]);
|
||||
|
||||
if (this->matBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->matBufferID[i]);
|
||||
|
||||
if (this->sliBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->sliBufferID[i]);
|
||||
|
||||
if (this->datBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->datBufferID[i]);
|
||||
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
}
|
||||
|
||||
if (this->vaSurfaceID[0] != VA_INVALID_ID)
|
||||
vaDestroySurfaces(this->vaDisplay, this->vaSurfaceID, SURFACE_NUM);
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
|
||||
if (this->vaContextID != VA_INVALID_ID)
|
||||
vaDestroyContext(this->vaDisplay, this->vaContextID);
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaConfigID != VA_INVALID_ID)
|
||||
vaDestroyConfig(this->vaDisplay, this->vaConfigID);
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaDisplay)
|
||||
vaTerminate(this->vaDisplay);
|
||||
this->vaDisplay = NULL;
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_h264_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_YUV420;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool get_buffer(struct Inst * this, const VABufferType type, const unsigned int size, VABufferID * buf_id)
|
||||
{
|
||||
if (*buf_id != VA_INVALID_ID)
|
||||
return true;
|
||||
|
||||
VAStatus status = vaCreateBuffer(this->vaDisplay, this->vaContextID, type, size, 1, NULL, buf_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create buffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_surface(this, this->currentSID, NULL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_pic_buffer(struct Inst * this, const NAL_SLICE * slice)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * picBufferID = &this->picBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), picBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get picBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAPictureParameterBufferH264 *p;
|
||||
status = vaMapBuffer(this->vaDisplay, *picBufferID, (void **)&p);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_SPS * sps;
|
||||
if (!nal_get_sps(this->nal, &sps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_sps");
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_PPS * pps;
|
||||
if (!nal_get_pps(this->nal, &pps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_pps");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(p, 0, sizeof(VAPictureParameterBufferH264));
|
||||
p->picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1;
|
||||
p->picture_height_in_mbs_minus1 = sps->pic_height_in_map_units_minus1;
|
||||
p->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8;
|
||||
p->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8;
|
||||
p->num_ref_frames = sps->num_ref_frames;
|
||||
|
||||
p->seq_fields.value = 0;
|
||||
p->seq_fields.bits.chroma_format_idc = sps->chroma_format_idc;
|
||||
p->seq_fields.bits.residual_colour_transform_flag = sps->gaps_in_frame_num_value_allowed_flag;
|
||||
p->seq_fields.bits.frame_mbs_only_flag = sps->frame_mbs_only_flag;
|
||||
p->seq_fields.bits.mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag;
|
||||
p->seq_fields.bits.direct_8x8_inference_flag = sps->direct_8x8_inference_flag;
|
||||
p->seq_fields.bits.MinLumaBiPredSize8x8 = sps->level_idc >= 31;
|
||||
p->seq_fields.bits.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4;
|
||||
p->seq_fields.bits.pic_order_cnt_type = sps->pic_order_cnt_type;
|
||||
p->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4;
|
||||
p->seq_fields.bits.delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag;
|
||||
|
||||
#if 0
|
||||
// these are deprecated, FMO is not supported
|
||||
p->num_slice_groups_minus1 = pps->num_slice_groups_minus1;
|
||||
p->slice_group_map_type = pps->slice_group_map_type;
|
||||
p->slice_group_change_rate_minus1 = pps->slice_group_change_rate_minus1;
|
||||
#endif
|
||||
|
||||
p->pic_init_qp_minus26 = pps->pic_init_qp_minus26;
|
||||
p->pic_init_qs_minus26 = pps->pic_init_qs_minus26;
|
||||
p->chroma_qp_index_offset = pps->chroma_qp_index_offset;
|
||||
p->second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset;
|
||||
|
||||
p->pic_fields.value = 0;
|
||||
p->pic_fields.bits.entropy_coding_mode_flag = pps->entropy_coding_mode_flag;
|
||||
p->pic_fields.bits.weighted_pred_flag = pps->weighted_pred_flag;
|
||||
p->pic_fields.bits.weighted_bipred_idc = pps->weighted_bipred_idc;
|
||||
p->pic_fields.bits.transform_8x8_mode_flag = pps->transform_8x8_mode_flag;
|
||||
p->pic_fields.bits.field_pic_flag = slice->field_pic_flag;
|
||||
p->pic_fields.bits.constrained_intra_pred_flag = pps->constrained_intra_pred_flag;
|
||||
p->pic_fields.bits.pic_order_present_flag = pps->pic_order_present_flag;
|
||||
p->pic_fields.bits.deblocking_filter_control_present_flag = pps->deblocking_filter_control_present_flag;
|
||||
p->pic_fields.bits.redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag;
|
||||
p->pic_fields.bits.reference_pic_flag = slice->nal_ref_idc != 0;
|
||||
|
||||
p->frame_num = slice->frame_num;
|
||||
|
||||
for(int i = 0; i < 16; ++i)
|
||||
{
|
||||
p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
|
||||
p->ReferenceFrames[i].picture_id = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
this->curPic.picture_id = this->vaSurfaceID[this->currentSID];
|
||||
this->curPic.frame_idx = p->frame_num;
|
||||
this->curPic.flags = 0;
|
||||
this->curPic.BottomFieldOrderCnt = this->fieldCount;
|
||||
this->curPic.TopFieldOrderCnt = this->fieldCount;
|
||||
memcpy(&p->CurrPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
if (this->sliceType != 2)
|
||||
{
|
||||
memcpy(&p->ReferenceFrames[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
p->ReferenceFrames[0].flags = 0;
|
||||
}
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *picBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_mat_buffer(struct Inst * this)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * matBufferID = &this->matBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), matBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get matBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAIQMatrixBufferH264 * m;
|
||||
status = vaMapBuffer(this->vaDisplay, *matBufferID, (void **)&m);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(m, MatrixBufferH264, sizeof(MatrixBufferH264));
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *matBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fill_pred_weight_table(
|
||||
NAL_PW_TABLE_L * list,
|
||||
uint32_t active,
|
||||
uint32_t luma_log2_weight_denom,
|
||||
uint8_t luma_weight_flag,
|
||||
short luma_weight[32],
|
||||
short luma_offset[32],
|
||||
uint32_t chroma_log2_weight_denom,
|
||||
uint8_t chroma_weight_flag,
|
||||
short chroma_weight[32][2],
|
||||
short chroma_offset[32][2]
|
||||
)
|
||||
{
|
||||
assert(active < 32);
|
||||
|
||||
for(uint32_t i = 0; i <= active; ++i)
|
||||
{
|
||||
NAL_PW_TABLE_L * l = &list[i];
|
||||
if (luma_weight_flag)
|
||||
{
|
||||
luma_weight[i] = l->luma_weight;
|
||||
luma_offset[i] = l->luma_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
luma_weight[i] = 1 << luma_log2_weight_denom;
|
||||
luma_weight[i] = 0;
|
||||
}
|
||||
|
||||
if (chroma_weight_flag)
|
||||
{
|
||||
chroma_weight[i][0] = l->chroma_weight[0];
|
||||
chroma_offset[i][0] = l->chroma_offset[0];
|
||||
chroma_weight[i][1] = l->chroma_weight[1];
|
||||
chroma_offset[i][1] = l->chroma_offset[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
chroma_weight[i][0] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][0] = 0;
|
||||
chroma_weight[i][1] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool setup_sli_buffer(struct Inst * this, size_t srcSize, const NAL_SLICE * slice, const size_t seek)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * sliBufferID = &this->sliBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), sliBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get sliBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VASliceParameterBufferH264 * s;
|
||||
status = vaMapBuffer(this->vaDisplay, *sliBufferID, (void **)&s);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(s, 0, sizeof(VASliceParameterBufferH264));
|
||||
|
||||
s->slice_data_size = srcSize;
|
||||
s->slice_data_bit_offset = seek << 3;
|
||||
s->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
|
||||
s->first_mb_in_slice = slice->first_mb_in_slice;
|
||||
s->slice_type = slice->slice_type;
|
||||
s->direct_spatial_mv_pred_flag = slice->direct_spatial_mv_pred_flag;
|
||||
s->num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1;
|
||||
s->num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1;
|
||||
s->cabac_init_idc = slice->cabac_init_idc;
|
||||
s->slice_qp_delta = slice->slice_qp_delta;
|
||||
s->disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc;
|
||||
s->slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2;
|
||||
s->slice_beta_offset_div2 = slice->slice_beta_offset_div2;
|
||||
s->luma_log2_weight_denom = slice->pred_weight_table.luma_log2_weight_denom;
|
||||
s->chroma_log2_weight_denom = slice->pred_weight_table.chroma_log2_weight_denom;
|
||||
s->luma_weight_l0_flag = slice->pred_weight_table.luma_weight_flag [0];
|
||||
s->chroma_weight_l0_flag = slice->pred_weight_table.chroma_weight_flag[0];
|
||||
s->luma_weight_l1_flag = slice->pred_weight_table.luma_weight_flag [1];
|
||||
s->chroma_weight_l1_flag = slice->pred_weight_table.chroma_weight_flag[1];
|
||||
|
||||
//RefPicList0/1
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l0,
|
||||
s->num_ref_idx_l0_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l0_flag,
|
||||
s->luma_weight_l0,
|
||||
s->luma_offset_l0,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l0_flag,
|
||||
s->chroma_weight_l0,
|
||||
s->chroma_weight_l0
|
||||
);
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l1,
|
||||
s->num_ref_idx_l1_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l1_flag,
|
||||
s->luma_weight_l1,
|
||||
s->luma_offset_l1,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l1_flag,
|
||||
s->chroma_weight_l1,
|
||||
s->chroma_weight_l1
|
||||
);
|
||||
|
||||
#if 0
|
||||
if (this->sliceType == 2)
|
||||
{
|
||||
set_slice_parameter_buffer_t2(s, this->t2First);
|
||||
this->t2First = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_slice_parameter_buffer(s);
|
||||
memcpy(&s->RefPicList0[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
s->RefPicList0[0].flags = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *sliBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_dat_buffer(struct Inst * this, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * datBufferID = &this->datBufferID[this->currentSID];
|
||||
|
||||
if (!get_buffer(this, VASliceDataBufferType, srcSize, datBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get datBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, *datBufferID, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(d, src, srcSize);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *datBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
size_t seek;
|
||||
if (!nal_parse(this->nal, src, srcSize, &seek))
|
||||
{
|
||||
DEBUG_WARN("nal_parse, perhaps mid stream");
|
||||
return true;
|
||||
}
|
||||
|
||||
const NAL_SLICE * slice;
|
||||
if (!nal_get_slice(this->nal, &slice))
|
||||
{
|
||||
DEBUG_WARN("nal_get_slice failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(seek < srcSize);
|
||||
this->sliceType = slice->slice_type;
|
||||
|
||||
// don't start until we have an I-FRAME
|
||||
if (this->frameNum == 0 && this->sliceType != NAL_SLICE_TYPE_I)
|
||||
return true;
|
||||
|
||||
{
|
||||
if (!setup_pic_buffer(this, slice)) return false;
|
||||
if (!setup_mat_buffer(this)) return false;
|
||||
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->picBufferID[this->currentSID],
|
||||
this->matBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->picBufferID[this->currentSID] =
|
||||
this->matBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!setup_sli_buffer(this, srcSize, slice, seek)) return false;
|
||||
if (!setup_dat_buffer(this, src, srcSize )) return false;
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->sliBufferID[this->currentSID],
|
||||
this->datBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->sliBufferID[this->currentSID] =
|
||||
this->datBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
status = vaEndPicture(this->vaDisplay, this->vaContextID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaEndPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// advance to the next surface and save the old picture info
|
||||
this->lastSID = this->currentSID;
|
||||
if (++this->currentSID == SURFACE_NUM)
|
||||
this->currentSID = 0;
|
||||
this->frameNum += 1;
|
||||
this->fieldCount += 2;
|
||||
memcpy(&this->oldPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
// prepare the next surface
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[this->currentSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_get_buffer(void * opaque, uint8_t * dst, size_t dstSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
// ensure the surface is ready
|
||||
status = vaSyncSurface(this->vaDisplay, this->vaSurfaceID[this->lastSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// this doesn't work on my system, seems the vdpau va driver is bugged
|
||||
VASurfaceStatus surfStatus;
|
||||
if (!check_surface(this, this->lastSID, &surfStatus))
|
||||
return false;
|
||||
|
||||
if (surfStatus != VASurfaceReady)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface didn't block, the surface is not ready!");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// get the decoded data
|
||||
VAImage decoded =
|
||||
{
|
||||
.image_id = VA_INVALID_ID,
|
||||
.buf = VA_INVALID_ID
|
||||
};
|
||||
|
||||
status = vaDeriveImage(this->vaDisplay, this->vaSurfaceID[this->lastSID], &decoded);
|
||||
if (status == VA_STATUS_ERROR_OPERATION_FAILED)
|
||||
{
|
||||
VAImageFormat format =
|
||||
{
|
||||
.fourcc = VA_FOURCC_NV12,
|
||||
.byte_order = VA_LSB_FIRST,
|
||||
.bits_per_pixel = 12
|
||||
};
|
||||
|
||||
status = vaCreateImage(
|
||||
this->vaDisplay,
|
||||
&format,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
&decoded
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaPutImage(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
decoded.image_id,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaPutImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDeriveImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, decoded.buf, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(dst, d, decoded.data_size);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, decoded.buf);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDestroyImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaCreateSurfaceGLX(this->vaDisplay, target, texture, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
*ref = NULL;
|
||||
DEBUG_ERROR("vaCreateSurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaDestroySurfaceGLX(this->vaDisplay, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
DEBUG_ERROR("vaDestroySurfaceGLX: %s", vaErrorStr(status));
|
||||
}
|
||||
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
status = vaCopySurfaceGLX(
|
||||
this->vaDisplay,
|
||||
ref,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
0
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCopySurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_H264 =
|
||||
{
|
||||
.name = "H.264",
|
||||
.create = lgd_h264_create,
|
||||
.destroy = lgd_h264_destroy,
|
||||
.initialize = lgd_h264_initialize,
|
||||
.deinitialize = lgd_h264_deinitialize,
|
||||
.get_out_format = lgd_h264_get_out_format,
|
||||
.get_frame_pitch = lgd_h264_get_frame_pitch,
|
||||
.get_frame_stride = lgd_h264_get_frame_stride,
|
||||
.decode = lgd_h264_decode,
|
||||
.get_buffer = lgd_h264_get_buffer,
|
||||
|
||||
.has_gl = true,
|
||||
.init_gl_texture = lgd_h264_init_gl_texture,
|
||||
.free_gl_texture = lgd_h264_free_gl_texture,
|
||||
.update_gl_texture = lgd_h264_update_gl_texture
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
const uint8_t * src;
|
||||
};
|
||||
|
||||
static bool lgd_null_create (void ** opaque);
|
||||
static void lgd_null_destroy (void * opaque);
|
||||
static bool lgd_null_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_null_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_null_get_out_format (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque);
|
||||
static bool lgd_null_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_null_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_null_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_null_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memset(this, 0, sizeof(struct Inst));
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_null_get_out_format(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
switch(this->format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA : return LG_OUTPUT_BGRA;
|
||||
case FRAME_TYPE_RGBA : return LG_OUTPUT_RGBA;
|
||||
case FRAME_TYPE_RGBA10: return LG_OUTPUT_RGBA10;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown frame type");
|
||||
return LG_OUTPUT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.pitch;
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.stride;
|
||||
}
|
||||
|
||||
static bool lgd_null_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->src = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_null_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->src)
|
||||
return NULL;
|
||||
|
||||
return this->src;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_NULL =
|
||||
{
|
||||
.name = "NULL",
|
||||
.create = lgd_null_create,
|
||||
.destroy = lgd_null_destroy,
|
||||
.initialize = lgd_null_initialize,
|
||||
.deinitialize = lgd_null_deinitialize,
|
||||
.get_out_format = lgd_null_get_out_format,
|
||||
.get_frame_pitch = lgd_null_get_frame_pitch,
|
||||
.get_frame_stride = lgd_null_get_frame_stride,
|
||||
.decode = lgd_null_decode,
|
||||
.get_buffer = lgd_null_get_buffer
|
||||
};
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
struct Pixel
|
||||
{
|
||||
uint8_t b, g, r, a;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
struct Pixel * pixels;
|
||||
unsigned int yBytes;
|
||||
};
|
||||
|
||||
static bool lgd_yuv420_create (void ** opaque);
|
||||
static void lgd_yuv420_destroy (void * opaque);
|
||||
static bool lgd_yuv420_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_yuv420_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_yuv420_get_out_format (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque);
|
||||
static bool lgd_yuv420_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_yuv420_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_yuv420_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
this->yBytes = format.width * format.height;
|
||||
this->pixels = malloc(sizeof(struct Pixel) * (format.width * format.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
free(this->pixels);
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_yuv420_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_BGRA;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
//FIXME: implement this properly using GLSL
|
||||
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
const unsigned int hw = this->format.width / 2;
|
||||
const unsigned int hp = this->yBytes / 4;
|
||||
|
||||
for(size_t y = 0; y < this->format.height; ++y)
|
||||
for(size_t x = 0; x < this->format.width; ++x)
|
||||
{
|
||||
const unsigned int yoff = y * this->format.width + x;
|
||||
const unsigned int uoff = this->yBytes + ((y / 2) * hw + x / 2);
|
||||
const unsigned int voff = uoff + hp;
|
||||
|
||||
float b = 1.164f * ((float)src[yoff] - 16.0f) + 2.018f * ((float)src[uoff] - 128.0f);
|
||||
float g = 1.164f * ((float)src[yoff] - 16.0f) - 0.813f * ((float)src[voff] - 128.0f) - 0.391f * ((float)src[uoff] - 128.0f);
|
||||
float r = 1.164f * ((float)src[yoff] - 16.0f) + 1.596f * ((float)src[voff] - 128.0f);
|
||||
|
||||
#define CLAMP(x) (x < 0 ? 0 : (x > 255 ? 255 : x))
|
||||
this->pixels[yoff].b = CLAMP(b);
|
||||
this->pixels[yoff].g = CLAMP(g);
|
||||
this->pixels[yoff].r = CLAMP(r);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_yuv420_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return (uint8_t *)this->pixels;
|
||||
}
|
||||
|
||||
bool lgd_yuv420_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void lgd_yuv420_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool lgd_yuv420_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_YUV420 =
|
||||
{
|
||||
.name = "YUV420",
|
||||
.create = lgd_yuv420_create,
|
||||
.destroy = lgd_yuv420_destroy,
|
||||
.initialize = lgd_yuv420_initialize,
|
||||
.deinitialize = lgd_yuv420_deinitialize,
|
||||
.get_out_format = lgd_yuv420_get_out_format,
|
||||
.get_frame_pitch = lgd_yuv420_get_frame_pitch,
|
||||
.get_frame_stride = lgd_yuv420_get_frame_stride,
|
||||
.decode = lgd_yuv420_decode,
|
||||
.get_buffer = lgd_yuv420_get_buffer,
|
||||
|
||||
.has_gl = false, //FIXME: Implement this
|
||||
.init_gl_texture = lgd_yuv420_init_gl_texture,
|
||||
.free_gl_texture = lgd_yuv420_free_gl_texture,
|
||||
.update_gl_texture = lgd_yuv420_update_gl_texture
|
||||
};
|
||||
50
client/displayservers/CMakeLists.txt
Normal file
50
client/displayservers/CMakeLists.txt
Normal file
@@ -0,0 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayservers LANGUAGES C)
|
||||
|
||||
set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h")
|
||||
set(DISPLAYSERVER_C "${CMAKE_BINARY_DIR}/src/displayservers.c")
|
||||
|
||||
file(WRITE ${DISPLAYSERVER_H} "#include \"interface/displayserver.h\"\n\n")
|
||||
file(APPEND ${DISPLAYSERVER_H} "extern struct LG_DisplayServerOps * LG_DisplayServers[];\n\n")
|
||||
|
||||
file(WRITE ${DISPLAYSERVER_C} "#include \"interface/displayserver.h\"\n\n")
|
||||
file(APPEND ${DISPLAYSERVER_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(DISPLAYSERVERS "_")
|
||||
set(DISPLAYSERVERS_LINK "_")
|
||||
function(add_displayserver name)
|
||||
set(DISPLAYSERVERS "${DISPLAYSERVERS};${name}" PARENT_SCOPE)
|
||||
set(DISPLAYSERVERS_LINK "${DISPLAYSERVERS_LINK};displayserver_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove displayservers here!
|
||||
if (ENABLE_WAYLAND)
|
||||
add_displayserver(Wayland)
|
||||
endif()
|
||||
|
||||
if (ENABLE_X11)
|
||||
add_displayserver(X11)
|
||||
endif()
|
||||
|
||||
# SDL must be last as it's the fallback implemntation
|
||||
add_displayserver(SDL)
|
||||
|
||||
list(REMOVE_AT DISPLAYSERVERS 0)
|
||||
list(REMOVE_AT DISPLAYSERVERS_LINK 0)
|
||||
|
||||
list(LENGTH DISPLAYSERVERS DISPLAYSERVER_COUNT)
|
||||
file(APPEND ${DISPLAYSERVER_H} "#define LG_DISPLAYSERVER_COUNT ${DISPLAYSERVER_COUNT}\n")
|
||||
|
||||
foreach(displayserver ${DISPLAYSERVERS})
|
||||
file(APPEND ${DISPLAYSERVER_C} "extern struct LG_DisplayServerOps LGDS_${displayserver};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${DISPLAYSERVER_C} "\nconst struct LG_DisplayServerOps * LG_DisplayServers[] =\n{\n")
|
||||
foreach(displayserver ${DISPLAYSERVERS})
|
||||
file(APPEND ${DISPLAYSERVER_C} " &LGDS_${displayserver},\n")
|
||||
endforeach()
|
||||
file(APPEND ${DISPLAYSERVER_C} " NULL\n};")
|
||||
|
||||
add_library(displayservers STATIC ${DISPLAYSERVER_C})
|
||||
target_link_libraries(displayservers ${DISPLAYSERVERS_LINK})
|
||||
24
client/displayservers/SDL/CMakeLists.txt
Normal file
24
client/displayservers/SDL/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_SDL LANGUAGES C)
|
||||
|
||||
#find_package(PkgConfig)
|
||||
#pkg_check_modules(DISPLAYSERVER_SDL_PKGCONFIG REQUIRED
|
||||
# #sdl2
|
||||
#)
|
||||
|
||||
add_library(displayserver_SDL STATIC
|
||||
sdl.c
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_SDL
|
||||
${DISPLAYSERVER_SDL_PKGCONFIG_LIBRARIES}
|
||||
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_SDL
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_SDL_PKGCONFIG_INCLUDE_DIRS}
|
||||
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
196
client/displayservers/SDL/kb.h
Normal file
196
client/displayservers/SDL/kb.h
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
//FIXME: this should be made static once config.c is no longer using SDL
|
||||
//scancodes
|
||||
uint32_t sdl_to_xfree86[SDL_NUM_SCANCODES] =
|
||||
{
|
||||
[SDL_SCANCODE_UNKNOWN] /* = USB 0 */ = KEY_RESERVED,
|
||||
[SDL_SCANCODE_A] /* = USB 4 */ = KEY_A,
|
||||
[SDL_SCANCODE_B] /* = USB 5 */ = KEY_B,
|
||||
[SDL_SCANCODE_C] /* = USB 6 */ = KEY_C,
|
||||
[SDL_SCANCODE_D] /* = USB 7 */ = KEY_D,
|
||||
[SDL_SCANCODE_E] /* = USB 8 */ = KEY_E,
|
||||
[SDL_SCANCODE_F] /* = USB 9 */ = KEY_F,
|
||||
[SDL_SCANCODE_G] /* = USB 10 */ = KEY_G,
|
||||
[SDL_SCANCODE_H] /* = USB 11 */ = KEY_H,
|
||||
[SDL_SCANCODE_I] /* = USB 12 */ = KEY_I,
|
||||
[SDL_SCANCODE_J] /* = USB 13 */ = KEY_J,
|
||||
[SDL_SCANCODE_K] /* = USB 14 */ = KEY_K,
|
||||
[SDL_SCANCODE_L] /* = USB 15 */ = KEY_L,
|
||||
[SDL_SCANCODE_M] /* = USB 16 */ = KEY_M,
|
||||
[SDL_SCANCODE_N] /* = USB 17 */ = KEY_N,
|
||||
[SDL_SCANCODE_O] /* = USB 18 */ = KEY_O,
|
||||
[SDL_SCANCODE_P] /* = USB 19 */ = KEY_P,
|
||||
[SDL_SCANCODE_Q] /* = USB 20 */ = KEY_Q,
|
||||
[SDL_SCANCODE_R] /* = USB 21 */ = KEY_R,
|
||||
[SDL_SCANCODE_S] /* = USB 22 */ = KEY_S,
|
||||
[SDL_SCANCODE_T] /* = USB 23 */ = KEY_T,
|
||||
[SDL_SCANCODE_U] /* = USB 24 */ = KEY_U,
|
||||
[SDL_SCANCODE_V] /* = USB 25 */ = KEY_V,
|
||||
[SDL_SCANCODE_W] /* = USB 26 */ = KEY_W,
|
||||
[SDL_SCANCODE_X] /* = USB 27 */ = KEY_X,
|
||||
[SDL_SCANCODE_Y] /* = USB 28 */ = KEY_Y,
|
||||
[SDL_SCANCODE_Z] /* = USB 29 */ = KEY_Z,
|
||||
[SDL_SCANCODE_1] /* = USB 30 */ = KEY_1,
|
||||
[SDL_SCANCODE_2] /* = USB 31 */ = KEY_2,
|
||||
[SDL_SCANCODE_3] /* = USB 32 */ = KEY_3,
|
||||
[SDL_SCANCODE_4] /* = USB 33 */ = KEY_4,
|
||||
[SDL_SCANCODE_5] /* = USB 34 */ = KEY_5,
|
||||
[SDL_SCANCODE_6] /* = USB 35 */ = KEY_6,
|
||||
[SDL_SCANCODE_7] /* = USB 36 */ = KEY_7,
|
||||
[SDL_SCANCODE_8] /* = USB 37 */ = KEY_8,
|
||||
[SDL_SCANCODE_9] /* = USB 38 */ = KEY_9,
|
||||
[SDL_SCANCODE_0] /* = USB 39 */ = KEY_0,
|
||||
[SDL_SCANCODE_RETURN] /* = USB 40 */ = KEY_ENTER,
|
||||
[SDL_SCANCODE_ESCAPE] /* = USB 41 */ = KEY_ESC,
|
||||
[SDL_SCANCODE_BACKSPACE] /* = USB 42 */ = KEY_BACKSPACE,
|
||||
[SDL_SCANCODE_TAB] /* = USB 43 */ = KEY_TAB,
|
||||
[SDL_SCANCODE_SPACE] /* = USB 44 */ = KEY_SPACE,
|
||||
[SDL_SCANCODE_MINUS] /* = USB 45 */ = KEY_MINUS,
|
||||
[SDL_SCANCODE_EQUALS] /* = USB 46 */ = KEY_EQUAL,
|
||||
[SDL_SCANCODE_LEFTBRACKET] /* = USB 47 */ = KEY_LEFTBRACE,
|
||||
[SDL_SCANCODE_RIGHTBRACKET] /* = USB 48 */ = KEY_RIGHTBRACE,
|
||||
[SDL_SCANCODE_BACKSLASH] /* = USB 49 */ = KEY_BACKSLASH,
|
||||
[SDL_SCANCODE_SEMICOLON] /* = USB 51 */ = KEY_SEMICOLON,
|
||||
[SDL_SCANCODE_APOSTROPHE] /* = USB 52 */ = KEY_APOSTROPHE,
|
||||
[SDL_SCANCODE_GRAVE] /* = USB 53 */ = KEY_GRAVE,
|
||||
[SDL_SCANCODE_COMMA] /* = USB 54 */ = KEY_COMMA,
|
||||
[SDL_SCANCODE_PERIOD] /* = USB 55 */ = KEY_DOT,
|
||||
[SDL_SCANCODE_SLASH] /* = USB 56 */ = KEY_SLASH,
|
||||
[SDL_SCANCODE_CAPSLOCK] /* = USB 57 */ = KEY_CAPSLOCK,
|
||||
[SDL_SCANCODE_F1] /* = USB 58 */ = KEY_F1,
|
||||
[SDL_SCANCODE_F2] /* = USB 59 */ = KEY_F2,
|
||||
[SDL_SCANCODE_F3] /* = USB 60 */ = KEY_F3,
|
||||
[SDL_SCANCODE_F4] /* = USB 61 */ = KEY_F4,
|
||||
[SDL_SCANCODE_F5] /* = USB 62 */ = KEY_F5,
|
||||
[SDL_SCANCODE_F6] /* = USB 63 */ = KEY_F6,
|
||||
[SDL_SCANCODE_F7] /* = USB 64 */ = KEY_F7,
|
||||
[SDL_SCANCODE_F8] /* = USB 65 */ = KEY_F8,
|
||||
[SDL_SCANCODE_F9] /* = USB 66 */ = KEY_F9,
|
||||
[SDL_SCANCODE_F10] /* = USB 67 */ = KEY_F10,
|
||||
[SDL_SCANCODE_F11] /* = USB 68 */ = KEY_F11,
|
||||
[SDL_SCANCODE_F12] /* = USB 69 */ = KEY_F12,
|
||||
[SDL_SCANCODE_PRINTSCREEN] /* = USB 70 */ = KEY_SYSRQ,
|
||||
[SDL_SCANCODE_SCROLLLOCK] /* = USB 71 */ = KEY_SCROLLLOCK,
|
||||
[SDL_SCANCODE_PAUSE] /* = USB 72 */ = KEY_PAUSE,
|
||||
[SDL_SCANCODE_INSERT] /* = USB 73 */ = KEY_INSERT,
|
||||
[SDL_SCANCODE_HOME] /* = USB 74 */ = KEY_HOME,
|
||||
[SDL_SCANCODE_PAGEUP] /* = USB 75 */ = KEY_PAGEUP,
|
||||
[SDL_SCANCODE_DELETE] /* = USB 76 */ = KEY_DELETE,
|
||||
[SDL_SCANCODE_END] /* = USB 77 */ = KEY_END,
|
||||
[SDL_SCANCODE_PAGEDOWN] /* = USB 78 */ = KEY_PAGEDOWN,
|
||||
[SDL_SCANCODE_RIGHT] /* = USB 79 */ = KEY_RIGHT,
|
||||
[SDL_SCANCODE_LEFT] /* = USB 80 */ = KEY_LEFT,
|
||||
[SDL_SCANCODE_DOWN] /* = USB 81 */ = KEY_DOWN,
|
||||
[SDL_SCANCODE_UP] /* = USB 82 */ = KEY_UP,
|
||||
[SDL_SCANCODE_NUMLOCKCLEAR] /* = USB 83 */ = KEY_NUMLOCK,
|
||||
[SDL_SCANCODE_KP_DIVIDE] /* = USB 84 */ = KEY_KPSLASH,
|
||||
[SDL_SCANCODE_KP_MULTIPLY] /* = USB 85 */ = KEY_KPASTERISK,
|
||||
[SDL_SCANCODE_KP_MINUS] /* = USB 86 */ = KEY_KPMINUS,
|
||||
[SDL_SCANCODE_KP_PLUS] /* = USB 87 */ = KEY_KPPLUS,
|
||||
[SDL_SCANCODE_KP_ENTER] /* = USB 88 */ = KEY_KPENTER,
|
||||
[SDL_SCANCODE_KP_1] /* = USB 89 */ = KEY_KP1,
|
||||
[SDL_SCANCODE_KP_2] /* = USB 90 */ = KEY_KP2,
|
||||
[SDL_SCANCODE_KP_3] /* = USB 91 */ = KEY_KP3,
|
||||
[SDL_SCANCODE_KP_4] /* = USB 92 */ = KEY_KP4,
|
||||
[SDL_SCANCODE_KP_5] /* = USB 93 */ = KEY_KP5,
|
||||
[SDL_SCANCODE_KP_6] /* = USB 94 */ = KEY_KP6,
|
||||
[SDL_SCANCODE_KP_7] /* = USB 95 */ = KEY_KP7,
|
||||
[SDL_SCANCODE_KP_8] /* = USB 96 */ = KEY_KP8,
|
||||
[SDL_SCANCODE_KP_9] /* = USB 97 */ = KEY_KP9,
|
||||
[SDL_SCANCODE_KP_0] /* = USB 98 */ = KEY_KP0,
|
||||
[SDL_SCANCODE_KP_PERIOD] /* = USB 99 */ = KEY_KPDOT,
|
||||
[SDL_SCANCODE_NONUSBACKSLASH] /* = USB 100 */ = KEY_102ND,
|
||||
[SDL_SCANCODE_APPLICATION] /* = USB 101 */ = KEY_COMPOSE,
|
||||
[SDL_SCANCODE_POWER] /* = USB 102 */ = KEY_POWER,
|
||||
[SDL_SCANCODE_KP_EQUALS] /* = USB 103 */ = KEY_KPEQUAL,
|
||||
[SDL_SCANCODE_F13] /* = USB 104 */ = KEY_CONFIG,
|
||||
[SDL_SCANCODE_F14] /* = USB 105 */ = KEY_F14,
|
||||
[SDL_SCANCODE_F15] /* = USB 106 */ = KEY_F15,
|
||||
[SDL_SCANCODE_F16] /* = USB 107 */ = KEY_F16,
|
||||
[SDL_SCANCODE_F17] /* = USB 108 */ = KEY_F17,
|
||||
[SDL_SCANCODE_F18] /* = USB 109 */ = KEY_F18,
|
||||
[SDL_SCANCODE_F19] /* = USB 110 */ = KEY_F19,
|
||||
[SDL_SCANCODE_F20] /* = USB 111 */ = KEY_F20,
|
||||
[SDL_SCANCODE_HELP] /* = USB 117 */ = KEY_HELP,
|
||||
[SDL_SCANCODE_MENU] /* = USB 118 */ = KEY_MENU,
|
||||
[SDL_SCANCODE_STOP] /* = USB 120 */ = KEY_CANCEL,
|
||||
[SDL_SCANCODE_AGAIN] /* = USB 121 */ = KEY_AGAIN,
|
||||
[SDL_SCANCODE_UNDO] /* = USB 122 */ = KEY_UNDO,
|
||||
[SDL_SCANCODE_CUT] /* = USB 123 */ = KEY_CUT,
|
||||
[SDL_SCANCODE_COPY] /* = USB 124 */ = KEY_COPY,
|
||||
[SDL_SCANCODE_PASTE] /* = USB 125 */ = KEY_PASTE,
|
||||
[SDL_SCANCODE_FIND] /* = USB 126 */ = KEY_FIND,
|
||||
[SDL_SCANCODE_MUTE] /* = USB 127 */ = KEY_MUTE,
|
||||
[SDL_SCANCODE_VOLUMEUP] /* = USB 128 */ = KEY_VOLUMEUP,
|
||||
[SDL_SCANCODE_VOLUMEDOWN] /* = USB 129 */ = KEY_VOLUMEDOWN,
|
||||
[SDL_SCANCODE_KP_COMMA] /* = USB 133 */ = KEY_KPCOMMA,
|
||||
[SDL_SCANCODE_INTERNATIONAL1] /* = USB 135 */ = KEY_RO,
|
||||
[SDL_SCANCODE_INTERNATIONAL2] /* = USB 136 */ = KEY_KATAKANAHIRAGANA,
|
||||
[SDL_SCANCODE_INTERNATIONAL3] /* = USB 137 */ = KEY_YEN,
|
||||
[SDL_SCANCODE_INTERNATIONAL4] /* = USB 138 */ = KEY_HENKAN,
|
||||
[SDL_SCANCODE_INTERNATIONAL5] /* = USB 139 */ = KEY_MUHENKAN,
|
||||
[SDL_SCANCODE_LANG1] /* = USB 144 */ = KEY_HANGEUL,
|
||||
[SDL_SCANCODE_LANG2] /* = USB 145 */ = KEY_HANJA,
|
||||
[SDL_SCANCODE_SYSREQ] /* = USB 154 */ = KEY_RIGHTSHIFT,
|
||||
[SDL_SCANCODE_CANCEL] /* = USB 155 */ = KEY_STOP,
|
||||
[SDL_SCANCODE_KP_LEFTPAREN] /* = USB 182 */ = KEY_KPLEFTPAREN,
|
||||
[SDL_SCANCODE_KP_RIGHTPAREN] /* = USB 183 */ = KEY_KPRIGHTPAREN,
|
||||
[SDL_SCANCODE_KP_PLUSMINUS] /* = USB 215 */ = KEY_KPPLUSMINUS,
|
||||
[SDL_SCANCODE_LCTRL] /* = USB 224 */ = KEY_LEFTCTRL,
|
||||
[SDL_SCANCODE_LSHIFT] /* = USB 225 */ = KEY_LEFTSHIFT,
|
||||
[SDL_SCANCODE_LALT] /* = USB 226 */ = KEY_LEFTALT,
|
||||
[SDL_SCANCODE_LGUI] /* = USB 227 */ = KEY_LEFTMETA,
|
||||
[SDL_SCANCODE_RCTRL] /* = USB 228 */ = KEY_RIGHTCTRL,
|
||||
[SDL_SCANCODE_RSHIFT] /* = USB 229 */ = KEY_RIGHTSHIFT,
|
||||
[SDL_SCANCODE_RALT] /* = USB 230 */ = KEY_RIGHTALT,
|
||||
[SDL_SCANCODE_RGUI] /* = USB 231 */ = KEY_RIGHTMETA,
|
||||
[SDL_SCANCODE_MODE] /* = USB 257 */ = KEY_ZENKAKUHANKAKU,
|
||||
[SDL_SCANCODE_AUDIONEXT] /* = USB 258 */ = KEY_NEXTSONG,
|
||||
[SDL_SCANCODE_AUDIOPREV] /* = USB 259 */ = KEY_PREVIOUSSONG,
|
||||
[SDL_SCANCODE_AUDIOSTOP] /* = USB 260 */ = KEY_STOPCD,
|
||||
[SDL_SCANCODE_AUDIOPLAY] /* = USB 261 */ = KEY_PLAYPAUSE,
|
||||
[SDL_SCANCODE_MEDIASELECT] /* = USB 263 */ = KEY_MEDIA,
|
||||
[SDL_SCANCODE_WWW] /* = USB 264 */ = KEY_WWW,
|
||||
[SDL_SCANCODE_MAIL] /* = USB 265 */ = KEY_MAIL,
|
||||
[SDL_SCANCODE_CALCULATOR] /* = USB 266 */ = KEY_CALC,
|
||||
[SDL_SCANCODE_COMPUTER] /* = USB 267 */ = KEY_COMPUTER,
|
||||
[SDL_SCANCODE_AC_SEARCH] /* = USB 268 */ = KEY_SEARCH,
|
||||
[SDL_SCANCODE_AC_HOME] /* = USB 269 */ = KEY_HOMEPAGE,
|
||||
[SDL_SCANCODE_AC_BACK] /* = USB 270 */ = KEY_BACK,
|
||||
[SDL_SCANCODE_AC_FORWARD] /* = USB 271 */ = KEY_FORWARD,
|
||||
[SDL_SCANCODE_AC_REFRESH] /* = USB 273 */ = KEY_REFRESH,
|
||||
[SDL_SCANCODE_AC_BOOKMARKS] /* = USB 274 */ = KEY_BOOKMARKS,
|
||||
[SDL_SCANCODE_BRIGHTNESSDOWN] /* = USB 275 */ = KEY_BRIGHTNESSDOWN,
|
||||
[SDL_SCANCODE_BRIGHTNESSUP] /* = USB 276 */ = KEY_BRIGHTNESSUP,
|
||||
[SDL_SCANCODE_DISPLAYSWITCH] /* = USB 277 */ = KEY_SWITCHVIDEOMODE,
|
||||
[SDL_SCANCODE_KBDILLUMTOGGLE] /* = USB 278 */ = KEY_KBDILLUMTOGGLE,
|
||||
[SDL_SCANCODE_KBDILLUMDOWN] /* = USB 279 */ = KEY_KBDILLUMDOWN,
|
||||
[SDL_SCANCODE_KBDILLUMUP] /* = USB 280 */ = KEY_KBDILLUMUP,
|
||||
[SDL_SCANCODE_EJECT] /* = USB 281 */ = KEY_EJECTCD,
|
||||
[SDL_SCANCODE_SLEEP] /* = USB 282 */ = KEY_SLEEP,
|
||||
[SDL_SCANCODE_APP1] /* = USB 283 */ = KEY_PROG1,
|
||||
[SDL_SCANCODE_APP2] /* = USB 284 */ = KEY_PROG2,
|
||||
[SDL_SCANCODE_AUDIOREWIND] /* = USB 285 */ = KEY_REWIND,
|
||||
[SDL_SCANCODE_AUDIOFASTFORWARD] /* = USB 286 */ = KEY_FASTFORWARD,
|
||||
};
|
||||
551
client/displayservers/SDL/sdl.c
Normal file
551
client/displayservers/SDL/sdl.c
Normal file
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "kb.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/types.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
struct SDLDSState
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Cursor * cursor;
|
||||
|
||||
EGLNativeWindowType wlDisplay;
|
||||
|
||||
bool keyboardGrabbed;
|
||||
bool pointerGrabbed;
|
||||
bool exiting;
|
||||
};
|
||||
|
||||
static struct SDLDSState sdl;
|
||||
|
||||
/* forwards */
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event);
|
||||
|
||||
static void sdlSetup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool sdlProbe(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlEarlyInit(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&sdl, 0, sizeof(sdl));
|
||||
|
||||
// Allow screensavers for now: we will enable and disable as needed.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
DEBUG_ERROR("SDL_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl.window = SDL_CreateWindow(
|
||||
params.title,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
||||
params.w,
|
||||
params.h,
|
||||
(
|
||||
SDL_WINDOW_HIDDEN |
|
||||
(params.resizable ? SDL_WINDOW_RESIZABLE : 0) |
|
||||
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
||||
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
||||
(params.opengl ? SDL_WINDOW_OPENGL : 0)
|
||||
)
|
||||
);
|
||||
|
||||
if (sdl.window == NULL)
|
||||
{
|
||||
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
||||
goto fail_init;
|
||||
}
|
||||
|
||||
const uint8_t data[4] = {0xf, 0x9, 0x9, 0xf};
|
||||
const uint8_t mask[4] = {0xf, 0xf, 0xf, 0xf};
|
||||
sdl.cursor = SDL_CreateCursor(data, mask, 8, 4, 4, 0);
|
||||
SDL_SetCursor(sdl.cursor);
|
||||
|
||||
SDL_ShowWindow(sdl.window);
|
||||
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
||||
params.minimizeOnFocusLoss ? "1" : "0");
|
||||
|
||||
if (params.fullscreen)
|
||||
SDL_SetWindowFullscreen(sdl.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
|
||||
if (!params.center)
|
||||
SDL_SetWindowPosition(sdl.window, params.x, params.y);
|
||||
|
||||
// ensure mouse acceleration is identical in server mode
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
||||
|
||||
SDL_SetEventFilter(sdlEventFilter, NULL);
|
||||
return true;
|
||||
|
||||
fail_init:
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlStartup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sdlShutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sdlFree(void)
|
||||
{
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
|
||||
if (sdl.cursor)
|
||||
SDL_FreeCursor(sdl.cursor);
|
||||
|
||||
if (sdl.window)
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static bool sdlGetProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
static EGLDisplay sdlGetEGLDisplay(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType native;
|
||||
EGLenum platform;
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
platform = EGL_PLATFORM_X11_KHR;
|
||||
break;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
platform = EGL_PLATFORM_WAYLAND_KHR;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (strstr(early_exts, "EGL_KHR_platform_base") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(platform, native, NULL);
|
||||
}
|
||||
|
||||
if (strstr(early_exts, "EGL_EXT_platform_base") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(platform, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
static EGLNativeWindowType sdlGetEGLNativeWindow(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
return (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
if (sdl.wlDisplay)
|
||||
return sdl.wlDisplay;
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(sdl.window, &width, &height);
|
||||
sdl.wlDisplay = (EGLNativeWindowType)wl_egl_window_create(
|
||||
wminfo.info.wl.surface, width, height);
|
||||
|
||||
return sdl.wlDisplay;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface)
|
||||
{
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
#endif //ENABLE_EGL
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
static LG_DSGLContext sdlGLCreateContext(void)
|
||||
{
|
||||
return (LG_DSGLContext)SDL_GL_CreateContext(sdl.window);
|
||||
}
|
||||
|
||||
static void sdlGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_DeleteContext((SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_MakeCurrent(sdl.window, (SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLSetSwapInterval(int interval)
|
||||
{
|
||||
SDL_GL_SetSwapInterval(interval);
|
||||
}
|
||||
|
||||
static void sdlGLSwapBuffers(void)
|
||||
{
|
||||
SDL_GL_SwapWindow(sdl.window);
|
||||
}
|
||||
#endif //ENABLE_OPENGL
|
||||
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event)
|
||||
{
|
||||
switch(event->type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
app_handleCloseEvent();
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
// stop motion events during the warp out of the window
|
||||
if (sdl.exiting)
|
||||
break;
|
||||
|
||||
app_updateCursorPos(event->motion.x, event->motion.y);
|
||||
app_handleMouseRelative(event->motion.xrel, event->motion.yrel,
|
||||
event->motion.xrel, event->motion.yrel);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
{
|
||||
int button = event->button.button;
|
||||
if (button > 3)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonPress(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
{
|
||||
int button = event->button.button;
|
||||
if (button > 3)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonRelease(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
{
|
||||
int button = event->wheel.y > 0 ? 4 : 5;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
SDL_Scancode sc = event->key.keysym.scancode;
|
||||
app_handleKeyPress(sdl_to_xfree86[sc]);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
SDL_Scancode sc = event->key.keysym.scancode;
|
||||
app_handleKeyRelease(sdl_to_xfree86[sc]);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch(event->window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
app_handleEnterEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
sdl.exiting = false;
|
||||
app_handleEnterEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
app_handleFocusEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
app_handleFocusEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
{
|
||||
struct Border border;
|
||||
SDL_GetWindowBordersSize(
|
||||
sdl.window,
|
||||
&border.top,
|
||||
&border.left,
|
||||
&border.bottom,
|
||||
&border.right
|
||||
);
|
||||
app_handleResizeEvent(
|
||||
event->window.data1,
|
||||
event->window.data2,
|
||||
border);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
app_updateWindowPos(event->window.data1, event->window.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
app_handleCloseEvent();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdlShowPointer(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
|
||||
static void sdlGrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
sdl.pointerGrabbed = false;
|
||||
}
|
||||
|
||||
static void sdlGrabKeyboard(void)
|
||||
{
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
else
|
||||
{
|
||||
DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all");
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabKeyboard(void)
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = false;
|
||||
}
|
||||
|
||||
static void sdlWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (sdl.exiting)
|
||||
return;
|
||||
|
||||
sdl.exiting = exiting;
|
||||
|
||||
// if exiting turn off relative mode
|
||||
if (exiting)
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
|
||||
// issue the warp
|
||||
SDL_WarpMouseInWindow(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlRealignPointer(void)
|
||||
{
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
static bool sdlIsValidPointerPos(int x, int y)
|
||||
{
|
||||
const int displays = SDL_GetNumVideoDisplays();
|
||||
for(int i = 0; i < displays; ++i)
|
||||
{
|
||||
SDL_Rect r;
|
||||
SDL_GetDisplayBounds(i, &r);
|
||||
if ((x >= r.x && x < r.x + r.w) &&
|
||||
(y >= r.y && y < r.y + r.h))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlInhibitIdle(void)
|
||||
{
|
||||
SDL_DisableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlUninhibitIdle(void)
|
||||
{
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlWait(unsigned int time)
|
||||
{
|
||||
SDL_WaitEventTimeout(NULL, time);
|
||||
}
|
||||
|
||||
static void sdlSetWindowSize(int x, int y)
|
||||
{
|
||||
SDL_SetWindowSize(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlSetFullscreen(bool fs)
|
||||
{
|
||||
SDL_SetWindowFullscreen(sdl.window, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
}
|
||||
|
||||
static bool sdlGetFullscreen(void)
|
||||
{
|
||||
return (SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_SDL =
|
||||
{
|
||||
.setup = sdlSetup,
|
||||
.probe = sdlProbe,
|
||||
.earlyInit = sdlEarlyInit,
|
||||
.init = sdlInit,
|
||||
.startup = sdlStartup,
|
||||
.shutdown = sdlShutdown,
|
||||
.free = sdlFree,
|
||||
.getProp = sdlGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = sdlGetEGLDisplay,
|
||||
.getEGLNativeWindow = sdlGetEGLNativeWindow,
|
||||
.eglSwapBuffers = sdlEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = sdlGLCreateContext,
|
||||
.glDeleteContext = sdlGLDeleteContext,
|
||||
.glMakeCurrent = sdlGLMakeCurrent,
|
||||
.glSetSwapInterval = sdlGLSetSwapInterval,
|
||||
.glSwapBuffers = sdlGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
.showPointer = sdlShowPointer,
|
||||
.grabPointer = sdlGrabPointer,
|
||||
.ungrabPointer = sdlUngrabPointer,
|
||||
.grabKeyboard = sdlGrabKeyboard,
|
||||
.ungrabKeyboard = sdlUngrabKeyboard,
|
||||
.warpPointer = sdlWarpPointer,
|
||||
.realignPointer = sdlRealignPointer,
|
||||
.isValidPointerPos = sdlIsValidPointerPos,
|
||||
.inhibitIdle = sdlInhibitIdle,
|
||||
.uninhibitIdle = sdlUninhibitIdle,
|
||||
.wait = sdlWait,
|
||||
.setWindowSize = sdlSetWindowSize,
|
||||
.setFullscreen = sdlSetFullscreen,
|
||||
.getFullscreen = sdlGetFullscreen,
|
||||
|
||||
/* SDL does not have clipboard support */
|
||||
.cbInit = NULL,
|
||||
};
|
||||
76
client/displayservers/Wayland/CMakeLists.txt
Normal file
76
client/displayservers/Wayland/CMakeLists.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_Wayland LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED
|
||||
wayland-client
|
||||
)
|
||||
|
||||
#pkg_check_modules(DISPLAYSERVER_Wayland_OPT_PKGCONFIG
|
||||
#)
|
||||
|
||||
add_library(displayserver_Wayland STATIC
|
||||
clipboard.c
|
||||
cursor.c
|
||||
gl.c
|
||||
idle.c
|
||||
input.c
|
||||
poll.c
|
||||
state.c
|
||||
registry.c
|
||||
wayland.c
|
||||
window.c
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_Wayland
|
||||
${DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES}
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_Wayland
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS}
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
|
||||
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
|
||||
|
||||
macro(wayland_generate protocol_file output_file)
|
||||
add_custom_command(OUTPUT "${output_file}.h"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
|
||||
add_custom_command(OUTPUT "${output_file}.c"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
|
||||
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
|
||||
endmacro()
|
||||
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
|
||||
include_directories("${CMAKE_BINARY_DIR}/wayland")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
|
||||
|
||||
477
client/displayservers/Wayland/clipboard.c
Normal file
477
client/displayservers/Wayland/clipboard.c
Normal file
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
static const char * textMimetypes[] =
|
||||
{
|
||||
"text/plain",
|
||||
"text/plain;charset=utf-8",
|
||||
"TEXT",
|
||||
"STRING",
|
||||
"UTF8_STRING",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * pngMimetypes[] =
|
||||
{
|
||||
"image/png",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * bmpMimetypes[] =
|
||||
{
|
||||
"image/bmp",
|
||||
"image/x-bmp",
|
||||
"image/x-MS-bmp",
|
||||
"image/x-win-bitmap",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * tiffMimetypes[] =
|
||||
{
|
||||
"image/tiff",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * jpegMimetypes[] =
|
||||
{
|
||||
"image/jpeg",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT:
|
||||
return textMimetypes;
|
||||
case LG_CLIPBOARD_DATA_PNG:
|
||||
return pngMimetypes;
|
||||
case LG_CLIPBOARD_DATA_BMP:
|
||||
return bmpMimetypes;
|
||||
case LG_CLIPBOARD_DATA_TIFF:
|
||||
return tiffMimetypes;
|
||||
case LG_CLIPBOARD_DATA_JPEG:
|
||||
return jpegMimetypes;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard type");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsMimetype(const char ** mimetypes,
|
||||
const char * needle)
|
||||
{
|
||||
for (const char ** mimetype = mimetypes; *mimetype; mimetype++)
|
||||
if (!strcmp(needle, *mimetype))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mimetypeEndswith(const char * mimetype, const char * what)
|
||||
{
|
||||
size_t mimetypeLen = strlen(mimetype);
|
||||
size_t whatLen = strlen(what);
|
||||
|
||||
if (mimetypeLen < whatLen)
|
||||
return false;
|
||||
|
||||
return !strcmp(mimetype + mimetypeLen - whatLen, what);
|
||||
}
|
||||
|
||||
static bool isTextMimetype(const char * mimetype)
|
||||
{
|
||||
if (containsMimetype(textMimetypes, mimetype))
|
||||
return true;
|
||||
|
||||
char * text = "text/";
|
||||
if (!strncmp(mimetype, text, strlen(text)))
|
||||
return true;
|
||||
|
||||
if (mimetypeEndswith(mimetype, "script") ||
|
||||
mimetypeEndswith(mimetype, "xml") ||
|
||||
mimetypeEndswith(mimetype, "yaml"))
|
||||
return true;
|
||||
|
||||
if (strstr(mimetype, "json"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum LG_ClipboardData mimetypeToCbType(const char * mimetype)
|
||||
{
|
||||
if (isTextMimetype(mimetype))
|
||||
return LG_CLIPBOARD_DATA_TEXT;
|
||||
|
||||
if (containsMimetype(pngMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_PNG;
|
||||
|
||||
if (containsMimetype(bmpMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_BMP;
|
||||
|
||||
if (containsMimetype(tiffMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_TIFF;
|
||||
|
||||
if (containsMimetype(jpegMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_JPEG;
|
||||
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
|
||||
static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer,
|
||||
const char * mimetype)
|
||||
{
|
||||
enum LG_ClipboardData type = mimetypeToCbType(mimetype);
|
||||
// We almost never prefer text/html, as that's used to represent rich text.
|
||||
// Since we can't copy or paste rich text, we should instead prefer actual
|
||||
// images or plain text.
|
||||
if (type != LG_CLIPBOARD_DATA_NONE &&
|
||||
(wlCb.pendingType == LG_CLIPBOARD_DATA_NONE ||
|
||||
strstr(wlCb.pendingMimetype, "html")))
|
||||
{
|
||||
wlCb.pendingType = type;
|
||||
if (wlCb.pendingMimetype)
|
||||
free(wlCb.pendingMimetype);
|
||||
wlCb.pendingMimetype = strdup(mimetype);
|
||||
}
|
||||
|
||||
if (!strcmp(mimetype, wlCb.lgMimetype))
|
||||
wlCb.isSelfCopy = true;
|
||||
}
|
||||
|
||||
static void dataOfferHandleSourceActions(void * data,
|
||||
struct wl_data_offer * offer, uint32_t sourceActions)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void dataOfferHandleAction(void * data, struct wl_data_offer * offer,
|
||||
uint32_t dndAction)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_data_offer_listener dataOfferListener = {
|
||||
.offer = dataOfferHandleOffer,
|
||||
.source_actions = dataOfferHandleSourceActions,
|
||||
.action = dataOfferHandleAction,
|
||||
};
|
||||
|
||||
static void dataDeviceHandleDataOffer(void * data,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
wlCb.pendingType = LG_CLIPBOARD_DATA_NONE;
|
||||
wlCb.isSelfCopy = false;
|
||||
wl_data_offer_add_listener(offer, &dataOfferListener, NULL);
|
||||
}
|
||||
|
||||
static void clipboardReadCancel(struct ClipboardRead * data, bool freeBuf)
|
||||
{
|
||||
waylandEpollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
wl_data_offer_destroy(data->offer);
|
||||
if (freeBuf)
|
||||
free(data->buf);
|
||||
free(data);
|
||||
wlCb.currentRead = NULL;
|
||||
}
|
||||
|
||||
static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
{
|
||||
clipboardReadCancel(data, true);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead);
|
||||
if (result < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
|
||||
clipboardReadCancel(data, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
data->buf[data->numRead] = 0;
|
||||
wlCb.stashedType = data->type;
|
||||
wlCb.stashedSize = data->numRead;
|
||||
wlCb.stashedContents = data->buf;
|
||||
|
||||
clipboardReadCancel(data, false);
|
||||
app_clipboardNotify(wlCb.stashedType, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
data->numRead += result;
|
||||
if (data->numRead >= data->size)
|
||||
{
|
||||
data->size *= 2;
|
||||
void * nbuf = realloc(data->buf, data->size);
|
||||
if (!nbuf) {
|
||||
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
|
||||
clipboardReadCancel(data, true);
|
||||
return;
|
||||
}
|
||||
|
||||
data->buf = nbuf;
|
||||
}
|
||||
}
|
||||
|
||||
static void dataDeviceHandleSelection(void * opaque,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
if (wlCb.pendingType == LG_CLIPBOARD_DATA_NONE || wlCb.isSelfCopy || !offer)
|
||||
return;
|
||||
|
||||
if (wlCb.currentRead)
|
||||
clipboardReadCancel(wlCb.currentRead, true);
|
||||
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
|
||||
wl_data_offer_receive(offer, wlCb.pendingMimetype, fds[1]);
|
||||
close(fds[1]);
|
||||
free(wlCb.pendingMimetype);
|
||||
wlCb.pendingMimetype = NULL;
|
||||
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
if (wlCb.stashedContents)
|
||||
{
|
||||
free(wlCb.stashedContents);
|
||||
wlCb.stashedContents = NULL;
|
||||
}
|
||||
|
||||
struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to read clipboard");
|
||||
close(fds[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
data->fd = fds[0];
|
||||
data->size = 4096;
|
||||
data->numRead = 0;
|
||||
data->buf = malloc(data->size);
|
||||
data->offer = offer;
|
||||
data->type = wlCb.pendingType;
|
||||
|
||||
if (!data->buf)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to receive clipboard data");
|
||||
close(data->fd);
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!waylandEpollRegister(data->fd, clipboardReadCallback, data, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno));
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
}
|
||||
|
||||
wlCb.currentRead = data;
|
||||
}
|
||||
|
||||
static const struct wl_data_device_listener dataDeviceListener = {
|
||||
.data_offer = dataDeviceHandleDataOffer,
|
||||
.selection = dataDeviceHandleSelection,
|
||||
};
|
||||
|
||||
bool waylandCBInit(void)
|
||||
{
|
||||
memset(&wlCb, 0, sizeof(wlCb));
|
||||
|
||||
if (!wlWm.dataDeviceManager)
|
||||
{
|
||||
DEBUG_ERROR("Missing wl_data_device_manager interface");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlCb.dataDevice = wl_data_device_manager_get_data_device(
|
||||
wlWm.dataDeviceManager, wlWm.seat);
|
||||
if (!wlCb.dataDevice)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get data device");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlCb.stashedType = LG_CLIPBOARD_DATA_NONE;
|
||||
wl_data_device_add_listener(wlCb.dataDevice, &dataDeviceListener, NULL);
|
||||
|
||||
snprintf(wlCb.lgMimetype, sizeof(wlCb.lgMimetype),
|
||||
"application/x-looking-glass-copy;pid=%d", getpid());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandCBRequest(LG_ClipboardData type)
|
||||
{
|
||||
// We only notified once, so it must be this.
|
||||
assert(type == wlCb.stashedType);
|
||||
app_clipboardData(wlCb.stashedType, wlCb.stashedContents, wlCb.stashedSize);
|
||||
}
|
||||
|
||||
struct ClipboardWrite
|
||||
{
|
||||
int fd;
|
||||
size_t pos;
|
||||
struct CountedBuffer * buffer;
|
||||
};
|
||||
|
||||
static void clipboardWriteCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
goto error;
|
||||
|
||||
ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos);
|
||||
if (written < 0)
|
||||
{
|
||||
if (errno != EPIPE)
|
||||
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->pos += written;
|
||||
if (data->pos < data->buffer->size)
|
||||
return;
|
||||
|
||||
error:
|
||||
waylandEpollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
const char * mimetype, int fd)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
// Consider making this do non-blocking sends to not stall the Wayland
|
||||
// event loop if it becomes a problem. This is "fine" in the sense that
|
||||
// wl-copy also stalls like this, but it's not necessary.
|
||||
fcntl(fd, F_SETFL, 0);
|
||||
|
||||
struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite");
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->fd = fd;
|
||||
data->pos = 0;
|
||||
data->buffer = transfer->data;
|
||||
countedBufferAddRef(transfer->data);
|
||||
waylandEpollRegister(fd, clipboardWriteCallback, data, EPOLLOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void dataSourceHandleCancelled(void * data,
|
||||
struct wl_data_source * source)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
countedBufferRelease(&transfer->data);
|
||||
free(transfer);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener dataSourceListener = {
|
||||
.send = dataSourceHandleSend,
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
{
|
||||
struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer));
|
||||
if (!transfer)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating WCBTransfer");
|
||||
return;
|
||||
}
|
||||
|
||||
transfer->mimetypes = cbTypeToMimetypes(type);
|
||||
transfer->data = countedBufferNew(size);
|
||||
if (!transfer->data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating clipboard buffer");
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
memcpy(transfer->data->data, data, size);
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wlWm.dataDeviceManager);
|
||||
wl_data_source_add_listener(source, &dataSourceListener, transfer);
|
||||
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
|
||||
wl_data_source_offer(source, *mimetype);
|
||||
wl_data_source_offer(source, wlCb.lgMimetype);
|
||||
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
}
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
wlCb.haveRequest = true;
|
||||
wlCb.type = type;
|
||||
app_clipboardRequest(waylandCBReplyFn, NULL);
|
||||
}
|
||||
|
||||
void waylandCBRelease(void)
|
||||
{
|
||||
wlCb.haveRequest = false;
|
||||
}
|
||||
100
client/displayservers/Wayland/cursor.c
Normal file
100
client/displayservers/Wayland/cursor.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static const uint32_t cursorBitmap[] = {
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000,
|
||||
0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
};
|
||||
|
||||
static struct wl_buffer * createCursorBuffer(void)
|
||||
{
|
||||
int fd = memfd_create("lg-cursor", 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create cursor shared memory: %d", errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wl_buffer * result = NULL;
|
||||
|
||||
if (ftruncate(fd, sizeof cursorBitmap) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (shm_data == MAP_FAILED)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map memory for cursor: %d", errno);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, sizeof cursorBitmap);
|
||||
result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
memcpy(shm_data, cursorBitmap, sizeof cursorBitmap);
|
||||
munmap(shm_data, sizeof cursorBitmap);
|
||||
|
||||
fail:
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool waylandCursorInit(void)
|
||||
{
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_compositor, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct wl_buffer * cursorBuffer = createCursorBuffer();
|
||||
if (cursorBuffer)
|
||||
{
|
||||
wlWm.cursor = wl_compositor_create_surface(wlWm.compositor);
|
||||
wl_surface_attach(wlWm.cursor, cursorBuffer, 0, 0);
|
||||
wl_surface_commit(wlWm.cursor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandShowPointer(bool show)
|
||||
{
|
||||
wlWm.showPointer = show;
|
||||
wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, show ? wlWm.cursor : NULL, 0, 0);
|
||||
}
|
||||
171
client/displayservers/Wayland/gl.c
Normal file
171
client/displayservers/Wayland/gl.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
#include "egl_dynprocs.h"
|
||||
|
||||
bool waylandEGLInit(int w, int h)
|
||||
{
|
||||
wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h);
|
||||
if (!wlWm.eglWindow)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL window");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLDisplay waylandGetEGLDisplay(void)
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display;
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (strstr(early_exts, "EGL_KHR_platform_wayland") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL);
|
||||
}
|
||||
|
||||
if (strstr(early_exts, "EGL_EXT_platform_wayland") != NULL &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface)
|
||||
{
|
||||
eglSwapBuffers(display, surface);
|
||||
|
||||
if (wlWm.resizeSerial)
|
||||
{
|
||||
wl_egl_window_resize(wlWm.eglWindow, wlWm.width, wlWm.height, 0, 0);
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, 0, 0, wlWm.width, wlWm.height);
|
||||
wl_surface_set_opaque_region(wlWm.surface, region);
|
||||
wl_region_destroy(region);
|
||||
|
||||
app_handleResizeEvent(wlWm.width, wlWm.height, (struct Border) {0, 0, 0, 0});
|
||||
xdg_surface_ack_configure(wlWm.xdgSurface, wlWm.resizeSerial);
|
||||
wlWm.resizeSerial = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLNativeWindowType waylandGetEGLNativeWindow(void)
|
||||
{
|
||||
return (EGLNativeWindowType) wlWm.eglWindow;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
bool waylandOpenGLInit(void)
|
||||
{
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 24,
|
||||
EGL_CONFORMANT , EGL_OPENGL_BIT,
|
||||
EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT,
|
||||
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
|
||||
EGL_RED_SIZE , 8,
|
||||
EGL_GREEN_SIZE , 8,
|
||||
EGL_BLUE_SIZE , 8,
|
||||
EGL_SAMPLE_BUFFERS , 0,
|
||||
EGL_SAMPLES , 0,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
wlWm.glDisplay = waylandGetEGLDisplay();
|
||||
|
||||
int maj, min;
|
||||
if (!eglInitialize(wlWm.glDisplay, &maj, &min))
|
||||
{
|
||||
DEBUG_ERROR("Unable to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wlWm.glDisplay == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint num_config;
|
||||
if (!eglChooseConfig(wlWm.glDisplay, attr, &wlWm.glConfig, 1, &num_config))
|
||||
{
|
||||
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL);
|
||||
if (wlWm.glSurface == EGL_NO_SURFACE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LG_DSGLContext waylandGLCreateContext(void)
|
||||
{
|
||||
eglBindAPI(EGL_OPENGL_API);
|
||||
return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL);
|
||||
}
|
||||
|
||||
void waylandGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
eglDestroyContext(wlWm.glDisplay, context);
|
||||
}
|
||||
|
||||
void waylandGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context);
|
||||
}
|
||||
|
||||
void waylandGLSetSwapInterval(int interval)
|
||||
{
|
||||
eglSwapInterval(wlWm.glDisplay, interval);
|
||||
}
|
||||
|
||||
void waylandGLSwapBuffers(void)
|
||||
{
|
||||
waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface);
|
||||
}
|
||||
#endif
|
||||
59
client/displayservers/Wayland/idle.c
Normal file
59
client/displayservers/Wayland/idle.c
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
bool waylandIdleInit(void)
|
||||
{
|
||||
if (!wlWm.idleInhibitManager)
|
||||
DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will "
|
||||
"not be able to suppress idle states");
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandIdleFree(void)
|
||||
{
|
||||
if (wlWm.idleInhibitManager)
|
||||
{
|
||||
waylandUninhibitIdle();
|
||||
zwp_idle_inhibit_manager_v1_destroy(wlWm.idleInhibitManager);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandInhibitIdle(void)
|
||||
{
|
||||
if (wlWm.idleInhibitManager && !wlWm.idleInhibitor)
|
||||
wlWm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
|
||||
wlWm.idleInhibitManager, wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandUninhibitIdle(void)
|
||||
{
|
||||
if (wlWm.idleInhibitor)
|
||||
{
|
||||
zwp_idle_inhibitor_v1_destroy(wlWm.idleInhibitor);
|
||||
wlWm.idleInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
393
client/displayservers/Wayland/input.c
Normal file
393
client/displayservers/Wayland/input.c
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// Mouse-handling listeners.
|
||||
|
||||
static void pointerMotionHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW)
|
||||
{
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
wlWm.cursorY = wl_fixed_to_double(syW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
if (!wlWm.warpSupport && !wlWm.relativePointer)
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW,
|
||||
wl_fixed_t syW)
|
||||
{
|
||||
app_handleEnterEvent(true);
|
||||
|
||||
wl_pointer_set_cursor(pointer, serial, wlWm.showPointer ? wlWm.cursor : NULL, 0, 0);
|
||||
wlWm.pointerEnterSerial = serial;
|
||||
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
wlWm.cursorY = wl_fixed_to_double(syW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wlWm.relativePointer)
|
||||
return;
|
||||
|
||||
app_resyncMouseBasic();
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
app_handleEnterEvent(false);
|
||||
}
|
||||
|
||||
static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, uint32_t axis, wl_fixed_t value)
|
||||
{
|
||||
int button = value > 0 ?
|
||||
5 /* SPICE_MOUSE_BUTTON_DOWN */ :
|
||||
4 /* SPICE_MOUSE_BUTTON_UP */;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static int mapWaylandToSpiceButton(uint32_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case BTN_LEFT:
|
||||
return 1; // SPICE_MOUSE_BUTTON_LEFT
|
||||
case BTN_MIDDLE:
|
||||
return 2; // SPICE_MOUSE_BUTTON_MIDDLE
|
||||
case BTN_RIGHT:
|
||||
return 3; // SPICE_MOUSE_BUTTON_RIGHT
|
||||
case BTN_SIDE:
|
||||
return 6; // SPICE_MOUSE_BUTTON_SIDE
|
||||
case BTN_EXTRA:
|
||||
return 7; // SPICE_MOUSE_BUTTON_EXTRA
|
||||
}
|
||||
|
||||
return 0; // SPICE_MOUSE_BUTTON_INVALID
|
||||
}
|
||||
|
||||
static void pointerButtonHandler(void *data, struct wl_pointer *pointer,
|
||||
uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW)
|
||||
{
|
||||
button = mapWaylandToSpiceButton(button);
|
||||
|
||||
if (stateW == WL_POINTER_BUTTON_STATE_PRESSED)
|
||||
app_handleButtonPress(button);
|
||||
else
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
.enter = pointerEnterHandler,
|
||||
.leave = pointerLeaveHandler,
|
||||
.motion = pointerMotionHandler,
|
||||
.button = pointerButtonHandler,
|
||||
.axis = pointerAxisHandler,
|
||||
};
|
||||
|
||||
static void relativePointerMotionHandler(void * data,
|
||||
struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo,
|
||||
wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW,
|
||||
wl_fixed_t dyUnaccelW)
|
||||
{
|
||||
wlWm.cursorX += wl_fixed_to_double(dxW);
|
||||
wlWm.cursorY += wl_fixed_to_double(dyW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
app_handleMouseRelative(
|
||||
wl_fixed_to_double(dxW),
|
||||
wl_fixed_to_double(dyW),
|
||||
wl_fixed_to_double(dxUnaccelW),
|
||||
wl_fixed_to_double(dyUnaccelW));
|
||||
}
|
||||
|
||||
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
|
||||
.relative_motion = relativePointerMotionHandler,
|
||||
};
|
||||
|
||||
// Keyboard-handling listeners.
|
||||
|
||||
static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t format, int fd, uint32_t size)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
|
||||
{
|
||||
app_handleFocusEvent(true);
|
||||
wlWm.keyboardEnterSerial = serial;
|
||||
|
||||
uint32_t * key;
|
||||
wl_array_for_each(key, keys)
|
||||
app_handleKeyPress(*key);
|
||||
}
|
||||
|
||||
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
app_handleFocusEvent(false);
|
||||
}
|
||||
|
||||
static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
|
||||
{
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
app_handleKeyPress(key);
|
||||
else
|
||||
app_handleKeyRelease(key);
|
||||
}
|
||||
|
||||
static void keyboardModifiersHandler(void * data,
|
||||
struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
|
||||
uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
.keymap = keyboardKeymapHandler,
|
||||
.enter = keyboardEnterHandler,
|
||||
.leave = keyboardLeaveHandler,
|
||||
.key = keyboardKeyHandler,
|
||||
.modifiers = keyboardModifiersHandler,
|
||||
};
|
||||
|
||||
// Seat-handling listeners.
|
||||
|
||||
static void handlePointerCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
|
||||
if (!hasPointer && wlWm.pointer)
|
||||
{
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wlWm.pointer = NULL;
|
||||
}
|
||||
else if (hasPointer && !wlWm.pointer)
|
||||
{
|
||||
wlWm.pointer = wl_seat_get_pointer(wlWm.seat);
|
||||
wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleKeyboardCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
|
||||
if (!hasKeyboard && wlWm.keyboard)
|
||||
{
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
wlWm.keyboard = NULL;
|
||||
}
|
||||
else if (hasKeyboard && !wlWm.keyboard)
|
||||
{
|
||||
wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat);
|
||||
wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void seatCapabilitiesHandler(void * data, struct wl_seat * seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
wlWm.capabilities = capabilities;
|
||||
handlePointerCapability(capabilities);
|
||||
handleKeyboardCapability(capabilities);
|
||||
}
|
||||
|
||||
static void seatNameHandler(void * data, struct wl_seat * seat,
|
||||
const char * name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener seatListener = {
|
||||
.capabilities = seatCapabilitiesHandler,
|
||||
.name = seatNameHandler,
|
||||
};
|
||||
|
||||
bool waylandInputInit(void)
|
||||
{
|
||||
if (!wlWm.seat)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_seat, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wlWm.warpSupport && (!wlWm.relativePointerManager || !wlWm.pointerConstraints))
|
||||
{
|
||||
DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack "
|
||||
"of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1");
|
||||
wlWm.warpSupport = false;
|
||||
}
|
||||
|
||||
if (!wlWm.relativePointerManager)
|
||||
DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, "
|
||||
"mouse will not be captured");
|
||||
|
||||
if (!wlWm.pointerConstraints)
|
||||
DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse "
|
||||
"will not be captured");
|
||||
|
||||
if (!wlWm.keyboardInhibitManager)
|
||||
DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
|
||||
"compositor, keyboard will not be grabbed");
|
||||
|
||||
wl_seat_add_listener(wlWm.seat, &seatListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandInputFree(void)
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
wl_seat_destroy(wlWm.seat);
|
||||
}
|
||||
|
||||
void waylandGrabPointer(void)
|
||||
{
|
||||
if (!wlWm.relativePointerManager || !wlWm.pointerConstraints)
|
||||
return;
|
||||
|
||||
if (!wlWm.warpSupport && !wlWm.relativePointer)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
if (!wlWm.confinedPointer)
|
||||
{
|
||||
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandUngrabPointer(void)
|
||||
{
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
|
||||
if (!wlWm.warpSupport)
|
||||
{
|
||||
if (!wlWm.relativePointer)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
app_resyncMouseBasic();
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
}
|
||||
|
||||
void waylandGrabKeyboard(void)
|
||||
{
|
||||
if (wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor)
|
||||
{
|
||||
wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||
wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandUngrabKeyboard(void)
|
||||
{
|
||||
if (wlWm.keyboardInhibitor)
|
||||
{
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor);
|
||||
wlWm.keyboardInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (x < 0) x = 0;
|
||||
else if (x >= wlWm.width) x = wlWm.width - 1;
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= wlWm.height) y = wlWm.height - 1;
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, x, y, 1, 1);
|
||||
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region);
|
||||
wl_surface_commit(wlWm.surface);
|
||||
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct zwp_confined_pointer_v1 * confine;
|
||||
confine = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
wl_surface_commit(wlWm.surface);
|
||||
zwp_confined_pointer_v1_destroy(confine);
|
||||
}
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
|
||||
void waylandRealignPointer(void)
|
||||
{
|
||||
if (!wlWm.warpSupport)
|
||||
app_resyncMouseBasic();
|
||||
}
|
||||
177
client/displayservers/Wayland/poll.c
Normal file
177
client/displayservers/Wayland/poll.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait
|
||||
|
||||
static void waylandDisplayCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
if (events & EPOLLERR)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
else
|
||||
wl_display_read_events(wlWm.display);
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
}
|
||||
|
||||
bool waylandPollInit(void)
|
||||
{
|
||||
wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (wlWm.epollFd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_list_init(&wlWm.poll);
|
||||
wl_list_init(&wlWm.pollFree);
|
||||
LG_LOCK_INIT(wlWm.pollLock);
|
||||
LG_LOCK_INIT(wlWm.pollFreeLock);
|
||||
|
||||
wlWm.displayFd = wl_display_get_fd(wlWm.display);
|
||||
if (!waylandEpollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWait(unsigned int time)
|
||||
{
|
||||
while (wl_display_prepare_read(wlWm.display))
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
wl_display_flush(wlWm.display);
|
||||
|
||||
struct epoll_event events[EPOLL_EVENTS];
|
||||
int count;
|
||||
if ((count = epoll_wait(wlWm.epollFd, events, EPOLL_EVENTS, time)) < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
DEBUG_INFO("epoll failed: %s", strerror(errno));
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
return;
|
||||
}
|
||||
|
||||
bool sawDisplay = false;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
struct WaylandPoll * poll = events[i].data.ptr;
|
||||
if (!poll->removed)
|
||||
poll->callback(events[i].events, poll->opaque);
|
||||
if (poll->fd == wlWm.displayFd)
|
||||
sawDisplay = true;
|
||||
}
|
||||
|
||||
if (!sawDisplay)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
struct WaylandPoll * node;
|
||||
struct WaylandPoll * temp;
|
||||
wl_list_for_each_safe(node, temp, &wlWm.pollFree, link)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void waylandEpollRemoveNode(struct WaylandPoll * node)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
});
|
||||
}
|
||||
|
||||
bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events)
|
||||
{
|
||||
struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll));
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
node->fd = fd;
|
||||
node->removed = false;
|
||||
node->callback = callback;
|
||||
node->opaque = opaque;
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.poll, &node->link);
|
||||
});
|
||||
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) {
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
}) < 0)
|
||||
{
|
||||
waylandEpollRemoveNode(node);
|
||||
free(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waylandEpollUnregister(int fd)
|
||||
{
|
||||
struct WaylandPoll * node = NULL;
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_for_each(node, &wlWm.poll, link)
|
||||
{
|
||||
if (node->fd == fd)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (!node)
|
||||
{
|
||||
DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
node->removed = true;
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
waylandEpollRemoveNode(node);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.pollFree, &node->link);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
89
client/displayservers/Wayland/registry.c
Normal file
89
client/displayservers/Wayland/registry.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
uint32_t name, const char * interface, uint32_t version)
|
||||
{
|
||||
if (!strcmp(interface, wl_seat_interface.name) && !wlWm.seat)
|
||||
wlWm.seat = wl_registry_bind(wlWm.registry, name, &wl_seat_interface, 1);
|
||||
else if (!strcmp(interface, wl_shm_interface.name))
|
||||
wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1);
|
||||
else if (!strcmp(interface, wl_compositor_interface.name))
|
||||
wlWm.compositor = wl_registry_bind(wlWm.registry, name, &wl_compositor_interface, 4);
|
||||
else if (!strcmp(interface, xdg_wm_base_interface.name))
|
||||
wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1);
|
||||
else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
|
||||
wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
|
||||
wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_relative_pointer_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name))
|
||||
wlWm.pointerConstraints = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_pointer_constraints_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name))
|
||||
wlWm.keyboardInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, wl_data_device_manager_interface.name))
|
||||
wlWm.dataDeviceManager = wl_registry_bind(wlWm.registry, name,
|
||||
&wl_data_device_manager_interface, 3);
|
||||
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
|
||||
wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_idle_inhibit_manager_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registryGlobalRemoveHandler(void * data,
|
||||
struct wl_registry * registry, uint32_t name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registryListener = {
|
||||
.global = registryGlobalHandler,
|
||||
.global_remove = registryGlobalRemoveHandler,
|
||||
};
|
||||
|
||||
bool waylandRegistryInit(void)
|
||||
{
|
||||
wlWm.registry = wl_display_get_registry(wlWm.display);
|
||||
if (!wlWm.registry)
|
||||
{
|
||||
DEBUG_ERROR("Unable to find wl_registry");
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_registry_add_listener(wlWm.registry, ®istryListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandRegistryFree(void)
|
||||
{
|
||||
wl_registry_destroy(wlWm.registry);
|
||||
}
|
||||
24
client/displayservers/Wayland/state.c
Normal file
24
client/displayservers/Wayland/state.c
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
struct WaylandDSState wlWm;
|
||||
struct WCBState wlCb;
|
||||
201
client/displayservers/Wayland/wayland.c
Normal file
201
client/displayservers/Wayland/wayland.c
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/input.h>
|
||||
#include <poll.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
# include <wayland-egl.h>
|
||||
# include "egl_dynprocs.h"
|
||||
# include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "common/option.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
static struct Option waylandOptions[] =
|
||||
{
|
||||
{
|
||||
.module = "wayland",
|
||||
.name = "warpSupport",
|
||||
.description = "Enable cursor warping",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
static bool waylandEarlyInit(void)
|
||||
{
|
||||
// Request to receive EPIPE instead of SIGPIPE when one end of a pipe
|
||||
// disconnects while a write is pending. This is useful to the Wayland
|
||||
// clipboard backend, where an arbitrary application is on the other end of
|
||||
// that pipe.
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void waylandSetup(void)
|
||||
{
|
||||
option_register(waylandOptions);
|
||||
}
|
||||
|
||||
static bool waylandProbe(void)
|
||||
{
|
||||
return getenv("WAYLAND_DISPLAY") != NULL;
|
||||
}
|
||||
|
||||
static bool waylandInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&wlWm, 0, sizeof(wlWm));
|
||||
|
||||
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
|
||||
|
||||
wlWm.display = wl_display_connect(NULL);
|
||||
|
||||
if (!waylandPollInit())
|
||||
return false;
|
||||
|
||||
if (!waylandRegistryInit())
|
||||
return false;
|
||||
|
||||
if (!waylandIdleInit())
|
||||
return false;
|
||||
|
||||
if (!waylandInputInit())
|
||||
return false;
|
||||
|
||||
if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless))
|
||||
return false;
|
||||
|
||||
if (!waylandEGLInit(params.w, params.h))
|
||||
return false;
|
||||
|
||||
if (!waylandCursorInit())
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl && !waylandOpenGLInit())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
wlWm.width = params.w;
|
||||
wlWm.height = params.h;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void waylandStartup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void waylandShutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void waylandFree(void)
|
||||
{
|
||||
waylandIdleFree();
|
||||
waylandWindowFree();
|
||||
waylandInputFree();
|
||||
waylandRegistryFree();
|
||||
wl_display_disconnect(wlWm.display);
|
||||
}
|
||||
|
||||
static bool waylandGetProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
if (prop == LG_DS_WARP_SUPPORT)
|
||||
{
|
||||
*(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_Wayland =
|
||||
{
|
||||
.setup = waylandSetup,
|
||||
.probe = waylandProbe,
|
||||
.earlyInit = waylandEarlyInit,
|
||||
.init = waylandInit,
|
||||
.startup = waylandStartup,
|
||||
.shutdown = waylandShutdown,
|
||||
.free = waylandFree,
|
||||
.getProp = waylandGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = waylandGetEGLDisplay,
|
||||
.getEGLNativeWindow = waylandGetEGLNativeWindow,
|
||||
.eglSwapBuffers = waylandEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = waylandGLCreateContext,
|
||||
.glDeleteContext = waylandGLDeleteContext,
|
||||
.glMakeCurrent = waylandGLMakeCurrent,
|
||||
.glSetSwapInterval = waylandGLSetSwapInterval,
|
||||
.glSwapBuffers = waylandGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
.showPointer = waylandShowPointer,
|
||||
.grabPointer = waylandGrabPointer,
|
||||
.ungrabPointer = waylandUngrabPointer,
|
||||
.grabKeyboard = waylandGrabKeyboard,
|
||||
.ungrabKeyboard = waylandUngrabKeyboard,
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.isValidPointerPos = waylandIsValidPointerPos,
|
||||
.inhibitIdle = waylandInhibitIdle,
|
||||
.uninhibitIdle = waylandUninhibitIdle,
|
||||
.wait = waylandWait,
|
||||
.setWindowSize = waylandSetWindowSize,
|
||||
.setFullscreen = waylandSetFullscreen,
|
||||
.getFullscreen = waylandGetFullscreen,
|
||||
|
||||
.cbInit = waylandCBInit,
|
||||
.cbNotice = waylandCBNotice,
|
||||
.cbRelease = waylandCBRelease,
|
||||
.cbRequest = waylandCBRequest
|
||||
};
|
||||
219
client/displayservers/Wayland/wayland.h
Normal file
219
client/displayservers/Wayland/wayland.h
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
# include <wayland-egl.h>
|
||||
# include <EGL/egl.h>
|
||||
# include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
|
||||
|
||||
struct WaylandPoll
|
||||
{
|
||||
int fd;
|
||||
bool removed;
|
||||
WaylandPollCallback callback;
|
||||
void * opaque;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
struct WaylandDSState
|
||||
{
|
||||
bool pointerGrabbed;
|
||||
bool keyboardGrabbed;
|
||||
|
||||
struct wl_display * display;
|
||||
struct wl_surface * surface;
|
||||
struct wl_registry * registry;
|
||||
struct wl_seat * seat;
|
||||
struct wl_shm * shm;
|
||||
struct wl_compositor * compositor;
|
||||
|
||||
int32_t width, height;
|
||||
bool fullscreen;
|
||||
uint32_t resizeSerial;
|
||||
bool configured;
|
||||
bool warpSupport;
|
||||
double cursorX, cursorY;
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
struct wl_egl_window * eglWindow;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
EGLDisplay glDisplay;
|
||||
EGLConfig glConfig;
|
||||
EGLSurface glSurface;
|
||||
#endif
|
||||
|
||||
struct xdg_wm_base * xdgWmBase;
|
||||
struct xdg_surface * xdgSurface;
|
||||
struct xdg_toplevel * xdgToplevel;
|
||||
struct zxdg_decoration_manager_v1 * xdgDecorationManager;
|
||||
struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration;
|
||||
|
||||
struct wl_surface * cursor;
|
||||
|
||||
struct wl_data_device_manager * dataDeviceManager;
|
||||
|
||||
uint32_t capabilities;
|
||||
|
||||
struct wl_keyboard * keyboard;
|
||||
struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager;
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor;
|
||||
uint32_t keyboardEnterSerial;
|
||||
|
||||
struct wl_pointer * pointer;
|
||||
struct zwp_relative_pointer_manager_v1 * relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1 * pointerConstraints;
|
||||
struct zwp_relative_pointer_v1 * relativePointer;
|
||||
struct zwp_confined_pointer_v1 * confinedPointer;
|
||||
bool showPointer;
|
||||
uint32_t pointerEnterSerial;
|
||||
|
||||
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
|
||||
struct zwp_idle_inhibitor_v1 * idleInhibitor;
|
||||
|
||||
struct wl_list poll; // WaylandPoll::link
|
||||
struct wl_list pollFree; // WaylandPoll::link
|
||||
LG_Lock pollLock;
|
||||
LG_Lock pollFreeLock;
|
||||
int epollFd;
|
||||
int displayFd;
|
||||
};
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
struct CountedBuffer * data;
|
||||
const char ** mimetypes;
|
||||
};
|
||||
|
||||
struct ClipboardRead
|
||||
{
|
||||
int fd;
|
||||
size_t size;
|
||||
size_t numRead;
|
||||
uint8_t * buf;
|
||||
enum LG_ClipboardData type;
|
||||
struct wl_data_offer * offer;
|
||||
};
|
||||
|
||||
struct WCBState
|
||||
{
|
||||
struct wl_data_device * dataDevice;
|
||||
char lgMimetype[64];
|
||||
|
||||
enum LG_ClipboardData pendingType;
|
||||
char * pendingMimetype;
|
||||
bool isSelfCopy;
|
||||
|
||||
enum LG_ClipboardData stashedType;
|
||||
uint8_t * stashedContents;
|
||||
ssize_t stashedSize;
|
||||
|
||||
bool haveRequest;
|
||||
LG_ClipboardData type;
|
||||
|
||||
struct ClipboardRead * currentRead;
|
||||
};
|
||||
|
||||
extern struct WaylandDSState wlWm;
|
||||
extern struct WCBState wlCb;
|
||||
|
||||
// clipboard module
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBRequest(LG_ClipboardData type);
|
||||
void waylandCBNotice(LG_ClipboardData type);
|
||||
void waylandCBRelease(void);
|
||||
|
||||
// cursor module
|
||||
bool waylandCursorInit(void);
|
||||
void waylandShowPointer(bool show);
|
||||
|
||||
// gl module
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
bool waylandEGLInit(int w, int h);
|
||||
EGLDisplay waylandGetEGLDisplay(void);
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLNativeWindowType waylandGetEGLNativeWindow(void);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
bool waylandOpenGLInit(void);
|
||||
LG_DSGLContext waylandGLCreateContext(void);
|
||||
void waylandGLDeleteContext(LG_DSGLContext context);
|
||||
void waylandGLMakeCurrent(LG_DSGLContext context);
|
||||
void waylandGLSetSwapInterval(int interval);
|
||||
void waylandGLSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
// idle module
|
||||
bool waylandIdleInit(void);
|
||||
void waylandIdleFree(void);
|
||||
void waylandInhibitIdle(void);
|
||||
void waylandUninhibitIdle(void);
|
||||
|
||||
// input module
|
||||
bool waylandInputInit(void);
|
||||
void waylandInputFree(void);
|
||||
void waylandGrabKeyboard(void);
|
||||
void waylandGrabPointer(void);
|
||||
void waylandUngrabKeyboard(void);
|
||||
void waylandUngrabPointer(void);
|
||||
void waylandRealignPointer(void);
|
||||
void waylandWarpPointer(int x, int y, bool exiting);
|
||||
|
||||
// poll module
|
||||
bool waylandPollInit(void);
|
||||
void waylandWait(unsigned int time);
|
||||
bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events);
|
||||
bool waylandEpollUnregister(int fd);
|
||||
|
||||
// registry module
|
||||
bool waylandRegistryInit(void);
|
||||
void waylandRegistryFree(void);
|
||||
|
||||
// window module
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless);
|
||||
void waylandWindowFree(void);
|
||||
void waylandSetWindowSize(int x, int y);
|
||||
void waylandSetFullscreen(bool fs);
|
||||
bool waylandGetFullscreen(void);
|
||||
bool waylandIsValidPointerPos(int x, int y);
|
||||
171
client/displayservers/Wayland/window.c
Normal file
171
client/displayservers/Wayland/window.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com)
|
||||
Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca)
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// XDG WM base listeners.
|
||||
|
||||
static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial)
|
||||
{
|
||||
xdg_wm_base_pong(xdgWmBase, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_wm_base_listener xdgWmBaseListener = {
|
||||
.ping = xdgWmBasePing,
|
||||
};
|
||||
|
||||
// Surface-handling listeners.
|
||||
|
||||
static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface,
|
||||
uint32_t serial)
|
||||
{
|
||||
if (wlWm.configured)
|
||||
wlWm.resizeSerial = serial;
|
||||
else
|
||||
{
|
||||
xdg_surface_ack_configure(xdgSurface, serial);
|
||||
wlWm.configured = true;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
.configure = xdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
// XDG Surface listeners.
|
||||
|
||||
static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel,
|
||||
int32_t width, int32_t height, struct wl_array * states)
|
||||
{
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
wlWm.fullscreen = false;
|
||||
|
||||
enum xdg_toplevel_state * state;
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN)
|
||||
wlWm.fullscreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel)
|
||||
{
|
||||
app_handleCloseEvent();
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener xdgToplevelListener = {
|
||||
.configure = xdgToplevelConfigure,
|
||||
.close = xdgToplevelClose,
|
||||
};
|
||||
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
{
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_compositor, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wlWm.xdgWmBase)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
xdg_wm_base_add_listener(wlWm.xdgWmBase, &xdgWmBaseListener, NULL);
|
||||
//wl_display_roundtrip(wlWm.display);
|
||||
|
||||
wlWm.surface = wl_compositor_create_surface(wlWm.compositor);
|
||||
if (!wlWm.surface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create wl_surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.xdgSurface = xdg_wm_base_get_xdg_surface(wlWm.xdgWmBase, wlWm.surface);
|
||||
xdg_surface_add_listener(wlWm.xdgSurface, &xdgSurfaceListener, NULL);
|
||||
|
||||
wlWm.xdgToplevel = xdg_surface_get_toplevel(wlWm.xdgSurface);
|
||||
xdg_toplevel_add_listener(wlWm.xdgToplevel, &xdgToplevelListener, NULL);
|
||||
xdg_toplevel_set_title(wlWm.xdgToplevel, title);
|
||||
xdg_toplevel_set_app_id(wlWm.xdgToplevel, "looking-glass-client");
|
||||
|
||||
if (fullscreen)
|
||||
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
|
||||
|
||||
if (maximize)
|
||||
xdg_toplevel_set_maximized(wlWm.xdgToplevel);
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
|
||||
if (wlWm.xdgDecorationManager)
|
||||
{
|
||||
wlWm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
wlWm.xdgDecorationManager, wlWm.xdgToplevel);
|
||||
if (wlWm.xdgToplevelDecoration)
|
||||
{
|
||||
zxdg_toplevel_decoration_v1_set_mode(wlWm.xdgToplevelDecoration,
|
||||
borderless ?
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE :
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWindowFree(void)
|
||||
{
|
||||
wl_surface_destroy(wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandSetWindowSize(int x, int y)
|
||||
{
|
||||
// FIXME: implement.
|
||||
}
|
||||
|
||||
void waylandSetFullscreen(bool fs)
|
||||
{
|
||||
if (fs)
|
||||
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
|
||||
else
|
||||
xdg_toplevel_unset_fullscreen(wlWm.xdgToplevel);
|
||||
}
|
||||
|
||||
bool waylandGetFullscreen(void)
|
||||
{
|
||||
return wlWm.fullscreen;
|
||||
}
|
||||
|
||||
bool waylandIsValidPointerPos(int x, int y)
|
||||
{
|
||||
return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height;
|
||||
}
|
||||
|
||||
|
||||
28
client/displayservers/X11/CMakeLists.txt
Normal file
28
client/displayservers/X11/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xi
|
||||
xfixes
|
||||
xscrnsaver
|
||||
xinerama
|
||||
)
|
||||
|
||||
add_library(displayserver_X11 STATIC
|
||||
x11.c
|
||||
)
|
||||
|
||||
add_definitions(-D GLX_GLXEXT_PROTOTYPES)
|
||||
|
||||
target_link_libraries(displayserver_X11
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_X11
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
1536
client/displayservers/X11/x11.c
Normal file
1536
client/displayservers/X11/x11.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -36,19 +36,21 @@ struct Inst
|
||||
|
||||
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
if (g_initCount++ == 0)
|
||||
bool ret = false;
|
||||
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTF_Init Failed");
|
||||
return false;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
return false;
|
||||
goto fail_init;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,50 +58,136 @@ static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
goto fail_config;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
if (!font_name)
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern);
|
||||
if (!pat)
|
||||
{
|
||||
DEBUG_ERROR("FCNameParse failed");
|
||||
goto fail_opaque;
|
||||
}
|
||||
|
||||
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * font = FcFontMatch(g_fontConfig, pat, &result);
|
||||
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
||||
if (!match)
|
||||
{
|
||||
DEBUG_ERROR("FcFontMatch Failed");
|
||||
goto fail_parse;
|
||||
}
|
||||
|
||||
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch)
|
||||
{
|
||||
this->font = TTF_OpenFont((char *)file, size);
|
||||
if (!this->font)
|
||||
{
|
||||
DEBUG_ERROR("TTL_OpenFont Failed");
|
||||
return false;
|
||||
goto fail_match;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
return false;
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
++g_initCount;
|
||||
ret = true;
|
||||
|
||||
fail_match:
|
||||
FcPatternDestroy(match);
|
||||
|
||||
fail_parse:
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
return true;
|
||||
if (ret)
|
||||
return true;
|
||||
|
||||
fail_opaque:
|
||||
free(this);
|
||||
*opaque = NULL;
|
||||
|
||||
fail_config:
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
}
|
||||
|
||||
fail_init:
|
||||
if (g_initCount == 0)
|
||||
TTF_Quit();
|
||||
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void lgf_sdl_destroy(LG_FontObj opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->font)
|
||||
TTF_CloseFont(this->font);
|
||||
free(this);
|
||||
|
||||
if (--g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
|
||||
TTF_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
static int lgf_sdl_multiline_width(TTF_Font * font, const char * text)
|
||||
{
|
||||
int w, maxW = 0;
|
||||
const char * ptr = text;
|
||||
char * buf = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
while (*ptr)
|
||||
{
|
||||
const char * newLine = strchr(ptr, '\n');
|
||||
size_t line = newLine ? newLine - ptr : strlen(ptr);
|
||||
if (line > size)
|
||||
{
|
||||
size = line * 2 + 1;
|
||||
void * new = realloc(buf, size);
|
||||
if (!new)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
buf = new;
|
||||
}
|
||||
memcpy(buf, ptr, line);
|
||||
buf[line] = '\0';
|
||||
|
||||
if (TTF_SizeUTF8(font, buf, &w, NULL) < 0)
|
||||
{
|
||||
free(buf);
|
||||
DEBUG_ERROR("Failed to measure text: %s", TTF_GetError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (w > maxW)
|
||||
maxW = w;
|
||||
ptr += line + 1;
|
||||
}
|
||||
free(buf);
|
||||
return maxW;
|
||||
}
|
||||
|
||||
static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
|
||||
@@ -113,7 +201,17 @@ static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color,
|
||||
color.b = (fg_color & 0x0000ff00) >> 8;
|
||||
color.a = (fg_color & 0x000000ff) >> 0;
|
||||
|
||||
if (!(surface = TTF_RenderText_Blended(this->font, text, color)))
|
||||
if (strchr(text, '\n'))
|
||||
{
|
||||
int width = lgf_sdl_multiline_width(this->font, text);
|
||||
if (width < 1)
|
||||
return NULL;
|
||||
surface = TTF_RenderUTF8_Blended_Wrapped(this->font, text, color, width);
|
||||
}
|
||||
else
|
||||
surface = TTF_RenderUTF8_Blended(this->font, text, color);
|
||||
|
||||
if (!surface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text: %s", TTF_GetError());
|
||||
return NULL;
|
||||
@@ -150,4 +248,4 @@ struct LG_Font LGF_SDL =
|
||||
.destroy = lgf_sdl_destroy,
|
||||
.render = lgf_sdl_render,
|
||||
.release = lgf_sdl_release
|
||||
};
|
||||
};
|
||||
|
||||
123
client/include/app.h
Normal file
123
client/include/app.h
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_APP_
|
||||
#define _H_LG_APP_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
bool app_isRunning(void);
|
||||
bool app_inputEnabled(void);
|
||||
void app_updateCursorPos(double x, double y);
|
||||
void app_updateWindowPos(int x, int y);
|
||||
void app_handleResizeEvent(int w, int h, const struct Border border);
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy);
|
||||
|
||||
void app_handleMouseBasic(void);
|
||||
void app_resyncMouseBasic(void);
|
||||
|
||||
void app_handleButtonPress(int button);
|
||||
void app_handleButtonRelease(int button);
|
||||
void app_handleKeyPress(int scancode);
|
||||
void app_handleKeyRelease(int scancode);
|
||||
void app_handleEnterEvent(bool entered);
|
||||
void app_handleFocusEvent(bool focused);
|
||||
void app_handleCloseEvent(void);
|
||||
|
||||
void app_setFullscreen(bool fs);
|
||||
bool app_getFullscreen(void);
|
||||
bool app_getProp(LG_DSProperty prop, void * ret);
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLDisplay app_getEGLDisplay(void);
|
||||
EGLNativeWindowType app_getEGLNativeWindow(void);
|
||||
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
LG_DSGLContext app_glCreateContext(void);
|
||||
void app_glDeleteContext(LG_DSGLContext context);
|
||||
void app_glMakeCurrent(LG_DSGLContext context);
|
||||
void app_glSetSwapInterval(int interval);
|
||||
void app_glSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
void app_clipboardRelease(void);
|
||||
void app_clipboardNotify(const LG_ClipboardData type, size_t size);
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*KeybindFn)(int sc, void * opaque);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param sc The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_releaseKeybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_releaseKeybind(KeybindHandle * handle);
|
||||
|
||||
/**
|
||||
* Release all keybindings
|
||||
*/
|
||||
void app_releaseAllKeybinds(void);
|
||||
|
||||
/**
|
||||
* Changes whether the help message is displayed or not.
|
||||
*/
|
||||
void app_showHelp(bool show);
|
||||
|
||||
/**
|
||||
* Changes whether the FPS is displayed or not.
|
||||
*/
|
||||
void app_showFPS(bool showFPS);
|
||||
|
||||
#endif
|
||||
43
client/include/egl_dynprocs.h
Normal file
43
client/include/egl_dynprocs.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform,
|
||||
void *native_display, const EGLint *attrib_list);
|
||||
typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target,
|
||||
GLeglImageOES image);
|
||||
|
||||
struct EGLDynProcs
|
||||
{
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplay;
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT;
|
||||
glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES;
|
||||
};
|
||||
|
||||
extern struct EGLDynProcs g_egl_dynProcs;
|
||||
|
||||
void egl_dynProcsInit(void);
|
||||
|
||||
#else
|
||||
#define egl_dynProcsInit(...)
|
||||
#endif
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*SuperEventFn)(SDL_Scancode key, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param key The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_release_keybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_release_keybind(KeybindHandle * handle);
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size);
|
||||
typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
typedef void (* LG_ClipboardReleaseFn)();
|
||||
typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardDataFn )(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
|
||||
typedef const char * (* LG_ClipboardGetName)();
|
||||
typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn);
|
||||
typedef void (* LG_ClipboardFree)();
|
||||
typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg);
|
||||
typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardRelease)();
|
||||
typedef void (* LG_ClipboardRequest)(LG_ClipboardData type);
|
||||
|
||||
typedef struct LG_Clipboard
|
||||
{
|
||||
LG_ClipboardGetName getName;
|
||||
LG_ClipboardInit init;
|
||||
LG_ClipboardFree free;
|
||||
LG_ClipboardWMEvent wmevent;
|
||||
LG_ClipboardNotice notice;
|
||||
LG_ClipboardRelease release;
|
||||
LG_ClipboardRequest request;
|
||||
}
|
||||
LG_Clipboard;
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef enum LG_OutFormat
|
||||
{
|
||||
LG_OUTPUT_INVALID,
|
||||
|
||||
LG_OUTPUT_BGRA,
|
||||
LG_OUTPUT_RGBA,
|
||||
LG_OUTPUT_RGBA10,
|
||||
LG_OUTPUT_YUV420
|
||||
}
|
||||
LG_OutFormat;
|
||||
|
||||
typedef bool (* LG_DecoderCreate )(void ** opaque);
|
||||
typedef void (* LG_DecoderDestroy )(void * opaque);
|
||||
typedef bool (* LG_DecoderInitialize )(void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
typedef void (* LG_DecoderDeInitialize )(void * opaque);
|
||||
typedef LG_OutFormat (* LG_DecoderGetOutFormat )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFramePitch )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFrameStride)(void * opaque);
|
||||
typedef bool (* LG_DecoderDecode )(void * opaque, const uint8_t * src, size_t srcSize);
|
||||
typedef const uint8_t * (* LG_DecoderGetBuffer )(void * opaque);
|
||||
|
||||
typedef bool (* LG_DecoderInitGLTexture )(void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
typedef void (* LG_DecoderFreeGLTexture )(void * opaque, void * ref);
|
||||
typedef bool (* LG_DecoderUpdateGLTexture)(void * opaque, void * ref);
|
||||
|
||||
typedef struct LG_Decoder
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_DecoderCreate create;
|
||||
LG_DecoderDestroy destroy;
|
||||
LG_DecoderInitialize initialize;
|
||||
LG_DecoderDeInitialize deinitialize;
|
||||
LG_DecoderGetOutFormat get_out_format;
|
||||
LG_DecoderGetFramePitch get_frame_pitch;
|
||||
LG_DecoderGetFrameStride get_frame_stride;
|
||||
LG_DecoderDecode decode;
|
||||
LG_DecoderGetBuffer get_buffer;
|
||||
|
||||
// optional support
|
||||
const bool has_gl;
|
||||
LG_DecoderInitGLTexture init_gl_texture;
|
||||
LG_DecoderFreeGLTexture free_gl_texture;
|
||||
LG_DecoderUpdateGLTexture update_gl_texture;
|
||||
}
|
||||
LG_Decoder;
|
||||
210
client/include/interface/displayserver.h
Normal file
210
client/include/interface/displayserver.h
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_I_DISPLAYSERVER_
|
||||
#define _H_I_DISPLAYSERVER_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef enum LG_DSProperty
|
||||
{
|
||||
/**
|
||||
* returns the maximum number of samples supported
|
||||
* if not implemented LG assumes no multisample support
|
||||
* return data type: int
|
||||
*/
|
||||
LG_DS_MAX_MULTISAMPLE,
|
||||
|
||||
/**
|
||||
* returns if the platform is warp capable
|
||||
* if not implemented LG assumes that the platform is warp capable
|
||||
* return data type: bool
|
||||
*/
|
||||
LG_DS_WARP_SUPPORT,
|
||||
}
|
||||
LG_DSProperty;
|
||||
|
||||
enum LG_DSWarpSupport
|
||||
{
|
||||
LG_DS_WARP_NONE,
|
||||
LG_DS_WARP_SURFACE,
|
||||
LG_DS_WARP_SCREEN,
|
||||
};
|
||||
|
||||
typedef struct LG_DSInitParams
|
||||
{
|
||||
const char * title;
|
||||
int x, y, w, h;
|
||||
bool center;
|
||||
bool fullscreen;
|
||||
bool resizable;
|
||||
bool borderless;
|
||||
bool maximize;
|
||||
bool minimizeOnFocusLoss;
|
||||
|
||||
// if true the renderer requires an OpenGL context
|
||||
bool opengl;
|
||||
}
|
||||
LG_DSInitParams;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size);
|
||||
|
||||
typedef struct LG_DSGLContext
|
||||
* LG_DSGLContext;
|
||||
|
||||
struct LG_DisplayServerOps
|
||||
{
|
||||
/* called before options are parsed, useful for registering options */
|
||||
void (*setup)(void);
|
||||
|
||||
/* return true if the selected ds is valid for the current platform */
|
||||
bool (*probe)(void);
|
||||
|
||||
/* called before anything has been initialized */
|
||||
bool (*earlyInit)(void);
|
||||
|
||||
/* called when it's time to create and show the application window */
|
||||
bool (*init)(const LG_DSInitParams params);
|
||||
|
||||
/* called at startup after window creation, renderer and SPICE is ready */
|
||||
void (*startup)();
|
||||
|
||||
/* called just before final window destruction, before final free */
|
||||
void (*shutdown)();
|
||||
|
||||
/* final free */
|
||||
void (*free)();
|
||||
|
||||
/*
|
||||
* return a system specific property, returns false if unsupported or failure
|
||||
* if the platform does not support/implement the requested property the value
|
||||
* of `ret` must not be altered.
|
||||
*/
|
||||
bool (*getProp)(LG_DSProperty prop, void * ret);
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
/* EGL support */
|
||||
EGLDisplay (*getEGLDisplay)(void);
|
||||
EGLNativeWindowType (*getEGLNativeWindow)(void);
|
||||
void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
/* opengl platform specific methods */
|
||||
LG_DSGLContext (*glCreateContext)(void);
|
||||
void (*glDeleteContext)(LG_DSGLContext context);
|
||||
void (*glMakeCurrent)(LG_DSGLContext context);
|
||||
void (*glSetSwapInterval)(int interval);
|
||||
void (*glSwapBuffers)(void);
|
||||
#endif
|
||||
|
||||
/* dm specific cursor implementations */
|
||||
void (*showPointer)(bool show);
|
||||
void (*grabPointer)();
|
||||
void (*ungrabPointer)();
|
||||
void (*grabKeyboard)();
|
||||
void (*ungrabKeyboard)();
|
||||
|
||||
/* exiting = true if the warp is to leave the window */
|
||||
void (*warpPointer)(int x, int y, bool exiting);
|
||||
|
||||
/* called when the client needs to realign the pointer. This should simply
|
||||
* call the appropriate app_handleMouse* method for the platform with zero
|
||||
* deltas */
|
||||
void (*realignPointer)();
|
||||
|
||||
/* returns true if the position specified is actually valid */
|
||||
bool (*isValidPointerPos)(int x, int y);
|
||||
|
||||
/* called to disable/enable the screensaver */
|
||||
void (*inhibitIdle)();
|
||||
void (*uninhibitIdle)();
|
||||
|
||||
/* wait for the specified time without blocking UI processing/event loops */
|
||||
void (*wait)(unsigned int time);
|
||||
|
||||
/* get/set the window dimensions */
|
||||
void (*setWindowSize)(int x, int y);
|
||||
bool (*getFullscreen)(void);
|
||||
void (*setFullscreen)(bool fs);
|
||||
|
||||
/* clipboard support, optional, if not supported set to NULL */
|
||||
bool (*cbInit)(void);
|
||||
void (*cbNotice)(LG_ClipboardData type);
|
||||
void (*cbRelease)(void);
|
||||
void (*cbRequest)(LG_ClipboardData type);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#define ASSERT_EGL_FN(x) assert(x);
|
||||
#else
|
||||
#define ASSERT_EGL_FN(x)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
#define ASSERT_OPENGL_FN(x) assert(x)
|
||||
#else
|
||||
#define ASSERT_OPENGL_FN(x)
|
||||
#endif
|
||||
|
||||
#define ASSERT_LG_DS_VALID(x) \
|
||||
assert((x)->setup ); \
|
||||
assert((x)->probe ); \
|
||||
assert((x)->earlyInit ); \
|
||||
assert((x)->init ); \
|
||||
assert((x)->startup ); \
|
||||
assert((x)->shutdown ); \
|
||||
assert((x)->free ); \
|
||||
assert((x)->getProp ); \
|
||||
ASSERT_EGL_FN((x)->getEGLDisplay ); \
|
||||
ASSERT_EGL_FN((x)->getEGLNativeWindow ); \
|
||||
ASSERT_EGL_FN((x)->eglSwapBuffers ); \
|
||||
ASSERT_OPENGL_FN((x)->glCreateContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glDeleteContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glMakeCurrent ); \
|
||||
ASSERT_OPENGL_FN((x)->glSetSwapInterval); \
|
||||
ASSERT_OPENGL_FN((x)->glSwapBuffers ); \
|
||||
assert((x)->showPointer ); \
|
||||
assert((x)->grabPointer ); \
|
||||
assert((x)->ungrabPointer ); \
|
||||
assert((x)->warpPointer ); \
|
||||
assert((x)->realignPointer ); \
|
||||
assert((x)->isValidPointerPos ); \
|
||||
assert((x)->inhibitIdle ); \
|
||||
assert((x)->uninhibitIdle ); \
|
||||
assert((x)->wait ); \
|
||||
assert((x)->setWindowSize ); \
|
||||
assert((x)->setFullscreen ); \
|
||||
assert((x)->getFullscreen );
|
||||
|
||||
#endif
|
||||
@@ -21,9 +21,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/framebuffer.h"
|
||||
@@ -33,10 +30,13 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
(x)->create && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->on_restart && \
|
||||
(x)->on_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->on_alert && \
|
||||
(x)->on_help && \
|
||||
(x)->on_show_fps && \
|
||||
(x)->render_startup && \
|
||||
(x)->render && \
|
||||
(x)->update_fps)
|
||||
@@ -45,28 +45,48 @@ typedef struct LG_RendererParams
|
||||
{
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
bool showFPS;
|
||||
bool quickSplash;
|
||||
}
|
||||
LG_RendererParams;
|
||||
|
||||
typedef enum LG_RendererSupport
|
||||
{
|
||||
LG_SUPPORTS_DMABUF
|
||||
}
|
||||
LG_RendererSupport;
|
||||
|
||||
typedef enum LG_RendererRotate
|
||||
{
|
||||
LG_ROTATE_0,
|
||||
LG_ROTATE_90,
|
||||
LG_ROTATE_180,
|
||||
LG_ROTATE_270
|
||||
}
|
||||
LG_RendererRotate;
|
||||
|
||||
// kept out of the enum so gcc doesn't warn when it's missing from a switch
|
||||
// statement.
|
||||
#define LG_ROTATE_MAX (LG_ROTATE_270+1)
|
||||
|
||||
typedef struct LG_RendererFormat
|
||||
{
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
unsigned int stride; // scanline width (zero if compresed)
|
||||
unsigned int pitch; // scanline bytes (or compressed size)
|
||||
unsigned int bpp; // bits per pixel (zero if compressed)
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
unsigned int stride; // scanline width (zero if compresed)
|
||||
unsigned int pitch; // scanline bytes (or compressed size)
|
||||
unsigned int bpp; // bits per pixel (zero if compressed)
|
||||
LG_RendererRotate rotate; // guest rotation
|
||||
}
|
||||
LG_RendererFormat;
|
||||
|
||||
typedef struct LG_RendererRect
|
||||
{
|
||||
bool valid;
|
||||
int x;
|
||||
int y;
|
||||
unsigned int w;
|
||||
unsigned int h;
|
||||
bool valid;
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
}
|
||||
LG_RendererRect;
|
||||
|
||||
@@ -84,32 +104,43 @@ typedef const char * (* LG_RendererGetName)();
|
||||
// called pre-creation to allow the renderer to register any options it might have
|
||||
typedef void (* LG_RendererSetup)();
|
||||
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
|
||||
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
|
||||
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer frame);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params, bool * needsOpenGL);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque);
|
||||
typedef void (* LG_RendererDeInitialize )(void * opaque);
|
||||
typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support);
|
||||
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect, LG_RendererRotate rotate);
|
||||
typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||
typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y);
|
||||
typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA);
|
||||
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef void (* LG_RendererOnHelp )(void * opaque, const char * message);
|
||||
typedef void (* LG_RendererOnShowFPS )(void * opaque, bool showFPS);
|
||||
typedef bool (* LG_RendererRenderStartup)(void * opaque);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererSetup setup;
|
||||
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameEvent on_frame_event;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererRender render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererSupports supports;
|
||||
LG_RendererOnRestart on_restart;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameFormat on_frame_format;
|
||||
LG_RendererOnFrame on_frame;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererOnHelp on_help;
|
||||
LG_RendererOnShowFPS on_show_fps;
|
||||
LG_RendererRenderStartup render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
}
|
||||
LG_Renderer;
|
||||
LG_Renderer;
|
||||
|
||||
@@ -25,7 +25,8 @@ void ll_free (struct ll * list);
|
||||
void ll_push (struct ll * list, void * data);
|
||||
bool ll_shift (struct ll * list, void ** data);
|
||||
bool ll_peek_head(struct ll * list, void ** data);
|
||||
bool ll_peek_tail(struct ll * list, void ** data);
|
||||
unsigned int ll_count (struct ll * list);
|
||||
|
||||
void ll_reset (struct ll * list);
|
||||
bool ll_walk (struct ll * list, void ** data);
|
||||
bool ll_walk (struct ll * list, void ** data);
|
||||
|
||||
36
client/include/util.h
Normal file
36
client/include/util.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_UTIL_
|
||||
#define _H_LG_UTIL_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "common/types.h"
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
bool util_fileGetContents(const char * filename, char ** buffer, size_t * length);
|
||||
|
||||
void util_cursorToInt(double ex, double ey, int *x, int *y);
|
||||
bool util_guestCurToLocal(struct DoublePoint *local);
|
||||
void util_localCurToGuest(struct DoublePoint *guest);
|
||||
void util_rotatePoint(struct DoublePoint *point);
|
||||
|
||||
#endif
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static inline uint64_t microtime()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
|
||||
return ((uint64_t)time.tv_sec * 1000000) + (time.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
static inline uint64_t nanotime()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
|
||||
return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec;
|
||||
}
|
||||
|
||||
static inline void nsleep(uint64_t ns)
|
||||
{
|
||||
const struct timespec ts =
|
||||
{
|
||||
.tv_sec = ns / 1e9,
|
||||
.tv_nsec = ns - ((ns / 1e9) * 1e9)
|
||||
};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
#ifdef ATOMIC_LOCKING
|
||||
#define LG_LOCK_MODE "Atomic"
|
||||
typedef volatile int LG_Lock;
|
||||
#define LG_LOCK_INIT(x) (x) = 0
|
||||
#define LG_LOCK(x) while(__sync_lock_test_and_set(&(x), 1)) {nsleep(100);}
|
||||
#define LG_UNLOCK(x) __sync_lock_release(&x)
|
||||
#define LG_LOCK_FREE(x)
|
||||
#else
|
||||
#include <SDL2/SDL.h>
|
||||
#define LG_LOCK_MODE "Mutex"
|
||||
typedef SDL_mutex * LG_Lock;
|
||||
#define LG_LOCK_INIT(x) (x = SDL_CreateMutex())
|
||||
#define LG_LOCK(x) SDL_LockMutex(x)
|
||||
#define LG_UNLOCK(x) SDL_UnlockMutex(x)
|
||||
#define LG_LOCK_FREE(x) SDL_DestroyMutex(x)
|
||||
#endif
|
||||
|
||||
static inline uint32_t get_bit(const uint8_t * const base, size_t * const offset)
|
||||
{
|
||||
uint32_t out = ((*(base + (*offset >> 0x3))) >> (0x7 - (*offset & 0x7))) & 0x1;
|
||||
++*offset;
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline uint32_t get_bits(const uint8_t * const base, size_t * const offset, const uint8_t bits)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
for (int i = 0; i < bits; ++i)
|
||||
value |= (get_bit(base, offset) ? 1 : 0) << (bits - i - 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline uint32_t decode_u_golomb(const uint8_t * const base, size_t * const offset)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
while(get_bit(base, offset) == 0)
|
||||
++i;
|
||||
|
||||
return ((1 << i) - 1 + get_bits(base, offset, i));
|
||||
}
|
||||
|
||||
static inline int32_t decode_s_golomb(const uint8_t * const base, size_t * const offset)
|
||||
{
|
||||
const uint32_t g = decode_u_golomb(base, offset);
|
||||
return (g & 0x1) ? (g + 1) / 2 : -(g / 2);
|
||||
}
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
bool file_get_contents(const char * filename, char ** buffer, size_t * length);
|
||||
1009
client/parsers/nal.c
1009
client/parsers/nal.c
File diff suppressed because it is too large
Load Diff
@@ -1,305 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define NAL_TYPE_CODED_SLICE_NON_IDR 1
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_A 2
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_B 3
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_C 4
|
||||
#define NAL_TYPE_CODED_SLICE_IDR 5
|
||||
#define NAL_TYPE_SPS 7
|
||||
#define NAL_TYPE_PPS 8
|
||||
#define NAL_TYPE_AUD 9
|
||||
#define NAL_TYPE_END_OF_SEQUENCE 10
|
||||
#define NAL_TYPE_END_OF_STREAM 11
|
||||
#define NAL_TYPE_CODED_SLICE_AUX 19
|
||||
|
||||
#define IDC_PROFILE_BASELINE 66
|
||||
#define IDC_PROFILE_MAIN 77
|
||||
#define IDC_PROFILE_EXTENDED 88
|
||||
#define IDC_PROFILE_HP 100
|
||||
#define IDC_PROFILE_Hi10P 110
|
||||
#define IDC_PROFILE_Hi422 122
|
||||
#define IDC_PROFILE_Hi444 244
|
||||
#define IDC_PROFILE_CAVLC444 44
|
||||
|
||||
#define IDC_CHROMA_FORMAT_YUV400 0
|
||||
#define IDC_CHROMA_FORMAT_YUV420 1
|
||||
#define IDC_CHROMA_FORMAT_YVU422 2
|
||||
#define IDC_CHROMA_FORMAT_YUV444 3
|
||||
|
||||
#define IDC_VUI_ASPECT_RATIO_EXTENDED_SAR 0xFF
|
||||
|
||||
#define NAL_PICTURE_TYPE_I 0
|
||||
#define NAL_PICTURE_TYPE_P 1
|
||||
#define NAL_PICTURE_TYPE_B 2
|
||||
|
||||
#define NAL_SLICE_TYPE_P 0
|
||||
#define NAL_SLICE_TYPE_B 1
|
||||
#define NAL_SLICE_TYPE_I 2
|
||||
#define NAL_SLICE_TYPE_SP 3
|
||||
#define NAL_SLICE_TYPE_SI 4
|
||||
|
||||
typedef struct NAL_SPS
|
||||
{
|
||||
uint8_t profile_idc;
|
||||
uint8_t constraint_set_flags[3];
|
||||
uint8_t level_idc;
|
||||
uint32_t seq_parameter_set_id;
|
||||
uint32_t chroma_format_idc;
|
||||
uint8_t seperate_colour_plane_flag;
|
||||
uint32_t bit_depth_luma_minus8;
|
||||
uint32_t bit_depth_chroma_minus8;
|
||||
uint8_t lossless_qpprime_y_zero_flag;
|
||||
uint8_t seq_scaling_matrix_present_flag;
|
||||
uint8_t seq_scaling_list_present_flag[12];
|
||||
uint32_t log2_max_frame_num_minus4;
|
||||
uint32_t pic_order_cnt_type;
|
||||
uint32_t log2_max_pic_order_cnt_lsb_minus4;
|
||||
uint8_t delta_pic_order_always_zero_flag;
|
||||
int32_t offset_for_non_ref_pic;
|
||||
int32_t offset_for_top_to_bottom_field;
|
||||
uint32_t num_ref_frames_in_pic_order_cnt_cycle;
|
||||
int32_t * offset_for_ref_frame;
|
||||
uint32_t num_ref_frames;
|
||||
uint8_t gaps_in_frame_num_value_allowed_flag;
|
||||
uint32_t pic_width_in_mbs_minus1;
|
||||
uint32_t pic_height_in_map_units_minus1;
|
||||
uint8_t frame_mbs_only_flag;
|
||||
uint8_t mb_adaptive_frame_field_flag;
|
||||
uint8_t direct_8x8_inference_flag;
|
||||
uint8_t frame_cropping_flag;
|
||||
uint32_t frame_crop_left_offset;
|
||||
uint32_t frame_crop_right_offset;
|
||||
uint32_t frame_crop_top_offset;
|
||||
uint32_t frame_crop_bottom_offset;
|
||||
uint8_t vui_parameters_present_flag;
|
||||
}
|
||||
NAL_SPS;
|
||||
|
||||
typedef struct NAL_CPB
|
||||
{
|
||||
uint32_t bit_rate_value_minus1;
|
||||
uint32_t cpb_size_value_minus1;
|
||||
uint8_t cbr_flag;
|
||||
}
|
||||
NAL_CPB;
|
||||
|
||||
typedef struct NAL_HRD
|
||||
{
|
||||
uint32_t cpb_cnt_minus1;
|
||||
uint8_t bit_rate_scale;
|
||||
uint8_t cpb_size_scale;
|
||||
uint8_t cpb_size_count;
|
||||
NAL_CPB * cpb;
|
||||
uint8_t initial_cpb_removal_delay_length_minus1;
|
||||
uint8_t cpb_removal_delay_length_minus1;
|
||||
uint8_t dpb_output_delay_length_minus1;
|
||||
uint8_t time_offset_length;
|
||||
}
|
||||
NAL_HRD;
|
||||
|
||||
typedef struct NAL_VUI
|
||||
{
|
||||
uint8_t aspect_ratio_info_present_flag;
|
||||
uint8_t aspect_ratio_idc;
|
||||
uint16_t sar_width;
|
||||
uint16_t sar_height;
|
||||
uint8_t overscan_info_present_flag;
|
||||
uint8_t overscan_appropriate_flag;
|
||||
uint8_t video_signal_type_present_flag;
|
||||
uint8_t video_format;
|
||||
uint8_t video_full_range_flag;
|
||||
uint8_t colour_description_present_flag;
|
||||
uint8_t colour_primaries;
|
||||
uint8_t transfer_characteristics;
|
||||
uint8_t matrix_coefficients;
|
||||
uint8_t chroma_loc_info_present_flag;
|
||||
uint32_t chroma_sample_loc_type_top_field;
|
||||
uint32_t chroma_sample_loc_type_bottom_field;
|
||||
uint8_t timing_info_present_flag;
|
||||
uint32_t num_units_in_tick;
|
||||
uint32_t time_scale;
|
||||
uint8_t fixed_frame_rate_flag;
|
||||
uint8_t nal_hrd_parameters_present_flag;
|
||||
NAL_HRD nal_hrd_parameters;
|
||||
uint8_t vcl_hrd_parameters_present_flag;
|
||||
NAL_HRD vcl_hrd_parameters;
|
||||
uint8_t low_delay_hrd_flag;
|
||||
uint8_t pic_struct_present_flag;
|
||||
uint8_t bitstream_restriction_flag;
|
||||
uint8_t motion_vectors_over_pic_boundaries_flag;
|
||||
uint32_t max_bytes_per_pic_denom;
|
||||
uint32_t max_bits_per_mb_denom;
|
||||
uint32_t log2_max_mv_length_horizontal;
|
||||
uint32_t log2_max_mv_length_vertical;
|
||||
uint32_t num_reorder_frames;
|
||||
uint32_t max_dec_frame_buffering;
|
||||
}
|
||||
NAL_VUI;
|
||||
|
||||
typedef struct NAL_SLICE_GROUP_T0
|
||||
{
|
||||
uint32_t run_length_minus1;
|
||||
}
|
||||
NAL_SLICE_GROUP_T0;
|
||||
|
||||
typedef struct NAL_SLICE_GROUP_T2
|
||||
{
|
||||
uint32_t top_left;
|
||||
uint32_t bottom_right;
|
||||
}
|
||||
NAL_SLICE_GROUP_T2;
|
||||
|
||||
typedef union NAL_SLICE_GROUP
|
||||
{
|
||||
NAL_SLICE_GROUP_T0 t0;
|
||||
NAL_SLICE_GROUP_T2 t2;
|
||||
}
|
||||
NAL_SLICE_GROUP;
|
||||
|
||||
typedef struct NAL_PPS
|
||||
{
|
||||
uint32_t pic_parameter_set_id;
|
||||
uint32_t seq_parameter_set_id;
|
||||
uint8_t entropy_coding_mode_flag;
|
||||
uint8_t pic_order_present_flag;
|
||||
uint32_t num_slice_groups_minus1;
|
||||
NAL_SLICE_GROUP * slice_groups;
|
||||
uint32_t slice_group_map_type;
|
||||
uint8_t slice_group_change_direction_flag;
|
||||
uint32_t slice_group_change_rate_minus1;
|
||||
uint32_t pic_size_in_map_units_minus1;
|
||||
uint32_t * slice_group_id;
|
||||
uint32_t num_ref_idx_l0_active_minus1;
|
||||
uint32_t num_ref_idx_l1_active_minus1;
|
||||
uint8_t weighted_pred_flag;
|
||||
uint8_t weighted_bipred_idc;
|
||||
int32_t pic_init_qp_minus26;
|
||||
int32_t pic_init_qs_minus26;
|
||||
int32_t chroma_qp_index_offset;
|
||||
uint8_t deblocking_filter_control_present_flag;
|
||||
uint8_t constrained_intra_pred_flag;
|
||||
uint8_t redundant_pic_cnt_present_flag;
|
||||
|
||||
uint8_t transform_8x8_mode_flag;
|
||||
uint8_t pic_scaling_matrix_present_flag;
|
||||
uint8_t pic_scaling_list_present_flag[6];
|
||||
int32_t scaling_list_4x4[6];
|
||||
int32_t scaling_list_8x8[2];
|
||||
int32_t second_chroma_qp_index_offset;
|
||||
}
|
||||
NAL_PPS;
|
||||
|
||||
typedef struct NAL_RPL_REORDER_L
|
||||
{
|
||||
bool valid;
|
||||
uint32_t reordering_of_pic_nums_idc;
|
||||
uint32_t abs_diff_pic_num_minus1;
|
||||
uint32_t long_term_pic_num;
|
||||
}
|
||||
NAL_RPL_REORDER_L;
|
||||
|
||||
typedef struct NAL_RPL_REORDER
|
||||
{
|
||||
uint8_t ref_pic_list_reordering_flag_l0;
|
||||
NAL_RPL_REORDER_L l0[3];
|
||||
uint8_t ref_pic_list_reordering_flag_l1;
|
||||
NAL_RPL_REORDER_L l1[3];
|
||||
}
|
||||
NAL_RPL_REORDER;
|
||||
|
||||
typedef struct NAL_PW_TABLE_L
|
||||
{
|
||||
int32_t luma_weight;
|
||||
int32_t luma_offset;
|
||||
int32_t chroma_weight[2];
|
||||
int32_t chroma_offset[2];
|
||||
}
|
||||
NAL_PW_TABLE_L;
|
||||
|
||||
typedef struct NAL_PW_TABLE
|
||||
{
|
||||
uint32_t luma_log2_weight_denom;
|
||||
uint32_t chroma_log2_weight_denom;
|
||||
uint8_t luma_weight_flag[2];
|
||||
uint8_t chroma_weight_flag[2];
|
||||
NAL_PW_TABLE_L * l0;
|
||||
NAL_PW_TABLE_L * l1;
|
||||
}
|
||||
NAL_PW_TABLE;
|
||||
|
||||
typedef struct NAL_RP_MARKING
|
||||
{
|
||||
uint8_t no_output_of_prior_pics_flag;
|
||||
uint8_t long_term_reference_flag;
|
||||
uint8_t adaptive_ref_pic_marking_mode_flag;
|
||||
uint32_t memory_management_control_operation;
|
||||
uint32_t difference_of_pic_nums_minus1;
|
||||
uint32_t long_term_pic_num;
|
||||
uint32_t long_term_frame_idx;
|
||||
uint32_t max_long_term_frame_idx_plus1;
|
||||
}
|
||||
NAL_RP_MARKING;
|
||||
|
||||
typedef struct NAL_SLICE
|
||||
{
|
||||
uint8_t nal_ref_idc;
|
||||
uint32_t first_mb_in_slice;
|
||||
uint32_t slice_type;
|
||||
uint32_t pic_parameter_set_id;
|
||||
uint32_t frame_num;
|
||||
uint8_t field_pic_flag;
|
||||
uint8_t bottom_field_flag;
|
||||
uint32_t idr_pic_id;
|
||||
uint32_t pic_order_cnt_lsb;
|
||||
int32_t delta_pic_order_cnt_bottom;
|
||||
int32_t delta_pic_order_cnt[2];
|
||||
uint32_t redundant_pic_cnt;
|
||||
uint8_t direct_spatial_mv_pred_flag;
|
||||
uint8_t num_ref_idx_active_override_flag;
|
||||
uint32_t num_ref_idx_l0_active_minus1;
|
||||
uint32_t num_ref_idx_l1_active_minus1;
|
||||
NAL_RPL_REORDER ref_pic_list_reordering;
|
||||
NAL_PW_TABLE pred_weight_table;
|
||||
NAL_RP_MARKING dec_ref_pic_marking;
|
||||
uint32_t cabac_init_idc;
|
||||
int32_t slice_qp_delta;
|
||||
uint8_t sp_for_switch_flag;
|
||||
int32_t slice_qs_delta;
|
||||
uint32_t disable_deblocking_filter_idc;
|
||||
int32_t slice_alpha_c0_offset_div2;
|
||||
int32_t slice_beta_offset_div2;
|
||||
uint32_t slice_group_change_cycle;
|
||||
}
|
||||
NAL_SLICE;
|
||||
|
||||
typedef struct NAL * NAL;
|
||||
|
||||
bool nal_initialize (NAL * ptr);
|
||||
void nal_deinitialize(NAL this );
|
||||
bool nal_parse (NAL this, const uint8_t * src, size_t size, size_t * seek);
|
||||
|
||||
bool nal_get_primary_picture_type(NAL this, uint8_t * pic_type);
|
||||
bool nal_get_sps (NAL this, const NAL_SPS ** sps );
|
||||
bool nal_get_pps (NAL this, const NAL_PPS ** pps );
|
||||
bool nal_get_slice(NAL this, const NAL_SLICE ** slice);
|
||||
@@ -16,13 +16,15 @@ make_object(
|
||||
EGL_SHADER
|
||||
shader/desktop.vert
|
||||
shader/desktop_rgb.frag
|
||||
shader/desktop_yuv.frag
|
||||
shader/cursor.vert
|
||||
shader/cursor_rgb.frag
|
||||
shader/cursor_mono.frag
|
||||
shader/fps.vert
|
||||
shader/fps.frag
|
||||
shader/fps_bg.frag
|
||||
shader/help.vert
|
||||
shader/help.frag
|
||||
shader/help_bg.frag
|
||||
shader/alert.vert
|
||||
shader/alert.frag
|
||||
shader/alert_bg.frag
|
||||
@@ -34,13 +36,14 @@ make_object(
|
||||
|
||||
add_library(renderer_EGL STATIC
|
||||
egl.c
|
||||
debug.c
|
||||
egldebug.c
|
||||
shader.c
|
||||
texture.c
|
||||
model.c
|
||||
desktop.c
|
||||
cursor.c
|
||||
fps.c
|
||||
help.c
|
||||
draw.c
|
||||
splash.c
|
||||
alert.c
|
||||
|
||||
@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "alert.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
@@ -72,7 +72,7 @@ bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj
|
||||
(*alert)->fontObj = fontObj;
|
||||
LG_LOCK_INIT((*alert)->lock);
|
||||
|
||||
if (!egl_texture_init(&(*alert)->texture))
|
||||
if (!egl_texture_init(&(*alert)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert texture");
|
||||
return false;
|
||||
@@ -175,6 +175,7 @@ void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
|
||||
alert->bmp->width ,
|
||||
alert->bmp->height,
|
||||
alert->bmp->width * alert->bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
@@ -215,4 +216,4 @@ void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
|
||||
egl_model_render(alert->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "cursor.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/option.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
@@ -33,6 +34,15 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "cursor_rgb.frag.h"
|
||||
#include "cursor_mono.frag.h"
|
||||
|
||||
struct CursorTex
|
||||
{
|
||||
struct EGL_Texture * texture;
|
||||
struct EGL_Shader * shader;
|
||||
GLuint uMousePos;
|
||||
GLuint uRotate;
|
||||
GLuint uCBMode;
|
||||
};
|
||||
|
||||
struct EGL_Cursor
|
||||
{
|
||||
LG_Lock lock;
|
||||
@@ -47,21 +57,64 @@ struct EGL_Cursor
|
||||
// cursor state
|
||||
bool visible;
|
||||
float x, y, w, h;
|
||||
LG_RendererRotate rotate;
|
||||
int cbMode;
|
||||
|
||||
// textures
|
||||
struct EGL_Texture * texture;
|
||||
struct EGL_Texture * textureMono;
|
||||
struct CursorTex norm;
|
||||
struct CursorTex mono;
|
||||
struct EGL_Model * model;
|
||||
};
|
||||
|
||||
// shaders
|
||||
struct EGL_Shader * shader;
|
||||
struct EGL_Shader * shaderMono;
|
||||
static bool egl_cursor_tex_init(struct CursorTex * t,
|
||||
const char * vertex_code , size_t vertex_size,
|
||||
const char * fragment_code, size_t fragment_size)
|
||||
{
|
||||
if (!egl_texture_init(&t->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
// uniforms
|
||||
GLuint uMousePos;
|
||||
GLuint uMousePosMono;
|
||||
if (!egl_shader_init(&t->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
// model
|
||||
struct EGL_Model * model;
|
||||
if (!egl_shader_compile(t->shader,
|
||||
vertex_code, vertex_size, fragment_code, fragment_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
t->uMousePos = egl_shader_get_uniform_location(t->shader, "mouse" );
|
||||
t->uRotate = egl_shader_get_uniform_location(t->shader, "rotate");
|
||||
t->uCBMode = egl_shader_get_uniform_location(t->shader, "cbMode");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void egl_cursor_tex_uniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono)
|
||||
{
|
||||
if (mono)
|
||||
{
|
||||
glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
glUniform4f(t->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
}
|
||||
|
||||
static void egl_cursor_tex_free(struct CursorTex * t)
|
||||
{
|
||||
egl_texture_free(&t->texture);
|
||||
egl_shader_free (&t->shader );
|
||||
};
|
||||
|
||||
bool egl_cursor_init(EGL_Cursor ** cursor)
|
||||
@@ -76,50 +129,15 @@ bool egl_cursor_init(EGL_Cursor ** cursor)
|
||||
memset(*cursor, 0, sizeof(EGL_Cursor));
|
||||
LG_LOCK_INIT((*cursor)->lock);
|
||||
|
||||
if (!egl_texture_init(&(*cursor)->texture))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor texture");
|
||||
if (!egl_cursor_tex_init(&(*cursor)->norm,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_texture_init(&(*cursor)->textureMono))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor mono texture");
|
||||
if (!egl_cursor_tex_init(&(*cursor)->mono,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*cursor)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*cursor)->shaderMono))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor mono shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(
|
||||
(*cursor)->shader,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(
|
||||
(*cursor)->shaderMono,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor mono shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*cursor)->uMousePos = egl_shader_get_uniform_location((*cursor)->shader , "mouse");
|
||||
(*cursor)->uMousePosMono = egl_shader_get_uniform_location((*cursor)->shaderMono, "mouse");
|
||||
|
||||
if (!egl_model_init(&(*cursor)->model))
|
||||
{
|
||||
@@ -128,6 +146,9 @@ bool egl_cursor_init(EGL_Cursor ** cursor)
|
||||
}
|
||||
|
||||
egl_model_set_default((*cursor)->model);
|
||||
|
||||
(*cursor)->cbMode = option_get_int("egl", "cbMode");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,24 +161,23 @@ void egl_cursor_free(EGL_Cursor ** cursor)
|
||||
if ((*cursor)->data)
|
||||
free((*cursor)->data);
|
||||
|
||||
egl_texture_free(&(*cursor)->texture );
|
||||
egl_texture_free(&(*cursor)->textureMono);
|
||||
egl_shader_free (&(*cursor)->shader );
|
||||
egl_shader_free (&(*cursor)->shaderMono );
|
||||
egl_model_free (&(*cursor)->model );
|
||||
egl_cursor_tex_free(&(*cursor)->norm);
|
||||
egl_cursor_tex_free(&(*cursor)->mono);
|
||||
egl_model_free(&(*cursor)->model);
|
||||
|
||||
free(*cursor);
|
||||
*cursor = NULL;
|
||||
}
|
||||
|
||||
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data)
|
||||
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type,
|
||||
const int width, const int height, const int stride, const uint8_t * data)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
|
||||
cursor->type = type;
|
||||
cursor->width = width;
|
||||
cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height);
|
||||
cursor->stride = stride;
|
||||
cursor->stride = stride;
|
||||
|
||||
const size_t size = height * stride;
|
||||
if (size > cursor->dataSize)
|
||||
@@ -195,7 +215,7 @@ void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x
|
||||
cursor->y = y;
|
||||
}
|
||||
|
||||
void egl_cursor_render(EGL_Cursor * cursor)
|
||||
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
|
||||
{
|
||||
if (!cursor->visible)
|
||||
return;
|
||||
@@ -214,9 +234,9 @@ void egl_cursor_render(EGL_Cursor * cursor)
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_texture_setup(cursor->texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false);
|
||||
egl_texture_update(cursor->texture, data);
|
||||
egl_model_set_texture(cursor->model, cursor->texture);
|
||||
egl_texture_setup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false, false);
|
||||
egl_texture_update(cursor->norm.texture, data);
|
||||
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -238,39 +258,41 @@ void egl_cursor_render(EGL_Cursor * cursor)
|
||||
xor[y * cursor->width + x] = xorMask;
|
||||
}
|
||||
|
||||
egl_texture_setup (cursor->texture , EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
|
||||
egl_texture_setup (cursor->textureMono, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
|
||||
egl_texture_update(cursor->texture , (uint8_t *)and);
|
||||
egl_texture_update(cursor->textureMono, (uint8_t *)xor);
|
||||
egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
|
||||
egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
|
||||
egl_texture_update(cursor->norm.texture, (uint8_t *)and);
|
||||
egl_texture_update(cursor->mono.texture, (uint8_t *)xor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
}
|
||||
|
||||
cursor->rotate = rotate;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
egl_shader_use(cursor->shader);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
egl_shader_use(cursor->norm.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->norm, true);;
|
||||
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_model_set_texture(cursor->model, cursor->texture);
|
||||
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
||||
egl_model_render(cursor->model);
|
||||
|
||||
egl_shader_use(cursor->shaderMono);
|
||||
glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
egl_shader_use(cursor->mono.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->mono, true);;
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_set_texture(cursor->model, cursor->textureMono);
|
||||
egl_model_set_texture(cursor->model, cursor->mono.texture);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->shader);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
egl_shader_use(cursor->norm.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->norm, false);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
@@ -278,12 +300,12 @@ void egl_cursor_render(EGL_Cursor * cursor)
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->shaderMono);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
egl_shader_use(cursor->mono.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->mono, false);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,17 @@ typedef struct EGL_Cursor EGL_Cursor;
|
||||
bool egl_cursor_init(EGL_Cursor ** cursor);
|
||||
void egl_cursor_free(EGL_Cursor ** cursor);
|
||||
|
||||
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data);
|
||||
void egl_cursor_set_size (EGL_Cursor * cursor, const float x, const float y);
|
||||
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y);
|
||||
void egl_cursor_render (EGL_Cursor * cursor);
|
||||
bool egl_cursor_set_shape(
|
||||
EGL_Cursor * cursor,
|
||||
const LG_RendererCursor type,
|
||||
const int width,
|
||||
const int height,
|
||||
const int stride,
|
||||
const uint8_t * data);
|
||||
|
||||
void egl_cursor_set_size(EGL_Cursor * cursor, const float x, const float y);
|
||||
|
||||
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y);
|
||||
|
||||
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void egl_debug_printf(char * format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
|
||||
GLenum error = glGetError();
|
||||
switch(error)
|
||||
{
|
||||
case GL_NO_ERROR:
|
||||
fprintf(stderr, " (GL_NO_ERROR)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_ENUM:
|
||||
fprintf(stderr, " (GL_INVALID_ENUM)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
fprintf(stderr, " (GL_INVALID_VALUE)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_FRAMEBUFFER_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
fprintf(stderr, " (GL_OUT_OF_MEMORY)\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -20,8 +20,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "desktop.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "utils.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
@@ -29,48 +30,46 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "desktop.vert.h"
|
||||
#include "desktop_rgb.frag.h"
|
||||
#include "desktop_yuv.frag.h"
|
||||
|
||||
struct DesktopShader
|
||||
{
|
||||
EGL_Shader * shader;
|
||||
GLint uDesktopPos;
|
||||
GLint uDesktopSize;
|
||||
GLint uRotate;
|
||||
GLint uNearest;
|
||||
GLint uNV, uNVGain;
|
||||
GLint uCBMode;
|
||||
};
|
||||
|
||||
struct EGL_Desktop
|
||||
{
|
||||
EGLDisplay * display;
|
||||
|
||||
EGL_Texture * texture;
|
||||
struct DesktopShader * shader; // the active shader
|
||||
EGL_Model * model;
|
||||
|
||||
// internals
|
||||
int width, height;
|
||||
LG_RendererRotate rotate;
|
||||
|
||||
// shader instances
|
||||
struct DesktopShader shader_generic;
|
||||
struct DesktopShader shader_yuv;
|
||||
|
||||
// internals
|
||||
LG_Lock updateLock;
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
unsigned int width, height;
|
||||
unsigned int pitch;
|
||||
FrameBuffer frame;
|
||||
bool update;
|
||||
|
||||
// night vision
|
||||
KeybindHandle kbNV;
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
|
||||
// colorblind mode
|
||||
int cbMode;
|
||||
};
|
||||
|
||||
// forwards
|
||||
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque);
|
||||
void egl_desktop_toggle_nv(int key, void * opaque);
|
||||
|
||||
static bool egl_init_desktop_shader(
|
||||
struct DesktopShader * shader,
|
||||
@@ -90,14 +89,16 @@ static bool egl_init_desktop_shader(
|
||||
|
||||
shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position");
|
||||
shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" );
|
||||
shader->uRotate = egl_shader_get_uniform_location(shader->shader, "rotate" );
|
||||
shader->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" );
|
||||
shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" );
|
||||
shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" );
|
||||
shader->uCBMode = egl_shader_get_uniform_location(shader->shader, "cbMode" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop)
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display)
|
||||
{
|
||||
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
||||
if (!*desktop)
|
||||
@@ -107,8 +108,9 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
|
||||
}
|
||||
|
||||
memset(*desktop, 0, sizeof(EGL_Desktop));
|
||||
(*desktop)->display = display;
|
||||
|
||||
if (!egl_texture_init(&(*desktop)->texture))
|
||||
if (!egl_texture_init(&(*desktop)->texture, display))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop texture");
|
||||
return false;
|
||||
@@ -123,15 +125,6 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_init_desktop_shader(
|
||||
&(*desktop)->shader_yuv,
|
||||
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
||||
b_shader_desktop_yuv_frag, b_shader_desktop_yuv_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the yuv desktop shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_model_init(&(*desktop)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop model");
|
||||
@@ -141,18 +134,16 @@ bool egl_desktop_init(EGL_Desktop ** desktop)
|
||||
egl_model_set_default((*desktop)->model);
|
||||
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
|
||||
|
||||
LG_LOCK_INIT((*desktop)->updateLock);
|
||||
|
||||
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
|
||||
app_registerKeybind(KEY_N, egl_desktop_toggle_nv, *desktop, "Toggle night vision mode");
|
||||
|
||||
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
|
||||
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
|
||||
(*desktop)->cbMode = option_get_int("egl", "cbMode" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque)
|
||||
void egl_desktop_toggle_nv(int key, void * opaque)
|
||||
{
|
||||
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
||||
if (desktop->nvGain++ == desktop->nvMax)
|
||||
@@ -168,107 +159,115 @@ void egl_desktop_free(EGL_Desktop ** desktop)
|
||||
if (!*desktop)
|
||||
return;
|
||||
|
||||
LG_LOCK_FREE((*desktop)->updateLock);
|
||||
|
||||
egl_texture_free(&(*desktop)->texture );
|
||||
egl_shader_free (&(*desktop)->shader_generic.shader);
|
||||
egl_shader_free (&(*desktop)->shader_yuv.shader );
|
||||
egl_model_free (&(*desktop)->model );
|
||||
|
||||
app_release_keybind(&(*desktop)->kbNV);
|
||||
|
||||
free(*desktop);
|
||||
*desktop = NULL;
|
||||
}
|
||||
|
||||
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame)
|
||||
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA)
|
||||
{
|
||||
if (sourceChanged)
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
switch(format.type)
|
||||
{
|
||||
LG_LOCK(desktop->updateLock);
|
||||
switch(format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA:
|
||||
desktop->pixFmt = EGL_PF_BGRA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
case FRAME_TYPE_BGRA:
|
||||
pixFmt = EGL_PF_BGRA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA:
|
||||
desktop->pixFmt = EGL_PF_RGBA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
case FRAME_TYPE_RGBA:
|
||||
pixFmt = EGL_PF_RGBA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA10:
|
||||
desktop->pixFmt = EGL_PF_RGBA10;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
case FRAME_TYPE_RGBA10:
|
||||
pixFmt = EGL_PF_RGBA10;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_YUV420:
|
||||
desktop->pixFmt = EGL_PF_YUV420;
|
||||
desktop->shader = &desktop->shader_yuv;
|
||||
break;
|
||||
case FRAME_TYPE_RGBA16F:
|
||||
pixFmt = EGL_PF_RGBA16F;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format");
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
desktop->width = format.width;
|
||||
desktop->height = format.height;
|
||||
desktop->pitch = format.pitch;
|
||||
desktop->frame = frame;
|
||||
desktop->update = true;
|
||||
|
||||
/* defer the actual update as the format has changed and we need to issue GL commands first */
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
return true;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* update the texture now */
|
||||
return egl_texture_update_from_frame(desktop->texture, frame);
|
||||
desktop->width = format.width;
|
||||
desktop->height = format.height;
|
||||
|
||||
if (!egl_texture_setup(
|
||||
desktop->texture,
|
||||
pixFmt,
|
||||
format.width,
|
||||
format.height,
|
||||
format.pitch,
|
||||
true, // streaming texture
|
||||
useDMA
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the desktop texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd)
|
||||
{
|
||||
if (sourceChanged)
|
||||
if (dmaFd >= 0)
|
||||
{
|
||||
LG_LOCK(desktop->updateLock);
|
||||
if (!egl_texture_setup(
|
||||
desktop->texture,
|
||||
desktop->pixFmt,
|
||||
desktop->width,
|
||||
desktop->height,
|
||||
desktop->pitch,
|
||||
true // streaming texture
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the desktop texture");
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
return;
|
||||
}
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
if (!egl_texture_update_from_dma(desktop->texture, frame, dmaFd))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!egl_texture_update_from_frame(desktop->texture, frame))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (desktop->update)
|
||||
enum EGL_TexStatus status;
|
||||
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
|
||||
{
|
||||
desktop->update = false;
|
||||
egl_texture_update_from_frame(desktop->texture, desktop->frame);
|
||||
if (status != EGL_TEX_STATUS_NOTREADY)
|
||||
DEBUG_ERROR("Failed to process the desktop texture");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest)
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||
const float scaleX, const float scaleY, const bool nearest,
|
||||
LG_RendererRotate rotate)
|
||||
{
|
||||
if (!desktop->shader)
|
||||
return false;
|
||||
|
||||
if (egl_texture_process(desktop->texture) != EGL_TEX_STATUS_OK)
|
||||
return false;
|
||||
bool useNearest = nearest;
|
||||
if (!nearest)
|
||||
{
|
||||
switch(rotate)
|
||||
{
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
if (scaleX < 1.0f || scaleY < 1.0f)
|
||||
useNearest = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const struct DesktopShader * shader = desktop->shader;
|
||||
egl_shader_use(shader->shader);
|
||||
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
|
||||
glUniform1i(shader->uNearest , nearest ? 1 : 0);
|
||||
glUniform1i(shader->uRotate , rotate);
|
||||
glUniform1i(shader->uNearest , useNearest ? 1 : 0);
|
||||
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
|
||||
|
||||
if (desktop->nvGain)
|
||||
@@ -279,6 +278,7 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, con
|
||||
else
|
||||
glUniform1i(shader->uNV, 0);
|
||||
|
||||
glUniform1i(shader->uCBMode, desktop->cbMode);
|
||||
egl_model_render(desktop->model);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop);
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display);
|
||||
void egl_desktop_free(EGL_Desktop ** desktop);
|
||||
|
||||
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame);
|
||||
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);
|
||||
bool egl_desktop_setup (EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA);
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd);
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y,
|
||||
const float scaleX, const float scaleY, const bool nearest,
|
||||
LG_RendererRotate rotate);
|
||||
|
||||
@@ -22,16 +22,18 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/sysinfo.h"
|
||||
#include "utils.h"
|
||||
#include "common/time.h"
|
||||
#include "common/locking.h"
|
||||
#include "util.h"
|
||||
#include "dynamic/fonts.h"
|
||||
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <SDL2/SDL_egl.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "desktop.h"
|
||||
@@ -39,6 +41,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "fps.h"
|
||||
#include "splash.h"
|
||||
#include "alert.h"
|
||||
#include "help.h"
|
||||
|
||||
#define SPLASH_FADE_TIME 1000000
|
||||
#define ALERT_TIMEOUT 2000000
|
||||
@@ -46,10 +49,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
struct Options
|
||||
{
|
||||
bool vsync;
|
||||
bool doubleBuffer;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
bool dmaSupport;
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
@@ -57,16 +62,18 @@ struct Inst
|
||||
EGLDisplay display;
|
||||
EGLConfig configs;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
EGLContext context, frameContext;
|
||||
|
||||
EGL_Desktop * desktop; // the desktop
|
||||
EGL_Cursor * cursor; // the mouse cursor
|
||||
EGL_FPS * fps; // the fps display
|
||||
EGL_Splash * splash; // the splash screen
|
||||
EGL_Alert * alert; // the alert display
|
||||
EGL_Help * help; // the help display
|
||||
|
||||
LG_RendererFormat format;
|
||||
bool sourceChanged;
|
||||
bool formatValid;
|
||||
bool start;
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
@@ -75,8 +82,9 @@ struct Inst
|
||||
bool useCloseFlag;
|
||||
bool closeFlag;
|
||||
|
||||
int width, height;
|
||||
LG_RendererRect destRect;
|
||||
int width, height;
|
||||
LG_RendererRect destRect;
|
||||
LG_RendererRotate rotate; //client side rotation
|
||||
|
||||
float translateX , translateY;
|
||||
float scaleX , scaleY;
|
||||
@@ -91,9 +99,9 @@ struct Inst
|
||||
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
LG_FontObj helpFontObj;
|
||||
};
|
||||
|
||||
|
||||
static struct Option egl_options[] =
|
||||
{
|
||||
{
|
||||
@@ -101,14 +109,14 @@ static struct Option egl_options[] =
|
||||
.name = "vsync",
|
||||
.description = "Enable vsync",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "doubleBuffer",
|
||||
.description = "Enable double buffering",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
@@ -131,23 +139,34 @@ static struct Option egl_options[] =
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "cbMode",
|
||||
.description = "Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope)",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
void update_mouse_shape(struct Inst * this);
|
||||
|
||||
const char * egl_get_name()
|
||||
const char * egl_get_name(void)
|
||||
{
|
||||
return "EGL";
|
||||
}
|
||||
|
||||
void egl_setup()
|
||||
void egl_setup(void)
|
||||
{
|
||||
option_register(egl_options);
|
||||
}
|
||||
|
||||
bool egl_create(void ** opaque, const LG_RendererParams params)
|
||||
bool egl_create(void ** opaque, const LG_RendererParams params, bool * needsOpenGL)
|
||||
{
|
||||
// check if EGL is even available
|
||||
if (!eglQueryString(EGL_NO_DISPLAY, EGL_VERSION))
|
||||
return false;
|
||||
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
@@ -161,7 +180,8 @@ bool egl_create(void ** opaque, const LG_RendererParams params)
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
memcpy(&this->params, ¶ms, sizeof(LG_RendererParams));
|
||||
|
||||
this->opt.vsync = option_get_bool("egl", "vsync");
|
||||
this->opt.vsync = option_get_bool("egl", "vsync");
|
||||
this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer");
|
||||
|
||||
this->translateX = 0;
|
||||
this->translateY = 0;
|
||||
@@ -177,32 +197,20 @@ bool egl_create(void ** opaque, const LG_RendererParams params)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->font->create(&this->helpFontObj, NULL, 14))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create a font instance");
|
||||
return false;
|
||||
}
|
||||
|
||||
*needsOpenGL = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
bool egl_initialize(void * opaque)
|
||||
{
|
||||
const bool doubleBuffer = option_get_bool("egl", "doubleBuffer");
|
||||
DEBUG_INFO("Double buffering is %s", doubleBuffer ? "on" : "off");
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , doubleBuffer ? 1 : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
if (option_get_bool("egl", "multisample"))
|
||||
{
|
||||
int maxSamples = sysinfo_gfx_max_multisample();
|
||||
if (maxSamples > 1)
|
||||
{
|
||||
if (maxSamples > 4)
|
||||
maxSamples = 4;
|
||||
|
||||
DEBUG_INFO("Multsampling enabled, max samples: %d", maxSamples);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples);
|
||||
}
|
||||
}
|
||||
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -210,24 +218,147 @@ void egl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->font && this->fontObj)
|
||||
this->font->destroy(this->fontObj);
|
||||
if (this->font)
|
||||
{
|
||||
if (this->fontObj)
|
||||
this->font->destroy(this->fontObj);
|
||||
|
||||
if (this->helpFontObj)
|
||||
this->font->destroy(this->helpFontObj);
|
||||
}
|
||||
|
||||
|
||||
egl_desktop_free(&this->desktop);
|
||||
egl_cursor_free (&this->cursor);
|
||||
egl_fps_free (&this->fps );
|
||||
egl_splash_free (&this->splash);
|
||||
egl_alert_free (&this->alert );
|
||||
egl_help_free (&this->help);
|
||||
|
||||
LG_LOCK_FREE(this->lock);
|
||||
|
||||
eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (this->frameContext)
|
||||
eglDestroyContext(this->display, this->frameContext);
|
||||
|
||||
if (this->context)
|
||||
eglDestroyContext(this->display, this->context);
|
||||
|
||||
eglTerminate(this->display);
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
bool egl_supports(void * opaque, LG_RendererSupport flag)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
switch(flag)
|
||||
{
|
||||
case LG_SUPPORTS_DMABUF:
|
||||
return this->dmaSupport;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void egl_on_restart(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
eglDestroyContext(this->display, this->frameContext);
|
||||
this->frameContext = NULL;
|
||||
this->start = false;
|
||||
}
|
||||
|
||||
static void egl_calc_mouse_size(struct Inst * this)
|
||||
{
|
||||
if (!this->formatValid)
|
||||
return;
|
||||
|
||||
int w, h;
|
||||
switch(this->format.rotate)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
case LG_ROTATE_180:
|
||||
this->mouseScaleX = 2.0f / this->format.width;
|
||||
this->mouseScaleY = 2.0f / this->format.height;
|
||||
w = this->format.width;
|
||||
h = this->format.height;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
this->mouseScaleX = 2.0f / this->format.height;
|
||||
this->mouseScaleY = 2.0f / this->format.width;
|
||||
w = this->format.height;
|
||||
h = this->format.width;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"unreachable");
|
||||
}
|
||||
|
||||
switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
case LG_ROTATE_180:
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / w)) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / h)) * this->scaleY
|
||||
);
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / w)) * this->scaleY,
|
||||
(this->mouseHeight * (1.0f / h)) * this->scaleX
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void egl_calc_mouse_state(struct Inst * this)
|
||||
{
|
||||
if (!this->formatValid)
|
||||
return;
|
||||
|
||||
switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
case LG_ROTATE_180:
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
);
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void egl_on_resize(void * opaque, const int width, const int height,
|
||||
const LG_RendererRect destRect, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->rotate = rotate;
|
||||
|
||||
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
||||
|
||||
glViewport(0, 0, width, height);
|
||||
@@ -240,28 +371,21 @@ void egl_on_resize(void * opaque, const int width, const int height, const LG_Re
|
||||
this->scaleY = (float)destRect.h / (float)height;
|
||||
}
|
||||
|
||||
this->mouseScaleX = 2.0f / this->format.width ;
|
||||
this->mouseScaleY = 2.0f / this->format.height;
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
|
||||
);
|
||||
egl_calc_mouse_size(this);
|
||||
|
||||
this->splashRatio = (float)width / (float)height;
|
||||
this->screenScaleX = 1.0f / width;
|
||||
this->screenScaleY = 1.0f / height;
|
||||
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
);
|
||||
egl_calc_mouse_state(this);
|
||||
}
|
||||
|
||||
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
|
||||
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor,
|
||||
const int width, const int height,
|
||||
const int pitch, const uint8_t * data)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (!egl_cursor_set_shape(this->cursor, cursor, width, height, pitch, data))
|
||||
{
|
||||
DEBUG_ERROR("Failed to update the cursor shape");
|
||||
@@ -270,10 +394,7 @@ bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int
|
||||
|
||||
this->mouseWidth = width;
|
||||
this->mouseHeight = height;
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
|
||||
);
|
||||
egl_calc_mouse_size(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -284,39 +405,52 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
|
||||
this->cursorVisible = visible;
|
||||
this->cursorX = x;
|
||||
this->cursorY = y;
|
||||
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
);
|
||||
|
||||
egl_calc_mouse_state(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
|
||||
bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->sourceChanged = (
|
||||
this->sourceChanged ||
|
||||
this->format.type != format.type ||
|
||||
this->format.width != format.width ||
|
||||
this->format.height != format.height ||
|
||||
this->format.pitch != format.pitch
|
||||
);
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->formatValid = true;
|
||||
|
||||
if (this->sourceChanged)
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
/* this event runs in a second thread so we need to init it here */
|
||||
if (!this->frameContext)
|
||||
{
|
||||
static EGLint attrs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext))
|
||||
{
|
||||
DEBUG_ERROR("Failed to make the frame context current");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->useNearest = this->width < format.width || this->height < format.height;
|
||||
return egl_desktop_setup(this->desktop, format, useDMA);
|
||||
}
|
||||
|
||||
if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, frame))
|
||||
bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (!egl_desktop_update(this->desktop, frame, dmaFd))
|
||||
{
|
||||
DEBUG_INFO("Failed to prepare to update the desktop");
|
||||
DEBUG_INFO("Failed to to update the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->start = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -355,61 +489,55 @@ void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message,
|
||||
this->showAlert = true;
|
||||
}
|
||||
|
||||
bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
void egl_on_help(void * opaque, const char * message)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
egl_help_set_text(this->help, message);
|
||||
}
|
||||
|
||||
void egl_on_show_fps(void * opaque, bool showFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
egl_fps_set_display(this->fps, showFPS);
|
||||
}
|
||||
|
||||
bool egl_render_startup(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
this->nativeWind = app_getEGLNativeWindow();
|
||||
if (!this->nativeWind)
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
{
|
||||
this->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, wminfo.info.x11.display, NULL);
|
||||
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
int width, height;
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
this->display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wminfo.info.wl.display, NULL);
|
||||
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->display = app_getEGLDisplay();
|
||||
if (this->display == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("eglGetDisplay failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eglInitialize(this->display, NULL, NULL))
|
||||
int maj, min;
|
||||
if (!eglInitialize(this->display, &maj, &min))
|
||||
{
|
||||
DEBUG_ERROR("Unable to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxSamples = 1;
|
||||
if (option_get_bool("egl", "multisample"))
|
||||
{
|
||||
if (app_getProp(LG_DS_MAX_MULTISAMPLE, &maxSamples) && maxSamples > 1)
|
||||
{
|
||||
if (maxSamples > 4)
|
||||
maxSamples = 4;
|
||||
|
||||
DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples);
|
||||
}
|
||||
}
|
||||
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 32,
|
||||
EGL_BUFFER_SIZE , 24,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SAMPLE_BUFFERS , 1,
|
||||
EGL_SAMPLES , 4,
|
||||
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
|
||||
EGL_SAMPLES , maxSamples,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
@@ -420,7 +548,13 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
return false;
|
||||
}
|
||||
|
||||
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, NULL);
|
||||
const EGLint surfattr[] =
|
||||
{
|
||||
EGL_RENDER_BUFFER, this->opt.doubleBuffer ? EGL_BACK_BUFFER : EGL_SINGLE_BUFFER,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, surfattr);
|
||||
if (this->surface == EGL_NO_SURFACE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
||||
@@ -440,15 +574,60 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
return false;
|
||||
}
|
||||
|
||||
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
|
||||
EGLint rb = 0;
|
||||
eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb);
|
||||
switch(rb)
|
||||
{
|
||||
case EGL_SINGLE_BUFFER:
|
||||
DEBUG_INFO("Single buffer mode");
|
||||
break;
|
||||
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
case EGL_BACK_BUFFER:
|
||||
DEBUG_INFO("Back buffer mode");
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_WARN("Unknown render buffer mode: %d", rb);
|
||||
break;
|
||||
}
|
||||
|
||||
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
|
||||
const char *client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
|
||||
const char *vendor = (const char *)glGetString(GL_VENDOR);
|
||||
|
||||
DEBUG_INFO("EGL : %d.%d", maj, min);
|
||||
DEBUG_INFO("Vendor : %s", vendor);
|
||||
DEBUG_INFO("Renderer : %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
DEBUG_INFO("EGL APIs : %s", eglQueryString(this->display, EGL_CLIENT_APIS));
|
||||
DEBUG_INFO("Extensions: %s", client_exts);
|
||||
|
||||
if (g_egl_dynProcs.glEGLImageTargetTexture2DOES)
|
||||
{
|
||||
if (strstr(client_exts, "EGL_EXT_image_dma_buf_import") != NULL)
|
||||
{
|
||||
/*
|
||||
* As of version 455.45.01 NVidia started advertising support for this
|
||||
* feature, however even on the latest version 460.27.04 this is still
|
||||
* broken and does not work, until this is fixed and we have way to detect
|
||||
* this early just disable dma for all NVIDIA devices.
|
||||
*
|
||||
* ref: https://forums.developer.nvidia.com/t/egl-ext-image-dma-buf-import-broken-egl-bad-alloc-with-tons-of-free-ram/165552
|
||||
*/
|
||||
if (strstr(vendor, "NVIDIA") != NULL)
|
||||
DEBUG_WARN("NVIDIA driver detected, ignoring broken DMA support");
|
||||
else
|
||||
this->dmaSupport = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled");
|
||||
}
|
||||
|
||||
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
||||
|
||||
if (!egl_desktop_init(&this->desktop))
|
||||
if (!egl_desktop_init(&this->desktop, this->display))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop");
|
||||
return false;
|
||||
@@ -478,21 +657,38 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_help_init(&this->help, this->font, this->helpFontObj))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert display");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_render(void * opaque, SDL_Window * window)
|
||||
bool egl_render(void * opaque, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
|
||||
if (this->start && egl_desktop_render(this->desktop,
|
||||
this->translateX, this->translateY,
|
||||
this->scaleX , this->scaleY ,
|
||||
this->useNearest,
|
||||
rotate))
|
||||
{
|
||||
if (!this->waitFadeTime)
|
||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||
egl_cursor_render(this->cursor);
|
||||
{
|
||||
if (!this->params.quickSplash)
|
||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||
else
|
||||
this->waitDone = true;
|
||||
}
|
||||
|
||||
egl_cursor_render(this->cursor,
|
||||
(this->format.rotate + rotate) % LG_ROTATE_MAX);
|
||||
}
|
||||
|
||||
if (!this->waitDone)
|
||||
@@ -515,6 +711,11 @@ bool egl_render(void * opaque, SDL_Window * window)
|
||||
if (!this->waitDone)
|
||||
egl_splash_render(this->splash, a, this->splashRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this->start)
|
||||
egl_splash_render(this->splash, 1.0f, this->splashRatio);
|
||||
}
|
||||
|
||||
if (this->showAlert)
|
||||
{
|
||||
@@ -531,37 +732,35 @@ bool egl_render(void * opaque, SDL_Window * window)
|
||||
}
|
||||
|
||||
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
|
||||
eglSwapBuffers(this->display, this->surface);
|
||||
|
||||
// defer texture uploads until after the flip to avoid stalling
|
||||
egl_desktop_perform_update(this->desktop, this->sourceChanged);
|
||||
|
||||
this->sourceChanged = false;
|
||||
egl_help_render(this->help, this->screenScaleX, this->screenScaleY);
|
||||
app_eglSwapBuffers(this->display, this->surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->params.showFPS)
|
||||
return;
|
||||
|
||||
egl_fps_update(this->fps, avgUPS, avgFPS);
|
||||
}
|
||||
|
||||
struct LG_Renderer LGR_EGL =
|
||||
{
|
||||
.get_name = egl_get_name,
|
||||
.setup = egl_setup,
|
||||
.create = egl_create,
|
||||
.initialize = egl_initialize,
|
||||
.deinitialize = egl_deinitialize,
|
||||
.on_resize = egl_on_resize,
|
||||
.on_mouse_shape = egl_on_mouse_shape,
|
||||
.on_mouse_event = egl_on_mouse_event,
|
||||
.on_frame_event = egl_on_frame_event,
|
||||
.on_alert = egl_on_alert,
|
||||
.render_startup = egl_render_startup,
|
||||
.render = egl_render,
|
||||
.update_fps = egl_update_fps
|
||||
};
|
||||
.get_name = egl_get_name,
|
||||
.setup = egl_setup,
|
||||
.create = egl_create,
|
||||
.initialize = egl_initialize,
|
||||
.deinitialize = egl_deinitialize,
|
||||
.supports = egl_supports,
|
||||
.on_restart = egl_on_restart,
|
||||
.on_resize = egl_on_resize,
|
||||
.on_mouse_shape = egl_on_mouse_shape,
|
||||
.on_mouse_event = egl_on_mouse_event,
|
||||
.on_frame_format = egl_on_frame_format,
|
||||
.on_frame = egl_on_frame,
|
||||
.on_alert = egl_on_alert,
|
||||
.on_help = egl_on_help,
|
||||
.on_show_fps = egl_on_show_fps,
|
||||
.render_startup = egl_render_startup,
|
||||
.render = egl_render,
|
||||
.update_fps = egl_update_fps
|
||||
};
|
||||
|
||||
45
client/renderers/EGL/egldebug.c
Normal file
45
client/renderers/EGL/egldebug.c
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "egldebug.h"
|
||||
#include <GL/gl.h>
|
||||
#include <EGL/egl.h>
|
||||
|
||||
const char * egl_getErrorStr(void)
|
||||
{
|
||||
switch (eglGetError())
|
||||
{
|
||||
case EGL_SUCCESS : return "EGL_SUCCESS";
|
||||
case EGL_NOT_INITIALIZED : return "EGL_NOT_INITIALIZED";
|
||||
case EGL_BAD_ACCESS : return "EGL_BAD_ACCESS";
|
||||
case EGL_BAD_ALLOC : return "EGL_BAD_ALLOC";
|
||||
case EGL_BAD_ATTRIBUTE : return "EGL_BAD_ATTRIBUTE";
|
||||
case EGL_BAD_CONTEXT : return "EGL_BAD_CONTEXT";
|
||||
case EGL_BAD_CONFIG : return "EGL_BAD_CONFIG";
|
||||
case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
|
||||
case EGL_BAD_DISPLAY : return "EGL_BAD_DISPLAY";
|
||||
case EGL_BAD_SURFACE : return "EGL_BAD_SURFACE";
|
||||
case EGL_BAD_MATCH : return "EGL_BAD_MATCH";
|
||||
case EGL_BAD_PARAMETER : return "EGL_BAD_PARAMETER";
|
||||
case EGL_BAD_NATIVE_PIXMAP : return "EGL_BAD_NATIVE_PIXMAP";
|
||||
case EGL_BAD_NATIVE_WINDOW : return "EGL_BAD_NATIVE_WINDOW";
|
||||
case EGL_CONTEXT_LOST : return "EGL_CONTEXT_LOST";
|
||||
default : return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,14 +17,13 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct spice_password
|
||||
{
|
||||
char * data;
|
||||
unsigned int size;
|
||||
};
|
||||
#include "common/debug.h"
|
||||
|
||||
bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result);
|
||||
void spice_rsa_free_password(struct spice_password * pass);
|
||||
const char * egl_getErrorStr(void);
|
||||
|
||||
#define DEBUG_EGL_WARN(fmt, ...) \
|
||||
DEBUG_WARN(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr())
|
||||
|
||||
#define DEBUG_EGL_ERROR(fmt, ...) \
|
||||
DEBUG_ERROR(fmt " (%s)", ##__VA_ARGS__, egl_getErrorStr())
|
||||
@@ -19,7 +19,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "fps.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
@@ -43,7 +42,9 @@ struct EGL_FPS
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
bool display;
|
||||
bool ready;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
@@ -65,7 +66,7 @@ bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj)
|
||||
(*fps)->font = font;
|
||||
(*fps)->fontObj = fontObj;
|
||||
|
||||
if (!egl_texture_init(&(*fps)->texture))
|
||||
if (!egl_texture_init(&(*fps)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps texture");
|
||||
return false;
|
||||
@@ -132,8 +133,16 @@ void egl_fps_free(EGL_FPS ** fps)
|
||||
*fps = NULL;
|
||||
}
|
||||
|
||||
void egl_fps_set_display(EGL_FPS * fps, bool display)
|
||||
{
|
||||
fps->display = display;
|
||||
}
|
||||
|
||||
void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
{
|
||||
if (!fps->display)
|
||||
return;
|
||||
|
||||
char str[128];
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
|
||||
@@ -144,14 +153,23 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
return;
|
||||
}
|
||||
|
||||
egl_texture_setup(
|
||||
fps->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false
|
||||
);
|
||||
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
|
||||
{
|
||||
fps->iwidth = bmp->width;
|
||||
fps->iheight = bmp->height;
|
||||
fps->width = (float)bmp->width;
|
||||
fps->height = (float)bmp->height;
|
||||
|
||||
egl_texture_setup(
|
||||
fps->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
@@ -159,16 +177,13 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
fps->width = bmp->width;
|
||||
fps->height = bmp->height;
|
||||
fps->ready = true;
|
||||
|
||||
fps->font->release(fps->fontObj, bmp);
|
||||
}
|
||||
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (!fps->ready)
|
||||
if (!fps->display || !fps->ready)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
@@ -187,4 +202,4 @@ void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
egl_model_render(fps->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@ typedef struct EGL_FPS EGL_FPS;
|
||||
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_fps_free(EGL_FPS ** fps);
|
||||
|
||||
void egl_fps_set_display(EGL_FPS * fps, bool display);
|
||||
void egl_fps_update(EGL_FPS * fps, const float avgUPS, const float avgFPS);
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY);
|
||||
209
client/renderers/EGL/help.c
Normal file
209
client/renderers/EGL/help.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen <quantum2048@gmail.com>
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "help.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "help.vert.h"
|
||||
#include "help.frag.h"
|
||||
#include "help_bg.frag.h"
|
||||
|
||||
struct EGL_Help
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
_Atomic(LG_FontBitmap *) bmp;
|
||||
|
||||
bool shouldRender;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG;
|
||||
};
|
||||
|
||||
bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*help = (EGL_Help *)malloc(sizeof(EGL_Help));
|
||||
if (!*help)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Help");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*help, 0, sizeof(EGL_Help));
|
||||
|
||||
(*help)->font = font;
|
||||
(*help)->fontObj = fontObj;
|
||||
|
||||
if (!egl_texture_init(&(*help)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*help)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*help)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the help bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*help)->shader,
|
||||
b_shader_help_vert, b_shader_help_vert_size,
|
||||
b_shader_help_frag, b_shader_help_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*help)->shaderBG,
|
||||
b_shader_help_vert , b_shader_help_vert_size,
|
||||
b_shader_help_bg_frag, b_shader_help_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the help shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*help)->uSize = egl_shader_get_uniform_location((*help)->shader , "size" );
|
||||
(*help)->uScreen = egl_shader_get_uniform_location((*help)->shader , "screen");
|
||||
(*help)->uSizeBG = egl_shader_get_uniform_location((*help)->shaderBG, "size" );
|
||||
(*help)->uScreenBG = egl_shader_get_uniform_location((*help)->shaderBG, "screen");
|
||||
|
||||
if (!egl_model_init(&(*help)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*help)->model);
|
||||
egl_model_set_texture((*help)->model, (*help)->texture);
|
||||
|
||||
atomic_init(&(*help)->bmp, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_help_free(EGL_Help ** help)
|
||||
{
|
||||
if (!*help)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*help)->texture );
|
||||
egl_shader_free (&(*help)->shader );
|
||||
egl_shader_free (&(*help)->shaderBG);
|
||||
egl_model_free (&(*help)->model );
|
||||
|
||||
free(*help);
|
||||
*help = NULL;
|
||||
}
|
||||
|
||||
void egl_help_set_text(EGL_Help * help, const char * help_text)
|
||||
{
|
||||
LG_FontBitmap * bmp = NULL;
|
||||
if (help_text)
|
||||
{
|
||||
bmp = help->font->render(help->fontObj, 0xffffff00, help_text);
|
||||
if (!bmp)
|
||||
DEBUG_ERROR("Failed to render help text");
|
||||
} else
|
||||
help->shouldRender = false;
|
||||
|
||||
bmp = atomic_exchange(&help->bmp, bmp);
|
||||
if (bmp)
|
||||
{
|
||||
help->font->release(help->fontObj, bmp);
|
||||
}
|
||||
}
|
||||
|
||||
void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY)
|
||||
{
|
||||
LG_FontBitmap * bmp = atomic_exchange(&help->bmp, NULL);
|
||||
if (bmp)
|
||||
{
|
||||
if (help->iwidth != bmp->width || help->iheight != bmp->height)
|
||||
{
|
||||
help->iwidth = bmp->width;
|
||||
help->iheight = bmp->height;
|
||||
help->width = (float)bmp->width;
|
||||
help->height = (float)bmp->height;
|
||||
|
||||
egl_texture_setup(
|
||||
help->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
help->texture,
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
help->shouldRender = true;
|
||||
help->font->release(help->fontObj, bmp);
|
||||
}
|
||||
|
||||
if (!help->shouldRender)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(help->shaderBG);
|
||||
glUniform2f(help->uScreenBG, scaleX , scaleY );
|
||||
glUniform2f(help->uSizeBG , help->width, help->height);
|
||||
egl_model_render(help->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(help->shader);
|
||||
glUniform2f(help->uScreen, scaleX , scaleY );
|
||||
glUniform2f(help->uSize , help->width, help->height);
|
||||
egl_model_render(help->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
32
client/renderers/EGL/help.h
Normal file
32
client/renderers/EGL/help.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2021 Guanzhong Chen <quantum2048@gmail.com>
|
||||
https://looking-glass.io
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
|
||||
typedef struct EGL_Help EGL_Help;
|
||||
|
||||
bool egl_help_init(EGL_Help ** help, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_help_free(EGL_Help ** help);
|
||||
|
||||
void egl_help_set_text(EGL_Help * help, const char * help_text);
|
||||
void egl_help_render(EGL_Help * help, const float scaleX, const float scaleY);
|
||||
@@ -22,7 +22,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -30,8 +29,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
struct EGL_Model
|
||||
{
|
||||
bool rebuild;
|
||||
@@ -216,4 +213,4 @@ void update_uniform_bindings(EGL_Model * model)
|
||||
|
||||
const int count = egl_texture_count(model->texture);
|
||||
egl_shader_associate_textures(model->shader, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "shader.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
struct EGL_Shader
|
||||
{
|
||||
bool hasShader;
|
||||
@@ -63,7 +61,7 @@ bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * f
|
||||
char * vertex_code, * fragment_code;
|
||||
size_t vertex_size, fragment_size;
|
||||
|
||||
if (!file_get_contents(vertex_file, &vertex_code, &vertex_size))
|
||||
if (!util_fileGetContents(vertex_file, &vertex_code, &vertex_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read vertex shader");
|
||||
return false;
|
||||
@@ -71,7 +69,7 @@ bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * f
|
||||
|
||||
DEBUG_INFO("Loaded vertex shader: %s", vertex_file);
|
||||
|
||||
if (!file_get_contents(fragment_file, &fragment_code, &fragment_size))
|
||||
if (!util_fileGetContents(fragment_file, &fragment_code, &fragment_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read fragment shader");
|
||||
free(vertex_code);
|
||||
@@ -222,4 +220,4 @@ GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name)
|
||||
}
|
||||
|
||||
return glGetUniformLocation(this->shader, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,40 @@ layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec4 mouse;
|
||||
uniform lowp int rotate;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
vec2 muv = vertexPosition_modelspace.xy;
|
||||
muv.x += 1.0f;
|
||||
muv.y -= 1.0f;
|
||||
muv.x *= mouse.z;
|
||||
muv.y *= mouse.w;
|
||||
muv.x += mouse.x;
|
||||
muv.y -= mouse.y;
|
||||
|
||||
gl_Position.x += 1.0f;
|
||||
gl_Position.y -= 1.0f;
|
||||
|
||||
gl_Position.x *= mouse.z;
|
||||
gl_Position.y *= mouse.w;
|
||||
|
||||
gl_Position.x += mouse.x;
|
||||
gl_Position.y -= mouse.y;
|
||||
if (rotate == 0) // 0
|
||||
{
|
||||
gl_Position.xy = muv;
|
||||
}
|
||||
else if (rotate == 1) // 90
|
||||
{
|
||||
gl_Position.x = muv.y;
|
||||
gl_Position.y = -muv.x;
|
||||
}
|
||||
else if (rotate == 2) // 180
|
||||
{
|
||||
gl_Position.x = -muv.x;
|
||||
gl_Position.y = -muv.y;
|
||||
}
|
||||
else if (rotate == 3) // 270
|
||||
{
|
||||
gl_Position.x = -muv.y;
|
||||
gl_Position.y = muv.x;
|
||||
}
|
||||
|
||||
gl_Position.w = 1.0;
|
||||
uv = vertexUV;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,47 @@ out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
uniform lowp int rotate;
|
||||
uniform int cbMode;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, uv);
|
||||
|
||||
if (cbMode > 0)
|
||||
{
|
||||
highp float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b);
|
||||
highp float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b);
|
||||
highp float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b);
|
||||
highp float l, m, s;
|
||||
|
||||
if (cbMode == 1) // Protanope
|
||||
{
|
||||
l = 0.0f * L + 2.02344f * M + -2.52581f * S;
|
||||
m = 0.0f * L + 1.0f * M + 0.0f * S;
|
||||
s = 0.0f * L + 0.0f * M + 1.0f * S;
|
||||
}
|
||||
else if (cbMode == 2) // Deuteranope
|
||||
{
|
||||
l = 1.000000 * L + 0.0f * M + 0.00000 * S;
|
||||
m = 0.494207 * L + 0.0f * M + 1.24827 * S;
|
||||
s = 0.000000 * L + 0.0f * M + 1.00000 * S;
|
||||
}
|
||||
else if (cbMode == 3) // Tritanope
|
||||
{
|
||||
l = 1.000000 * L + 0.000000 * M + 0.0 * S;
|
||||
m = 0.000000 * L + 1.000000 * M + 0.0 * S;
|
||||
s = -0.395913 * L + 0.801109 * M + 0.0 * S;
|
||||
}
|
||||
|
||||
highp vec4 error;
|
||||
error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s);
|
||||
error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s);
|
||||
error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s);
|
||||
error.a = 0.0;
|
||||
|
||||
error = color - error;
|
||||
color.g += (error.r * 0.7) + (error.g * 1.0);
|
||||
color.b += (error.r * 0.7) + (error.b * 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,76 @@ uniform sampler2D sampler1;
|
||||
|
||||
uniform int nearest;
|
||||
uniform highp vec2 size;
|
||||
uniform int rotate;
|
||||
|
||||
uniform int nv;
|
||||
uniform highp float nvGain;
|
||||
uniform int cbMode;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec2 ruv;
|
||||
if (rotate == 0) // 0
|
||||
{
|
||||
ruv = uv;
|
||||
}
|
||||
else if (rotate == 1) // 90
|
||||
{
|
||||
ruv.x = uv.y;
|
||||
ruv.y = -uv.x + 1.0f;
|
||||
}
|
||||
else if (rotate == 2) // 180
|
||||
{
|
||||
ruv.x = -uv.x + 1.0f;
|
||||
ruv.y = -uv.y + 1.0f;
|
||||
}
|
||||
else if (rotate == 3) // 270
|
||||
{
|
||||
ruv.x = -uv.y + 1.0f;
|
||||
ruv.y = uv.x;
|
||||
}
|
||||
|
||||
if(nearest == 1)
|
||||
color = texture(sampler1, uv);
|
||||
color = texture(sampler1, ruv);
|
||||
else
|
||||
color = texelFetch(sampler1, ivec2(uv * size), 0);
|
||||
color = texelFetch(sampler1, ivec2(ruv * size), 0);
|
||||
|
||||
if (cbMode > 0)
|
||||
{
|
||||
highp float L = (17.8824000 * color.r) + (43.516100 * color.g) + (4.11935 * color.b);
|
||||
highp float M = (03.4556500 * color.r) + (27.155400 * color.g) + (3.86714 * color.b);
|
||||
highp float S = (00.0299566 * color.r) + (00.184309 * color.g) + (1.46709 * color.b);
|
||||
highp float l, m, s;
|
||||
|
||||
if (cbMode == 1) // Protanope
|
||||
{
|
||||
l = 0.0f * L + 2.02344f * M + -2.52581f * S;
|
||||
m = 0.0f * L + 1.0f * M + 0.0f * S;
|
||||
s = 0.0f * L + 0.0f * M + 1.0f * S;
|
||||
}
|
||||
else if (cbMode == 2) // Deuteranope
|
||||
{
|
||||
l = 1.000000 * L + 0.0f * M + 0.00000 * S;
|
||||
m = 0.494207 * L + 0.0f * M + 1.24827 * S;
|
||||
s = 0.000000 * L + 0.0f * M + 1.00000 * S;
|
||||
}
|
||||
else if (cbMode == 3) // Tritanope
|
||||
{
|
||||
l = 1.000000 * L + 0.000000 * M + 0.0 * S;
|
||||
m = 0.000000 * L + 1.000000 * M + 0.0 * S;
|
||||
s = -0.395913 * L + 0.801109 * M + 0.0 * S;
|
||||
}
|
||||
|
||||
highp vec4 error;
|
||||
error.r = ( 0.080944447900 * l) + (-0.13050440900 * m) + ( 0.116721066 * s);
|
||||
error.g = (-0.010248533500 * l) + ( 0.05401932660 * m) + (-0.113614708 * s);
|
||||
error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + ( 0.693511405 * s);
|
||||
error.a = 0.0;
|
||||
|
||||
error = color - error;
|
||||
color.g += (error.r * 0.7) + (error.g * 1.0);
|
||||
color.b += (error.r * 0.7) + (error.b * 1.0);
|
||||
}
|
||||
|
||||
if (nv == 1)
|
||||
{
|
||||
@@ -24,4 +84,6 @@ void main()
|
||||
color *= 1.0 + lumi;
|
||||
color *= nvGain;
|
||||
}
|
||||
|
||||
color.a = 1.0;
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform int nearest;
|
||||
uniform highp vec2 size;
|
||||
|
||||
uniform int nv;
|
||||
uniform highp float nvGain;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform sampler2D sampler2;
|
||||
uniform sampler2D sampler3;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec4 yuv;
|
||||
if(nearest == 1)
|
||||
{
|
||||
yuv = vec4(
|
||||
texture(sampler1, uv).r,
|
||||
texture(sampler2, uv).r,
|
||||
texture(sampler3, uv).r,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
highp ivec2 px = ivec2(uv * size);
|
||||
yuv = vec4(
|
||||
texelFetch(sampler1, px, 0).r,
|
||||
texelFetch(sampler2, px, 0).r,
|
||||
texelFetch(sampler3, px, 0).r,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
||||
highp mat4 yuv_to_rgb = mat4(
|
||||
1.0, 0.0 , 1.402, -0.701,
|
||||
1.0, -0.344, -0.714, 0.529,
|
||||
1.0, 1.772, 0.0 , -0.886,
|
||||
1.0, 1.0 , 1.0 , 1.0
|
||||
);
|
||||
|
||||
color = yuv * yuv_to_rgb;
|
||||
if (nv == 1)
|
||||
{
|
||||
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
|
||||
color *= 1.0 + lumi;
|
||||
color *= nvGain;
|
||||
}
|
||||
}
|
||||
11
client/renderers/EGL/shader/help.frag
Normal file
11
client/renderers/EGL/shader/help.frag
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, uv);
|
||||
}
|
||||
22
client/renderers/EGL/shader/help.vert
Normal file
22
client/renderers/EGL/shader/help.vert
Normal file
@@ -0,0 +1,22 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec2 screen;
|
||||
uniform vec2 size;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.xy *= screen.xy * size.xy;
|
||||
gl_Position.x -= 1.0 - (screen.x * size.x);
|
||||
gl_Position.y -= 1.0 - (screen.y * size.y);
|
||||
gl_Position.x += screen.x * 10.0;
|
||||
gl_Position.y += screen.y * 10.0;
|
||||
|
||||
uv = vertexUV;
|
||||
}
|
||||
8
client/renderers/EGL/shader/help_bg.frag
Normal file
8
client/renderers/EGL/shader/help_bg.frag
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(0.0, 0.0, 1.0, 0.5);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec3 pos;
|
||||
in highp float a;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
@@ -9,5 +8,5 @@ uniform sampler2D sampler1;
|
||||
void main()
|
||||
{
|
||||
highp float d = 1.0 - sqrt(pos.x * pos.x + pos.y * pos.y) / 2.0;
|
||||
color = vec4(0.234375 * d, 0.015625f * d, 0.425781f * d, a);
|
||||
color = vec4(0.234375 * d, 0.015625f * d, 0.425781f * d, 1);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform float alpha;
|
||||
|
||||
out highp vec3 pos;
|
||||
out highp float a;
|
||||
|
||||
@@ -13,5 +11,4 @@ void main()
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
pos = vertexPosition_modelspace;
|
||||
a = alpha;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
in highp float a;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(1.0, 1.0, 1.0, a);
|
||||
color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform vec2 scale;
|
||||
|
||||
out highp float a;
|
||||
uniform float scale;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.y *= scale.y;
|
||||
gl_Position.y *= scale;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
a = scale.x;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "splash.h"
|
||||
#include "common/debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "draw.h"
|
||||
#include "texture.h"
|
||||
@@ -46,7 +45,6 @@ struct EGL_Splash
|
||||
EGL_Model * logo;
|
||||
|
||||
// uniforms
|
||||
GLint uBGAlpha;
|
||||
GLint uScale;
|
||||
};
|
||||
|
||||
@@ -75,8 +73,6 @@ bool egl_splash_init(EGL_Splash ** splash)
|
||||
return false;
|
||||
}
|
||||
|
||||
(*splash)->uBGAlpha = egl_shader_get_uniform_location((*splash)->bgShader, "alpha");
|
||||
|
||||
if (!egl_model_init(&(*splash)->bg))
|
||||
{
|
||||
DEBUG_ERROR("Failed to intiailize the splash bg model");
|
||||
@@ -154,8 +150,12 @@ void egl_splash_free(EGL_Splash ** splash)
|
||||
if (!*splash)
|
||||
return;
|
||||
|
||||
egl_model_free(&(*splash)->bg );
|
||||
egl_model_free(&(*splash)->logo);
|
||||
|
||||
egl_shader_free(&(*splash)->bgShader );
|
||||
egl_shader_free(&(*splash)->logoShader);
|
||||
|
||||
free(*splash);
|
||||
*splash = NULL;
|
||||
}
|
||||
@@ -163,15 +163,15 @@ void egl_splash_free(EGL_Splash ** splash)
|
||||
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY)
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendColor(0, 0, 0, alpha);
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
|
||||
egl_shader_use(splash->bgShader);
|
||||
glUniform1f(splash->uBGAlpha, alpha);
|
||||
egl_model_render(splash->bg);
|
||||
|
||||
egl_shader_use(splash->logoShader);
|
||||
glUniform2f(splash->uScale, alpha, scaleY);
|
||||
glUniform1f(splash->uScale, scaleY);
|
||||
egl_model_render(splash->logo);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,44 +19,79 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "texture.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include "debug.h"
|
||||
#include "utils.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "egldebug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
/**
|
||||
* the following comes from drm_fourcc.h and is included here to avoid the
|
||||
* external dependency for the few simple defines we need
|
||||
*/
|
||||
#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \
|
||||
((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
|
||||
#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4')
|
||||
#define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4')
|
||||
#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0')
|
||||
#define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H')
|
||||
|
||||
/* this must be a multiple of 2 */
|
||||
#define BUFFER_COUNT 4
|
||||
|
||||
struct Buffer
|
||||
{
|
||||
bool hasPBO;
|
||||
GLuint pbo;
|
||||
void * map;
|
||||
GLsync sync;
|
||||
};
|
||||
|
||||
struct BufferState
|
||||
{
|
||||
_Atomic(uint8_t) w, u, s, d;
|
||||
};
|
||||
|
||||
struct EGL_Texture
|
||||
{
|
||||
EGLDisplay * display;
|
||||
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
size_t width, height;
|
||||
size_t bpp;
|
||||
bool streaming;
|
||||
bool dma;
|
||||
bool ready;
|
||||
|
||||
int textureCount;
|
||||
GLuint textures[3];
|
||||
GLuint samplers[3];
|
||||
size_t planes[3][3];
|
||||
GLintptr offsets[3];
|
||||
GLenum intFormat;
|
||||
GLenum format;
|
||||
GLenum dataType;
|
||||
|
||||
bool hasPBO;
|
||||
GLuint pbo[2];
|
||||
int pboRIndex;
|
||||
int pboWIndex;
|
||||
volatile int pboCount;
|
||||
GLuint sampler;
|
||||
size_t width, height, stride, pitch;
|
||||
GLenum intFormat;
|
||||
GLenum format;
|
||||
GLenum dataType;
|
||||
unsigned int fourcc;
|
||||
size_t pboBufferSize;
|
||||
void * pboMap[2];
|
||||
GLsync pboSync[2];
|
||||
|
||||
struct BufferState state;
|
||||
int bufferCount;
|
||||
GLuint tex;
|
||||
struct Buffer buf[BUFFER_COUNT];
|
||||
|
||||
size_t dmaImageCount;
|
||||
size_t dmaImageUsed;
|
||||
struct
|
||||
{
|
||||
int fd;
|
||||
EGLImage image;
|
||||
}
|
||||
* dmaImages;
|
||||
|
||||
GLuint dmaFBO;
|
||||
GLuint dmaTex;
|
||||
};
|
||||
|
||||
bool egl_texture_init(EGL_Texture ** texture)
|
||||
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display)
|
||||
{
|
||||
*texture = (EGL_Texture *)malloc(sizeof(EGL_Texture));
|
||||
if (!*texture)
|
||||
@@ -66,7 +101,7 @@ bool egl_texture_init(EGL_Texture ** texture)
|
||||
}
|
||||
|
||||
memset(*texture, 0, sizeof(EGL_Texture));
|
||||
|
||||
(*texture)->display = display;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,95 +110,135 @@ void egl_texture_free(EGL_Texture ** texture)
|
||||
if (!*texture)
|
||||
return;
|
||||
|
||||
if ((*texture)->textureCount > 0)
|
||||
{
|
||||
glDeleteTextures((*texture)->textureCount, (*texture)->textures);
|
||||
glDeleteSamplers((*texture)->textureCount, (*texture)->samplers);
|
||||
}
|
||||
glDeleteSamplers(1, &(*texture)->sampler);
|
||||
|
||||
if ((*texture)->hasPBO)
|
||||
for(int i = 0; i < (*texture)->bufferCount; ++i)
|
||||
{
|
||||
for(int i = 0; i < 2; ++i)
|
||||
struct Buffer * b = &(*texture)->buf[i];
|
||||
if (b->hasPBO)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, (*texture)->pbo[i]);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
if ((*texture)->pboSync[i])
|
||||
glDeleteSync((*texture)->pboSync[i]);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, b->pbo);
|
||||
if ((*texture)->buf[i].map)
|
||||
{
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
(*texture)->buf[i].map = NULL;
|
||||
}
|
||||
glDeleteBuffers(1, &b->pbo);
|
||||
if (b->sync)
|
||||
glDeleteSync(b->sync);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glDeleteBuffers(2, (*texture)->pbo);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glDeleteTextures(1, &(*texture)->tex);
|
||||
|
||||
for (size_t i = 0; i < (*texture)->dmaImageUsed; ++i)
|
||||
eglDestroyImage((*texture)->display, (*texture)->dmaImages[i].image);
|
||||
free((*texture)->dmaImages);
|
||||
|
||||
free(*texture);
|
||||
*texture = NULL;
|
||||
}
|
||||
|
||||
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
|
||||
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
int textureCount;
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
|
||||
texture->buf[i].map = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
texture->pboBufferSize,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||
GL_MAP_INVALIDATE_BUFFER_BIT |
|
||||
GL_MAP_PERSISTENT_BIT
|
||||
);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
texture->pixFmt = pixFmt;
|
||||
texture->width = width;
|
||||
texture->height = height;
|
||||
texture->streaming = streaming;
|
||||
texture->ready = false;
|
||||
if (!texture->buf[i].map)
|
||||
{
|
||||
DEBUG_EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i,
|
||||
texture->pboBufferSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
if (!texture->buf[i].map)
|
||||
return;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
texture->buf[i].map = NULL;
|
||||
}
|
||||
|
||||
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA)
|
||||
{
|
||||
if (texture->streaming && !useDMA)
|
||||
{
|
||||
for(int i = 0; i < texture->bufferCount; ++i)
|
||||
{
|
||||
egl_texture_unmap(texture, i);
|
||||
if (texture->buf[i].hasPBO)
|
||||
{
|
||||
glDeleteBuffers(1, &texture->buf[i].pbo);
|
||||
texture->buf[i].hasPBO = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture->pixFmt = pixFmt;
|
||||
texture->width = width;
|
||||
texture->height = height;
|
||||
texture->stride = stride;
|
||||
texture->streaming = streaming;
|
||||
texture->bufferCount = streaming ? BUFFER_COUNT : 1;
|
||||
texture->dma = useDMA;
|
||||
texture->ready = false;
|
||||
|
||||
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
|
||||
|
||||
switch(pixFmt)
|
||||
{
|
||||
case EGL_PF_BGRA:
|
||||
textureCount = 1;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_BGRA;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride / 4;
|
||||
texture->offsets[0] = 0;
|
||||
texture->intFormat = GL_BGRA;
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->fourcc = DRM_FORMAT_ARGB8888;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_RGBA:
|
||||
textureCount = 1;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RGBA;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride / 4;
|
||||
texture->offsets[0] = 0;
|
||||
texture->intFormat = GL_BGRA;
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->fourcc = DRM_FORMAT_ABGR8888;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_RGBA10:
|
||||
textureCount = 1;
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RGBA;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride / 4;
|
||||
texture->offsets[0] = 0;
|
||||
texture->intFormat = GL_RGB10_A2;
|
||||
texture->dataType = GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
texture->fourcc = DRM_FORMAT_BGRA1010102;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_YUV420:
|
||||
textureCount = 3;
|
||||
texture->format = GL_RED;
|
||||
texture->planes[0][0] = width;
|
||||
texture->planes[0][1] = height;
|
||||
texture->planes[0][2] = stride;
|
||||
texture->planes[1][0] = width / 2;
|
||||
texture->planes[1][1] = height / 2;
|
||||
texture->planes[1][2] = stride / 2;
|
||||
texture->planes[2][0] = width / 2;
|
||||
texture->planes[2][1] = height / 2;
|
||||
texture->planes[2][2] = stride / 2;
|
||||
texture->offsets[0] = 0;
|
||||
texture->offsets[1] = stride * height;
|
||||
texture->offsets[2] = texture->offsets[1] + (texture->offsets[1] / 4);
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->pboBufferSize = texture->offsets[2] + (texture->offsets[1] / 4);
|
||||
case EGL_PF_RGBA16F:
|
||||
texture->bpp = 8;
|
||||
texture->format = GL_RGBA;
|
||||
texture->intFormat = GL_RGBA16F;
|
||||
texture->dataType = GL_HALF_FLOAT;
|
||||
texture->fourcc = DRM_FORMAT_ABGR16161616F;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -171,129 +246,236 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
||||
return false;
|
||||
}
|
||||
|
||||
if (textureCount > texture->textureCount)
|
||||
{
|
||||
if (texture->textureCount > 0)
|
||||
{
|
||||
glDeleteTextures(texture->textureCount, texture->textures);
|
||||
glDeleteSamplers(texture->textureCount, texture->samplers);
|
||||
}
|
||||
texture->pitch = stride / texture->bpp;
|
||||
|
||||
texture->textureCount = textureCount;
|
||||
glGenTextures(texture->textureCount, texture->textures);
|
||||
glGenSamplers(texture->textureCount, texture->samplers);
|
||||
if (texture->tex)
|
||||
glDeleteTextures(1, &texture->tex);
|
||||
glGenTextures(1, &texture->tex);
|
||||
|
||||
if (!texture->sampler)
|
||||
{
|
||||
glGenSamplers(1, &texture->sampler);
|
||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
for(int i = 0; i < textureCount; ++i)
|
||||
if (useDMA)
|
||||
{
|
||||
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[i][0], texture->planes[i][1],
|
||||
0, texture->format, texture->dataType, NULL);
|
||||
if (texture->dmaFBO)
|
||||
glDeleteFramebuffers(1, &texture->dmaFBO);
|
||||
if (texture->dmaTex)
|
||||
glDeleteTextures(1, &texture->dmaTex);
|
||||
glGenFramebuffers(1, &texture->dmaFBO);
|
||||
glGenTextures(1, &texture->dmaTex);
|
||||
return true;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->width,
|
||||
texture->height, 0, texture->format, texture->dataType, NULL);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
if (streaming)
|
||||
if (!streaming)
|
||||
return true;
|
||||
|
||||
for(int i = 0; i < texture->bufferCount; ++i)
|
||||
{
|
||||
if (texture->hasPBO)
|
||||
{
|
||||
// release old PBOs and delete the buffers
|
||||
for(int i = 0; i < 2; ++i)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
glDeleteBuffers(2, texture->pbo);
|
||||
}
|
||||
glGenBuffers(1, &texture->buf[i].pbo);
|
||||
texture->buf[i].hasPBO = true;
|
||||
|
||||
glGenBuffers(2, texture->pbo);
|
||||
texture->hasPBO = true;
|
||||
for(int i = 0; i < 2; ++i)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
|
||||
glBufferStorage(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
texture->pboBufferSize,
|
||||
NULL,
|
||||
GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_WRITE_BIT
|
||||
);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[i].pbo);
|
||||
glBufferStorage(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
texture->pboBufferSize,
|
||||
NULL,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_PERSISTENT_BIT
|
||||
);
|
||||
|
||||
texture->pboMap[i] = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
texture->pboBufferSize,
|
||||
GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||
GL_MAP_INVALIDATE_BUFFER_BIT |
|
||||
GL_MAP_FLUSH_EXPLICIT_BIT
|
||||
);
|
||||
|
||||
if (!texture->pboMap[i])
|
||||
{
|
||||
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
if (!egl_texture_map(texture, i))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_warn_slow(void)
|
||||
{
|
||||
static bool warnDone = false;
|
||||
if (!warnDone)
|
||||
{
|
||||
warnDone = true;
|
||||
DEBUG_BREAK();
|
||||
DEBUG_WARN("The guest is providing updates faster then your computer can display them");
|
||||
DEBUG_WARN("This is a hardware limitation, expect microstutters & frame skips");
|
||||
DEBUG_BREAK();
|
||||
}
|
||||
}
|
||||
|
||||
bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
||||
{
|
||||
if (texture->streaming)
|
||||
{
|
||||
/* NOTE: DO NOT use any gl commands here as streaming must be thread safe */
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
if (texture->pboCount == 2)
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* update the GPU buffer */
|
||||
memcpy(texture->pboMap[texture->pboWIndex], buffer, texture->pboBufferSize);
|
||||
texture->pboSync[texture->pboWIndex] = 0;
|
||||
|
||||
if (++texture->pboWIndex == 2)
|
||||
texture->pboWIndex = 0;
|
||||
INTERLOCKED_INC(&texture->pboCount);
|
||||
const uint8_t b = sw % BUFFER_COUNT;
|
||||
memcpy(texture->buf[b].map, buffer, texture->pboBufferSize);
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Non streaming, this is NOT thread safe */
|
||||
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][0]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1],
|
||||
texture->format, texture->dataType, buffer + texture->offsets[i]);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
|
||||
texture->format, texture->dataType, buffer);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame)
|
||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame)
|
||||
{
|
||||
if (!texture->streaming)
|
||||
return false;
|
||||
|
||||
if (texture->pboCount == 2)
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
framebuffer_read(frame, texture->pboMap[texture->pboWIndex], texture->pboBufferSize);
|
||||
texture->pboSync[texture->pboWIndex] = 0;
|
||||
const uint8_t b = sw % BUFFER_COUNT;
|
||||
|
||||
if (++texture->pboWIndex == 2)
|
||||
texture->pboWIndex = 0;
|
||||
INTERLOCKED_INC(&texture->pboCount);
|
||||
framebuffer_read(
|
||||
frame,
|
||||
texture->buf[b].map,
|
||||
texture->stride,
|
||||
texture->height,
|
||||
texture->width,
|
||||
texture->bpp,
|
||||
texture->stride
|
||||
);
|
||||
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_texture_update_from_dma(EGL_Texture * texture, const FrameBuffer * frame, const int dmaFd)
|
||||
{
|
||||
if (!texture->streaming)
|
||||
return false;
|
||||
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLImage image = EGL_NO_IMAGE;
|
||||
|
||||
for (int i = 0; i < texture->dmaImageUsed; ++i)
|
||||
{
|
||||
if (texture->dmaImages[i].fd == dmaFd)
|
||||
{
|
||||
image = texture->dmaImages[i].image;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (image == EGL_NO_IMAGE)
|
||||
{
|
||||
EGLAttrib const attribs[] =
|
||||
{
|
||||
EGL_WIDTH , texture->width,
|
||||
EGL_HEIGHT , texture->height,
|
||||
EGL_LINUX_DRM_FOURCC_EXT , texture->fourcc,
|
||||
EGL_DMA_BUF_PLANE0_FD_EXT , dmaFd,
|
||||
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
|
||||
EGL_DMA_BUF_PLANE0_PITCH_EXT , texture->stride,
|
||||
EGL_NONE , EGL_NONE
|
||||
};
|
||||
|
||||
/* create the image backed by the dma buffer */
|
||||
image = eglCreateImage(
|
||||
texture->display,
|
||||
EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT,
|
||||
(EGLClientBuffer)NULL,
|
||||
attribs
|
||||
);
|
||||
|
||||
if (image == EGL_NO_IMAGE)
|
||||
{
|
||||
DEBUG_EGL_ERROR("Failed to create ELGImage for DMA transfer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texture->dmaImageUsed == texture->dmaImageCount)
|
||||
{
|
||||
size_t newCount = texture->dmaImageCount * 2 + 2;
|
||||
void * new = realloc(texture->dmaImages, newCount * sizeof *texture->dmaImages);
|
||||
if (!new)
|
||||
{
|
||||
DEBUG_EGL_ERROR("Failed to allocate memory");
|
||||
eglDestroyImage(texture->display, image);
|
||||
return false;
|
||||
}
|
||||
texture->dmaImageCount = newCount;
|
||||
texture->dmaImages = new;
|
||||
}
|
||||
|
||||
const size_t index = texture->dmaImageUsed++;
|
||||
texture->dmaImages[index].fd = dmaFd;
|
||||
texture->dmaImages[index].image = image;
|
||||
}
|
||||
|
||||
/* wait for completion */
|
||||
framebuffer_wait(frame, texture->height * texture->stride);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->dmaTex);
|
||||
g_egl_dynProcs.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, texture->dmaFBO);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->dmaTex, 0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glCopyTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, 0, 0, texture->width, texture->height, 0);
|
||||
|
||||
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
|
||||
switch (glClientWaitSync(fence, 0, 10000000)) // 10ms
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
break;
|
||||
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
egl_warn_slow();
|
||||
break;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
case GL_INVALID_VALUE:
|
||||
DEBUG_EGL_ERROR("glClientWaitSync failed");
|
||||
}
|
||||
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -302,85 +484,95 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
||||
if (!texture->streaming)
|
||||
return EGL_TEX_STATUS_OK;
|
||||
|
||||
if (texture->pboCount == 0)
|
||||
const uint8_t su =
|
||||
atomic_load_explicit(&texture->state.u, memory_order_acquire);
|
||||
|
||||
const uint8_t nextu = su + 1;
|
||||
if (
|
||||
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
|
||||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
|
||||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
|
||||
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
/* process any buffers that have not yet been flushed */
|
||||
int pos = texture->pboRIndex;
|
||||
for(int i = 0; i < texture->pboCount; ++i)
|
||||
const uint8_t b = su % BUFFER_COUNT;
|
||||
|
||||
/* update the texture */
|
||||
if (!texture->dma)
|
||||
{
|
||||
if (texture->pboSync[pos] == 0)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]);
|
||||
glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texture->pboBufferSize);
|
||||
texture->pboSync[pos] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->buf[b].pbo);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->pitch);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height,
|
||||
texture->format, texture->dataType, (const void *)0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
if (++pos == 2)
|
||||
pos = 0;
|
||||
/* create a fence to prevent usage before the update is complete */
|
||||
texture->buf[b].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
||||
/* we must flush to ensure the sync is in the command buffer */
|
||||
glFlush();
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
/* wait for the buffer to be ready */
|
||||
pos = texture->pboRIndex;
|
||||
switch(glClientWaitSync(texture->pboSync[pos], 0, 0))
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
break;
|
||||
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
glDeleteSync(texture->pboSync[pos]);
|
||||
EGL_ERROR("glClientWaitSync failed");
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
}
|
||||
|
||||
/* delete the sync and bind the buffer */
|
||||
glDeleteSync(texture->pboSync[pos]);
|
||||
texture->pboSync[pos] = 0;
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]);
|
||||
|
||||
/* update the textures */
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][2]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1],
|
||||
texture->format, texture->dataType, (const void *)texture->offsets[i]);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
/* advance the read index */
|
||||
if (++texture->pboRIndex == 2)
|
||||
texture->pboRIndex = 0;
|
||||
INTERLOCKED_DEC(&texture->pboCount);
|
||||
|
||||
texture->ready = true;
|
||||
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
|
||||
|
||||
return EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||
{
|
||||
/* if there are no new buffers ready, then just bind the textures */
|
||||
if (texture->streaming && !texture->ready)
|
||||
return EGL_TEX_STATUS_NOTREADY;
|
||||
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
|
||||
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
|
||||
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
if (texture->streaming)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
|
||||
glBindSampler(i, texture->samplers[i]);
|
||||
if (!texture->ready)
|
||||
return EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
const uint8_t b = ss % BUFFER_COUNT;
|
||||
if (texture->dma)
|
||||
{
|
||||
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
||||
memory_order_release) + 1;
|
||||
}
|
||||
else if (texture->buf[b].sync != 0)
|
||||
{
|
||||
switch(glClientWaitSync(texture->buf[b].sync, 0, 20000000)) // 20ms
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
glDeleteSync(texture->buf[b].sync);
|
||||
texture->buf[b].sync = 0;
|
||||
|
||||
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
||||
memory_order_release) + 1;
|
||||
break;
|
||||
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
break;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
case GL_INVALID_VALUE:
|
||||
glDeleteSync(texture->buf[b].sync);
|
||||
texture->buf[b].sync = 0;
|
||||
DEBUG_EGL_ERROR("glClientWaitSync failed");
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (ss != sd && ss != (uint8_t)(sd + 1))
|
||||
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
||||
memory_order_release) + 1;
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex);
|
||||
glBindSampler(0, texture->sampler);
|
||||
|
||||
return EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
int egl_texture_count(EGL_Texture * texture)
|
||||
{
|
||||
return texture->textureCount;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
typedef struct EGL_Texture EGL_Texture;
|
||||
|
||||
@@ -32,6 +34,7 @@ enum EGL_PixelFormat
|
||||
EGL_PF_RGBA,
|
||||
EGL_PF_BGRA,
|
||||
EGL_PF_RGBA10,
|
||||
EGL_PF_RGBA16F,
|
||||
EGL_PF_YUV420
|
||||
};
|
||||
|
||||
@@ -42,12 +45,13 @@ enum EGL_TexStatus
|
||||
EGL_TEX_STATUS_ERROR
|
||||
};
|
||||
|
||||
bool egl_texture_init(EGL_Texture ** tex);
|
||||
bool egl_texture_init(EGL_Texture ** texture, EGLDisplay * display);
|
||||
void egl_texture_free(EGL_Texture ** tex);
|
||||
|
||||
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming);
|
||||
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming, bool useDMA);
|
||||
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
|
||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame);
|
||||
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame);
|
||||
bool egl_texture_update_from_dma (EGL_Texture * texture, const FrameBuffer * frmame, const int dmaFd);
|
||||
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
|
||||
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
|
||||
int egl_texture_count (EGL_Texture * texture);
|
||||
int egl_texture_count (EGL_Texture * texture);
|
||||
|
||||
@@ -4,7 +4,6 @@ project(renderer_Opengl LANGUAGES C)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED
|
||||
gl
|
||||
glu
|
||||
)
|
||||
|
||||
add_library(renderer_OpenGL STATIC
|
||||
@@ -14,7 +13,6 @@ add_library(renderer_OpenGL STATIC
|
||||
target_link_libraries(renderer_OpenGL
|
||||
${RENDERER_OPENGL_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
decoders
|
||||
fonts
|
||||
)
|
||||
|
||||
|
||||
@@ -27,13 +27,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include "utils.h"
|
||||
#include "common/locking.h"
|
||||
#include "dynamic/fonts.h"
|
||||
#include "ll.h"
|
||||
|
||||
@@ -62,7 +61,7 @@ static struct Option opengl_options[] =
|
||||
.name = "vsync",
|
||||
.description = "Enable vsync",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "opengl",
|
||||
@@ -77,7 +76,8 @@ static struct Option opengl_options[] =
|
||||
.description = "Use GL_AMD_pinned_memory if it is available",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
}
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
struct OpenGL_Options
|
||||
@@ -108,7 +108,7 @@ struct Inst
|
||||
bool renderStarted;
|
||||
bool configured;
|
||||
bool reconfigure;
|
||||
SDL_GLContext glContext;
|
||||
LG_DSGLContext glContext;
|
||||
|
||||
SDL_Point window;
|
||||
bool frameUpdate;
|
||||
@@ -116,14 +116,14 @@ struct Inst
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj, alertFontObj;
|
||||
|
||||
LG_Lock formatLock;
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
GLuint dataFormat;
|
||||
size_t texSize;
|
||||
size_t texPos;
|
||||
FrameBuffer frame;
|
||||
LG_Lock formatLock;
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
GLuint dataFormat;
|
||||
size_t texSize;
|
||||
size_t texPos;
|
||||
const FrameBuffer * frame;
|
||||
|
||||
uint64_t drawStart;
|
||||
bool hasBuffers;
|
||||
@@ -148,6 +148,7 @@ struct Inst
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
bool showFPS;
|
||||
bool fpsTexture;
|
||||
SDL_Rect fpsRect;
|
||||
|
||||
@@ -177,23 +178,24 @@ enum ConfigStatus
|
||||
};
|
||||
|
||||
static void deconfigure(struct Inst * this);
|
||||
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window);
|
||||
static enum ConfigStatus configure(struct Inst * this);
|
||||
static void update_mouse_shape(struct Inst * this, bool * newShape);
|
||||
static bool draw_frame(struct Inst * this);
|
||||
static void draw_mouse(struct Inst * this);
|
||||
static void render_wait(struct Inst * this);
|
||||
|
||||
const char * opengl_get_name()
|
||||
const char * opengl_get_name(void)
|
||||
{
|
||||
return "OpenGL";
|
||||
}
|
||||
|
||||
static void opengl_setup()
|
||||
static void opengl_setup(void)
|
||||
{
|
||||
option_register(opengl_options);
|
||||
}
|
||||
|
||||
bool opengl_create(void ** opaque, const LG_RendererParams params)
|
||||
bool opengl_create(void ** opaque, const LG_RendererParams params,
|
||||
bool * needsOpenGL)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
@@ -232,10 +234,11 @@ bool opengl_create(void ** opaque, const LG_RendererParams params)
|
||||
|
||||
this->alerts = ll_new();
|
||||
|
||||
*needsOpenGL = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
bool opengl_initialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
@@ -243,11 +246,6 @@ bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
|
||||
this->waiting = true;
|
||||
this->waitDone = false;
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -271,7 +269,7 @@ void opengl_deinitialize(void * opaque)
|
||||
|
||||
if (this->glContext)
|
||||
{
|
||||
SDL_GL_DeleteContext(this->glContext);
|
||||
app_glDeleteContext(this->glContext);
|
||||
this->glContext = NULL;
|
||||
}
|
||||
|
||||
@@ -294,7 +292,14 @@ void opengl_deinitialize(void * opaque)
|
||||
free(this);
|
||||
}
|
||||
|
||||
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
void opengl_on_restart(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->waiting = true;
|
||||
}
|
||||
|
||||
void opengl_on_resize(void * opaque, const int width, const int height,
|
||||
const LG_RendererRect destRect, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
@@ -308,7 +313,7 @@ void opengl_on_resize(void * opaque, const int width, const int height, const LG
|
||||
glViewport(0, 0, this->window.x, this->window.y);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, this->window.x, this->window.y, 0);
|
||||
glOrtho(0, this->window.x, this->window.y, 0, -1, 1);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
@@ -324,7 +329,8 @@ void opengl_on_resize(void * opaque, const int width, const int height, const LG
|
||||
}
|
||||
}
|
||||
|
||||
bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
|
||||
bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor,
|
||||
const int width, const int height, const int pitch, const uint8_t * data)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
@@ -368,36 +374,20 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
|
||||
bool opengl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Invalid opaque pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK(this->formatLock);
|
||||
if (this->reconfigure)
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->configured ||
|
||||
this->format.type != format.type ||
|
||||
this->format.width != format.width ||
|
||||
this->format.height != format.height ||
|
||||
this->format.stride != format.stride ||
|
||||
this->format.bpp != format.bpp
|
||||
)
|
||||
{
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->reconfigure = true;
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->reconfigure = true;
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
LG_LOCK(this->syncLock);
|
||||
this->frame = frame;
|
||||
@@ -406,8 +396,14 @@ bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const
|
||||
|
||||
if (this->waiting)
|
||||
{
|
||||
this->waiting = false;
|
||||
this->waitFadeTime = microtime() + FADE_TIME;
|
||||
this->waiting = false;
|
||||
if (!this->params.quickSplash)
|
||||
this->waitFadeTime = microtime() + FADE_TIME;
|
||||
else
|
||||
{
|
||||
glDisable(GL_MULTISAMPLE);
|
||||
this->waitDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -466,6 +462,17 @@ void opengl_on_alert(void * opaque, const LG_MsgAlert alert, const char * messag
|
||||
ll_push(this->alerts, a);
|
||||
}
|
||||
|
||||
void opengl_on_help(void * opaque, const char * message)
|
||||
{
|
||||
// TODO: Implement this.
|
||||
}
|
||||
|
||||
void opengl_on_show_fps(void * opaque, bool showFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->showFPS = showFPS;
|
||||
}
|
||||
|
||||
void bitmap_to_texture(LG_FontBitmap * bitmap, GLuint texture)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D , texture );
|
||||
@@ -491,16 +498,15 @@ void bitmap_to_texture(LG_FontBitmap * bitmap, GLuint texture)
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
bool opengl_render_startup(void * opaque, SDL_Window * window)
|
||||
bool opengl_render_startup(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
this->glContext = SDL_GL_CreateContext(window);
|
||||
this->glContext = app_glCreateContext();
|
||||
if (!this->glContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the OpenGL context");
|
||||
return false;
|
||||
}
|
||||
|
||||
app_glMakeCurrent(this->glContext);
|
||||
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
@@ -545,26 +551,24 @@ bool opengl_render_startup(void * opaque, SDL_Window * window)
|
||||
}
|
||||
this->hasTextures = true;
|
||||
|
||||
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
app_glSetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
this->renderStarted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_render(void * opaque, SDL_Window * window)
|
||||
bool opengl_render(void * opaque, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
switch(configure(this, window))
|
||||
switch(configure(this))
|
||||
{
|
||||
case CONFIG_STATUS_ERROR:
|
||||
DEBUG_ERROR("configure failed");
|
||||
return false;
|
||||
|
||||
case CONFIG_STATUS_NOOP :
|
||||
break;
|
||||
|
||||
case CONFIG_STATUS_OK :
|
||||
if (!draw_frame(this))
|
||||
return false;
|
||||
@@ -586,7 +590,7 @@ bool opengl_render(void * opaque, SDL_Window * window)
|
||||
render_wait(this);
|
||||
}
|
||||
|
||||
if (this->fpsTexture)
|
||||
if (this->showFPS && this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
|
||||
struct Alert * alert;
|
||||
@@ -658,11 +662,11 @@ bool opengl_render(void * opaque, SDL_Window * window)
|
||||
|
||||
if (this->opt.preventBuffer)
|
||||
{
|
||||
SDL_GL_SwapWindow(window);
|
||||
app_glSwapBuffers();
|
||||
glFinish();
|
||||
}
|
||||
else
|
||||
SDL_GL_SwapWindow(window);
|
||||
app_glSwapBuffers();
|
||||
|
||||
this->mouseUpdate = false;
|
||||
return true;
|
||||
@@ -671,7 +675,7 @@ bool opengl_render(void * opaque, SDL_Window * window)
|
||||
void opengl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->params.showFPS)
|
||||
if (!this->showFPS)
|
||||
return;
|
||||
|
||||
char str[128];
|
||||
@@ -818,20 +822,24 @@ static void render_wait(struct Inst * this)
|
||||
|
||||
const LG_Renderer LGR_OpenGL =
|
||||
{
|
||||
.get_name = opengl_get_name,
|
||||
.setup = opengl_setup,
|
||||
.get_name = opengl_get_name,
|
||||
.setup = opengl_setup,
|
||||
|
||||
.create = opengl_create,
|
||||
.initialize = opengl_initialize,
|
||||
.deinitialize = opengl_deinitialize,
|
||||
.on_resize = opengl_on_resize,
|
||||
.on_mouse_shape = opengl_on_mouse_shape,
|
||||
.on_mouse_event = opengl_on_mouse_event,
|
||||
.on_frame_event = opengl_on_frame_event,
|
||||
.on_alert = opengl_on_alert,
|
||||
.render_startup = opengl_render_startup,
|
||||
.render = opengl_render,
|
||||
.update_fps = opengl_update_fps
|
||||
.create = opengl_create,
|
||||
.initialize = opengl_initialize,
|
||||
.deinitialize = opengl_deinitialize,
|
||||
.on_restart = opengl_on_restart,
|
||||
.on_resize = opengl_on_resize,
|
||||
.on_mouse_shape = opengl_on_mouse_shape,
|
||||
.on_mouse_event = opengl_on_mouse_event,
|
||||
.on_frame_format = opengl_on_frame_format,
|
||||
.on_frame = opengl_on_frame,
|
||||
.on_alert = opengl_on_alert,
|
||||
.on_help = opengl_on_help,
|
||||
.on_show_fps = opengl_on_show_fps,
|
||||
.render_startup = opengl_render_startup,
|
||||
.render = opengl_render,
|
||||
.update_fps = opengl_update_fps
|
||||
};
|
||||
|
||||
static bool _check_gl_error(unsigned int line, const char * name)
|
||||
@@ -840,12 +848,45 @@ static bool _check_gl_error(unsigned int line, const char * name)
|
||||
if (error == GL_NO_ERROR)
|
||||
return false;
|
||||
|
||||
const GLubyte * errStr = gluErrorString(error);
|
||||
const char * errStr;
|
||||
switch (error)
|
||||
{
|
||||
case GL_INVALID_ENUM:
|
||||
errStr = "GL_INVALID_ENUM";
|
||||
break;
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
errStr = "GL_INVALID_VALUE";
|
||||
break;
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
errStr = "GL_INVALID_OPERATION";
|
||||
break;
|
||||
|
||||
case GL_STACK_OVERFLOW:
|
||||
errStr = "GL_STACK_OVERFLOW";
|
||||
break;
|
||||
|
||||
case GL_STACK_UNDERFLOW:
|
||||
errStr = "GL_STACK_UNDERFLOW";
|
||||
break;
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
errStr = "GL_OUT_OF_MEMORY";
|
||||
break;
|
||||
|
||||
case GL_TABLE_TOO_LARGE:
|
||||
errStr = "GL_TABLE_TOO_LARGE";
|
||||
break;
|
||||
|
||||
default:
|
||||
errStr = "unknown error";
|
||||
}
|
||||
DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
|
||||
static enum ConfigStatus configure(struct Inst * this)
|
||||
{
|
||||
LG_LOCK(this->formatLock);
|
||||
if (!this->reconfigure)
|
||||
@@ -877,6 +918,12 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
|
||||
this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA16F:
|
||||
this->intFormat = GL_RGB16F;
|
||||
this->vboFormat = GL_RGBA;
|
||||
this->dataFormat = GL_HALF_FLOAT;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown/unsupported compression type");
|
||||
return CONFIG_STATUS_ERROR;
|
||||
@@ -1091,7 +1138,6 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
|
||||
switch(cursor)
|
||||
{
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
for(int i = 0; i < width * height; ++i)
|
||||
{
|
||||
const uint32_t c = ((uint32_t *)data)[i];
|
||||
@@ -1102,7 +1148,6 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
|
||||
//
|
||||
// technically we should also create an XOR texture from the data but this
|
||||
// usage seems very rare in modern software.
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
@@ -1264,7 +1309,7 @@ static bool draw_frame(struct Inst * this)
|
||||
break;
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
|
||||
DEBUG_ERROR("Wait failed %d", glGetError());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1275,14 +1320,19 @@ static bool draw_frame(struct Inst * this)
|
||||
glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride);
|
||||
const int bpp = this->format.bpp / 8;
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , bpp);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width);
|
||||
|
||||
this->texPos = 0;
|
||||
|
||||
framebuffer_read_fn(
|
||||
this->frame,
|
||||
this->format.height,
|
||||
this->format.width,
|
||||
bpp,
|
||||
this->format.pitch,
|
||||
opengl_buffer_fn,
|
||||
this->format.height * this->format.stride * 4,
|
||||
this
|
||||
);
|
||||
|
||||
@@ -1344,4 +1394,4 @@ static void draw_mouse(struct Inst * this)
|
||||
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
|
||||
glCallList(this->mouseList);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(spice LANGUAGES C)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(SPICE_PKGCONFIG REQUIRED
|
||||
spice-protocol
|
||||
nettle
|
||||
hogweed
|
||||
)
|
||||
|
||||
add_definitions(-D USE_NETTLE)
|
||||
|
||||
add_library(spice STATIC
|
||||
src/spice.c
|
||||
src/rsa.c
|
||||
)
|
||||
|
||||
target_link_libraries(spice
|
||||
lg_common
|
||||
${SPICE_PKGCONFIG_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(spice
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${SPICE_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum SpiceDataType
|
||||
{
|
||||
SPICE_DATA_TEXT,
|
||||
SPICE_DATA_PNG,
|
||||
SPICE_DATA_BMP,
|
||||
SPICE_DATA_TIFF,
|
||||
SPICE_DATA_JPEG,
|
||||
|
||||
SPICE_DATA_NONE
|
||||
}
|
||||
SpiceDataType;
|
||||
|
||||
typedef void (*SpiceClipboardNotice )(const SpiceDataType type);
|
||||
typedef void (*SpiceClipboardData )(const SpiceDataType type, uint8_t * buffer, uint32_t size);
|
||||
typedef void (*SpiceClipboardRelease)();
|
||||
typedef void (*SpiceClipboardRequest)(const SpiceDataType type);
|
||||
|
||||
bool spice_connect(const char * host, const unsigned short port, const char * password);
|
||||
void spice_disconnect();
|
||||
bool spice_process();
|
||||
bool spice_ready();
|
||||
|
||||
bool spice_key_down (uint32_t code);
|
||||
bool spice_key_up (uint32_t code);
|
||||
bool spice_mouse_mode (bool server);
|
||||
bool spice_mouse_position(uint32_t x, uint32_t y);
|
||||
bool spice_mouse_motion ( int32_t x, int32_t y);
|
||||
bool spice_mouse_press (uint32_t button);
|
||||
bool spice_mouse_release (uint32_t button);
|
||||
|
||||
bool spice_clipboard_request(SpiceDataType type);
|
||||
bool spice_clipboard_grab (SpiceDataType type);
|
||||
bool spice_clipboard_release();
|
||||
bool spice_clipboard_data (SpiceDataType type, uint8_t * data, size_t size);
|
||||
|
||||
/* events */
|
||||
bool spice_set_clipboard_cb(
|
||||
SpiceClipboardNotice cbNoticeFn,
|
||||
SpiceClipboardData cbDataFn,
|
||||
SpiceClipboardRelease cbReleaseFn,
|
||||
SpiceClipboardRequest cbRequestFn);
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
typedef struct SpicePoint16
|
||||
{
|
||||
int16_t x, y;
|
||||
}
|
||||
SpicePoint16;
|
||||
|
||||
typedef struct SpiceMsgMainInit
|
||||
{
|
||||
uint32_t session_id;
|
||||
uint32_t display_channels_hint;
|
||||
uint32_t supported_mouse_modes;
|
||||
uint32_t current_mouse_mode;
|
||||
uint32_t agent_connected;
|
||||
uint32_t agent_tokens;
|
||||
uint32_t multi_media_time;
|
||||
uint32_t ram_hint;
|
||||
}
|
||||
SpiceMsgMainInit;
|
||||
|
||||
typedef struct SpiceChannelID
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t channel_id;
|
||||
}
|
||||
SpiceChannelID;
|
||||
|
||||
typedef struct SpiceMsgMainChannelsList
|
||||
{
|
||||
uint32_t num_of_channels;
|
||||
//SpiceChannelID channels[num_of_channels]
|
||||
}
|
||||
SpiceMainChannelsList;
|
||||
|
||||
typedef struct SpiceMsgcMainMouseModeRequest
|
||||
{
|
||||
uint16_t mouse_mode;
|
||||
}
|
||||
SpiceMsgcMainMouseModeRequest;
|
||||
|
||||
typedef struct SpiceMsgPing
|
||||
{
|
||||
uint32_t id;
|
||||
uint64_t timestamp;
|
||||
}
|
||||
SpiceMsgPing,
|
||||
SpiceMsgcPong;
|
||||
|
||||
typedef struct SpiceMsgSetAck
|
||||
{
|
||||
uint32_t generation;
|
||||
uint32_t window;
|
||||
}
|
||||
SpiceMsgSetAck;
|
||||
|
||||
typedef struct SpiceMsgcAckSync
|
||||
{
|
||||
uint32_t generation;
|
||||
}
|
||||
SpiceMsgcAckSync;
|
||||
|
||||
typedef struct SpiceMsgNotify
|
||||
{
|
||||
uint64_t time_stamp;
|
||||
uint32_t severity;
|
||||
uint32_t visibility;
|
||||
uint32_t what;
|
||||
uint32_t message_len;
|
||||
//char message[message_len+1]
|
||||
}
|
||||
SpiceMsgNotify;
|
||||
|
||||
typedef struct SpiceMsgInputsInit
|
||||
{
|
||||
uint16_t modifiers;
|
||||
}
|
||||
SpiceMsgInputsInit,
|
||||
SpiceMsgInputsKeyModifiers,
|
||||
SpiceMsgcInputsKeyModifiers;
|
||||
|
||||
typedef struct SpiceMsgcKeyDown
|
||||
{
|
||||
uint32_t code;
|
||||
}
|
||||
SpiceMsgcKeyDown,
|
||||
SpiceMsgcKeyUp;
|
||||
|
||||
typedef struct SpiceMsgcMousePosition
|
||||
{
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint16_t button_state;
|
||||
uint8_t display_id;
|
||||
}
|
||||
SpiceMsgcMousePosition;
|
||||
|
||||
typedef struct SpiceMsgcMouseMotion
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint16_t button_state;
|
||||
}
|
||||
SpiceMsgcMouseMotion;
|
||||
|
||||
typedef struct SpiceMsgcMousePress
|
||||
{
|
||||
uint8_t button;
|
||||
uint16_t button_state;
|
||||
}
|
||||
SpiceMsgcMousePress,
|
||||
SpiceMsgcMouseRelease;
|
||||
|
||||
|
||||
// spice is missing these defines, the offical reference library incorrectly uses the VD defines
|
||||
#define COMMON_CAPS_BYTES (((SPICE_COMMON_CAP_MINI_HEADER + 32) / 8) & ~3)
|
||||
#define COMMON_SET_CAPABILITY(caps, index) \
|
||||
{ (caps)[(index) / 32] |= (1 << ((index) % 32)); }
|
||||
|
||||
#define MAIN_CAPS_BYTES (((SPICE_MAIN_CAP_SEAMLESS_MIGRATE + 32) / 8) & ~3)
|
||||
#define MAIN_SET_CAPABILITY(caps, index) \
|
||||
{ (caps)[(index) / 32] |= (1 << ((index) % 32)); }
|
||||
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "rsa.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <spice/protocol.h>
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(USE_OPENSSL) && defined(USE_NETTLE)
|
||||
#error "USE_OPENSSL and USE_NETTLE are both defined"
|
||||
#elif !defined(USE_OPENSSL) && !defined(USE_NETTLE)
|
||||
#error "One of USE_OPENSSL or USE_NETTLE must be defined"
|
||||
#endif
|
||||
|
||||
#if defined(USE_OPENSSL)
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/x509.h>
|
||||
#endif
|
||||
|
||||
#if defined(USE_NETTLE)
|
||||
#include <stdlib.h>
|
||||
#include <nettle/asn1.h>
|
||||
#include <nettle/sha1.h>
|
||||
#include <nettle/rsa.h>
|
||||
#include <nettle/bignum.h>
|
||||
#include <gmp.h>
|
||||
|
||||
#define SHA1_HASH_LEN 20
|
||||
#endif
|
||||
|
||||
#if defined(USE_NETTLE)
|
||||
/* the below OAEP implementation is derived from the FreeTDS project */
|
||||
static void memxor(uint8_t * a, const uint8_t * b, const unsigned int len)
|
||||
{
|
||||
for(unsigned int i = 0; i < len; ++i)
|
||||
a[i] = a[i] ^ b[i];
|
||||
}
|
||||
|
||||
static void sha1(uint8_t * hash, const uint8_t *data, unsigned int len)
|
||||
{
|
||||
struct sha1_ctx ctx;
|
||||
|
||||
sha1_init(&ctx);
|
||||
sha1_update(&ctx, len, data);
|
||||
sha1_digest(&ctx, SHA1_HASH_LEN, hash);
|
||||
}
|
||||
|
||||
static void oaep_mask(uint8_t * dest, size_t dest_len, const uint8_t * mask, size_t mask_len)
|
||||
{
|
||||
uint8_t hash[SHA1_HASH_LEN];
|
||||
uint8_t seed[mask_len + 4 ];
|
||||
memcpy(seed, mask, mask_len);
|
||||
|
||||
for(unsigned int n = 0;; ++n)
|
||||
{
|
||||
(seed+mask_len)[0] = n >> 24;
|
||||
(seed+mask_len)[1] = n >> 16;
|
||||
(seed+mask_len)[2] = n >> 8;
|
||||
(seed+mask_len)[3] = n >> 0;
|
||||
|
||||
sha1(hash, seed, sizeof(seed));
|
||||
if (dest_len <= SHA1_HASH_LEN)
|
||||
{
|
||||
memxor(dest, hash, dest_len);
|
||||
break;
|
||||
}
|
||||
|
||||
memxor(dest, hash, SHA1_HASH_LEN);
|
||||
dest += SHA1_HASH_LEN;
|
||||
dest_len -= SHA1_HASH_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
static bool oaep_pad(mpz_t m, unsigned int key_size, const uint8_t * message, unsigned int len)
|
||||
{
|
||||
if (len + SHA1_HASH_LEN * 2 + 2 > key_size)
|
||||
{
|
||||
DEBUG_ERROR("Message too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t all[1];
|
||||
uint8_t ros[SHA1_HASH_LEN];
|
||||
uint8_t db [key_size - SHA1_HASH_LEN - 1];
|
||||
} em;
|
||||
|
||||
memset(&em, 0, sizeof(em));
|
||||
sha1(em.db, (uint8_t *)"", 0);
|
||||
em.all[key_size - len - 1] = 0x1;
|
||||
memcpy(em.all + (key_size - len), message, len);
|
||||
|
||||
/* we are not too worried about randomness since we are just making a local
|
||||
* connection, should anyone use this code outside of LookingGlass please be
|
||||
* sure to use something better such as `gnutls_rnd` */
|
||||
for(int i = 0; i < SHA1_HASH_LEN; ++i)
|
||||
em.ros[i] = rand() % 255;
|
||||
|
||||
const int db_len = key_size - SHA1_HASH_LEN - 1;
|
||||
oaep_mask(em.db , db_len , em.ros, SHA1_HASH_LEN);
|
||||
oaep_mask(em.ros, SHA1_HASH_LEN, em.db , db_len );
|
||||
|
||||
nettle_mpz_set_str_256_u(m, key_size, em.all);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result)
|
||||
{
|
||||
result->size = 0;
|
||||
result->data = NULL;
|
||||
|
||||
#if defined(USE_OPENSSL)
|
||||
BIO *bioKey = BIO_new(BIO_s_mem());
|
||||
if (!bioKey)
|
||||
{
|
||||
DEBUG_ERROR("failed to allocate bioKey");
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO_write(bioKey, pub_key, SPICE_TICKET_PUBKEY_BYTES);
|
||||
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
|
||||
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
|
||||
|
||||
result->size = RSA_size(rsa);
|
||||
result->data = (char *)malloc(result->size);
|
||||
|
||||
if (RSA_public_encrypt(
|
||||
strlen(password) + 1,
|
||||
(uint8_t*)password,
|
||||
(uint8_t*)result->data,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING
|
||||
) <= 0)
|
||||
{
|
||||
free(result->data);
|
||||
result->size = 0;
|
||||
result->data = NULL;
|
||||
|
||||
DEBUG_ERROR("rsa public encrypt failed");
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#if defined(USE_NETTLE)
|
||||
struct asn1_der_iterator der;
|
||||
struct asn1_der_iterator j;
|
||||
struct rsa_public_key pub;
|
||||
|
||||
if (asn1_der_iterator_first(&der, SPICE_TICKET_PUBKEY_BYTES, pub_key) == ASN1_ITERATOR_CONSTRUCTED
|
||||
&& der.type == ASN1_SEQUENCE
|
||||
&& asn1_der_decode_constructed_last(&der) == ASN1_ITERATOR_CONSTRUCTED
|
||||
&& der.type == ASN1_SEQUENCE
|
||||
&& asn1_der_decode_constructed(&der, &j) == ASN1_ITERATOR_PRIMITIVE
|
||||
&& j.type == ASN1_IDENTIFIER
|
||||
&& asn1_der_iterator_next(&der) == ASN1_ITERATOR_PRIMITIVE
|
||||
&& der.type == ASN1_BITSTRING
|
||||
&& asn1_der_decode_bitstring_last(&der))
|
||||
{
|
||||
if (j.length != 9)
|
||||
{
|
||||
DEBUG_ERROR("Invalid key, not RSA");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE
|
||||
&& j.type == ASN1_NULL
|
||||
&& j.length == 0
|
||||
&& asn1_der_iterator_next(&j) == ASN1_ITERATOR_END)
|
||||
{
|
||||
rsa_public_key_init(&pub);
|
||||
if (!rsa_public_key_from_der_iterator(&pub, 0, &der))
|
||||
{
|
||||
DEBUG_ERROR("Unable to load public key from DER iterator");
|
||||
rsa_public_key_clear(&pub);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mpz_t p;
|
||||
mpz_init(p);
|
||||
oaep_pad(p, pub.size, (uint8_t *)password, strlen(password)+1);
|
||||
mpz_powm(p, p, pub.e, pub.n);
|
||||
|
||||
result->size = pub.size;
|
||||
result->data = malloc(pub.size);
|
||||
nettle_mpz_get_str_256(pub.size, (uint8_t *)result->data, p);
|
||||
|
||||
rsa_public_key_clear(&pub);
|
||||
mpz_clear(p);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void spice_rsa_free_password(struct spice_password * pass)
|
||||
{
|
||||
free(pass->data);
|
||||
pass->size = 0;
|
||||
pass->data = NULL;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
512
client/src/app.c
512
client/src/app.c
@@ -17,13 +17,434 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "core.h"
|
||||
#include "util.h"
|
||||
#include "clipboard.h"
|
||||
|
||||
#include "ll.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
bool app_isRunning(void)
|
||||
{
|
||||
return
|
||||
g_state.state == APP_STATE_RUNNING ||
|
||||
g_state.state == APP_STATE_RESTART;
|
||||
}
|
||||
|
||||
void app_updateCursorPos(double x, double y)
|
||||
{
|
||||
g_cursor.pos.x = x;
|
||||
g_cursor.pos.y = y;
|
||||
g_cursor.valid = true;
|
||||
}
|
||||
|
||||
void app_handleFocusEvent(bool focused)
|
||||
{
|
||||
g_state.focused = focused;
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!focused)
|
||||
{
|
||||
core_setGrabQuiet(false);
|
||||
core_setCursorInView(false);
|
||||
|
||||
if (g_params.releaseKeysOnFocusLoss)
|
||||
for (int key = 0; key < KEY_MAX; key++)
|
||||
if (g_state.keyDown[key])
|
||||
app_handleKeyRelease(key);
|
||||
}
|
||||
|
||||
g_cursor.realign = true;
|
||||
g_state.ds->realignPointer();
|
||||
}
|
||||
|
||||
void app_handleEnterEvent(bool entered)
|
||||
{
|
||||
if (entered)
|
||||
{
|
||||
g_cursor.inWindow = true;
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
g_cursor.realign = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_cursor.inWindow = false;
|
||||
core_setCursorInView(false);
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!g_params.alwaysShowCursor)
|
||||
g_cursor.draw = false;
|
||||
g_cursor.redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
void app_clipboardRelease(void)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
spice_clipboard_release();
|
||||
}
|
||||
|
||||
void app_clipboardNotify(const LG_ClipboardData type, size_t size)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
spice_clipboard_release();
|
||||
return;
|
||||
}
|
||||
|
||||
g_state.cbType = cb_lgTypeToSpiceType(type);
|
||||
g_state.cbChunked = size > 0;
|
||||
g_state.cbXfer = size;
|
||||
|
||||
spice_clipboard_grab(g_state.cbType);
|
||||
|
||||
if (size)
|
||||
spice_clipboard_data_start(g_state.cbType, size);
|
||||
}
|
||||
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (g_state.cbChunked && size > g_state.cbXfer)
|
||||
{
|
||||
DEBUG_ERROR("refusing to send more then cbXfer bytes for chunked xfer");
|
||||
size = g_state.cbXfer;
|
||||
}
|
||||
|
||||
if (!g_state.cbChunked)
|
||||
spice_clipboard_data_start(g_state.cbType, size);
|
||||
|
||||
spice_clipboard_data(g_state.cbType, data, (uint32_t)size);
|
||||
g_state.cbXfer -= size;
|
||||
}
|
||||
|
||||
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest));
|
||||
|
||||
cbr->type = g_state.cbType;
|
||||
cbr->replyFn = replyFn;
|
||||
cbr->opaque = opaque;
|
||||
ll_push(g_state.cbRequestList, cbr);
|
||||
|
||||
spice_clipboard_request(g_state.cbType);
|
||||
}
|
||||
|
||||
void spiceClipboardNotice(const SpiceDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (!g_state.cbAvailable)
|
||||
return;
|
||||
|
||||
g_state.cbType = type;
|
||||
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
|
||||
void app_handleButtonPress(int button)
|
||||
{
|
||||
g_cursor.buttons |= (1U << button);
|
||||
|
||||
if (!core_inputEnabled() || !g_cursor.inView)
|
||||
return;
|
||||
|
||||
if (!spice_mouse_press(button))
|
||||
DEBUG_ERROR("app_handleButtonPress: failed to send message");
|
||||
}
|
||||
|
||||
void app_handleButtonRelease(int button)
|
||||
{
|
||||
g_cursor.buttons &= ~(1U << button);
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!spice_mouse_release(button))
|
||||
DEBUG_ERROR("app_handleButtonRelease: failed to send message");
|
||||
}
|
||||
|
||||
void app_handleKeyPress(int sc)
|
||||
{
|
||||
if (sc == g_params.escapeKey && !g_state.escapeActive)
|
||||
{
|
||||
g_state.escapeActive = true;
|
||||
g_state.escapeAction = -1;
|
||||
app_showHelp(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
g_state.escapeAction = sc;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
||||
return;
|
||||
|
||||
if (!g_state.keyDown[sc])
|
||||
{
|
||||
uint32_t ps2 = xfree86_to_ps2[sc];
|
||||
if (!ps2)
|
||||
return;
|
||||
|
||||
if (spice_key_down(ps2))
|
||||
g_state.keyDown[sc] = true;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("app_handleKeyPress: failed to send message");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleKeyRelease(int sc)
|
||||
{
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
if (g_state.escapeAction == -1)
|
||||
{
|
||||
if (g_params.useSpiceInput)
|
||||
core_setGrab(!g_cursor.grab);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeybindHandle handle = g_state.bindings[sc];
|
||||
if (handle)
|
||||
{
|
||||
handle->callback(sc, handle->opaque);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == g_params.escapeKey)
|
||||
{
|
||||
g_state.escapeActive = false;
|
||||
app_showHelp(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
// avoid sending key up events when we didn't send a down
|
||||
if (!g_state.keyDown[sc])
|
||||
return;
|
||||
|
||||
if (g_params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
||||
return;
|
||||
|
||||
uint32_t ps2 = xfree86_to_ps2[sc];
|
||||
if (!ps2)
|
||||
return;
|
||||
|
||||
if (spice_key_up(ps2))
|
||||
g_state.keyDown[sc] = false;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("app_handleKeyRelease: failed to send message");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy)
|
||||
{
|
||||
if (g_cursor.grab)
|
||||
{
|
||||
if (g_params.rawMouse)
|
||||
core_handleMouseGrabbed(rawx, rawy);
|
||||
else
|
||||
core_handleMouseGrabbed(normx, normy);
|
||||
}
|
||||
else
|
||||
if (g_cursor.inWindow)
|
||||
core_handleMouseNormal(normx, normy);
|
||||
}
|
||||
|
||||
static inline double clamp(double x, double min, double max)
|
||||
{
|
||||
if (x < min) return min;
|
||||
if (x > max) return max;
|
||||
return x;
|
||||
}
|
||||
|
||||
// On some display servers normal cursor logic does not work due to the lack of
|
||||
// cursor warp support. Instead, we attempt a best-effort emulation which works
|
||||
// with a 1:1 mouse movement patch applied in the guest. For anything fancy, use
|
||||
// capture mode.
|
||||
void app_handleMouseBasic()
|
||||
{
|
||||
/* do not pass mouse events to the guest if we do not have focus */
|
||||
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused)
|
||||
return;
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
const bool inView =
|
||||
g_cursor.pos.x >= g_state.dstRect.x &&
|
||||
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
|
||||
g_cursor.pos.y >= g_state.dstRect.y &&
|
||||
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
|
||||
|
||||
core_setCursorInView(inView);
|
||||
|
||||
/* translate the current position to guest coordinate space */
|
||||
struct DoublePoint guest;
|
||||
util_localCurToGuest(&guest);
|
||||
|
||||
int x = (int) round(clamp(guest.x, 0, g_state.srcSize.x) - g_cursor.projected.x);
|
||||
int y = (int) round(clamp(guest.y, 0, g_state.srcSize.y) - g_cursor.projected.y);
|
||||
|
||||
if (!x && !y)
|
||||
return;
|
||||
|
||||
g_cursor.projected.x += x;
|
||||
g_cursor.projected.y += y;
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
void app_resyncMouseBasic()
|
||||
{
|
||||
if (!g_cursor.guest.valid)
|
||||
return;
|
||||
g_cursor.projected.x = g_cursor.guest.x + g_cursor.guest.hx;
|
||||
g_cursor.projected.y = g_cursor.guest.y + g_cursor.guest.hy;
|
||||
}
|
||||
|
||||
void app_updateWindowPos(int x, int y)
|
||||
{
|
||||
g_state.windowPos.x = x;
|
||||
g_state.windowPos.y = y;
|
||||
}
|
||||
|
||||
void app_handleResizeEvent(int w, int h, const struct Border border)
|
||||
{
|
||||
memcpy(&g_state.border, &border, sizeof(border));
|
||||
|
||||
/* don't do anything else if the window dimensions have not changed */
|
||||
if (g_state.windowW == w && g_state.windowH == h)
|
||||
return;
|
||||
|
||||
g_state.windowW = w;
|
||||
g_state.windowH = h;
|
||||
g_state.windowCX = w / 2;
|
||||
g_state.windowCY = h / 2;
|
||||
core_updatePositionInfo();
|
||||
|
||||
if (core_inputEnabled())
|
||||
{
|
||||
/* if the window is moved/resized causing a loss of focus while grabbed, it
|
||||
* makes it impossible to re-focus the window, so we quietly re-enter
|
||||
* capture if we were already in it */
|
||||
if (g_cursor.grab)
|
||||
{
|
||||
core_setGrabQuiet(false);
|
||||
core_setGrabQuiet(true);
|
||||
}
|
||||
core_alignToGuest();
|
||||
}
|
||||
}
|
||||
|
||||
void app_handleCloseEvent(void)
|
||||
{
|
||||
if (!g_params.ignoreQuit || !g_cursor.inView)
|
||||
g_state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
void app_setFullscreen(bool fs)
|
||||
{
|
||||
g_state.ds->setFullscreen(fs);
|
||||
}
|
||||
|
||||
bool app_getFullscreen(void)
|
||||
{
|
||||
return g_state.ds->getFullscreen();
|
||||
}
|
||||
|
||||
bool app_getProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
return g_state.ds->getProp(prop, ret);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLDisplay app_getEGLDisplay(void)
|
||||
{
|
||||
return g_state.ds->getEGLDisplay();
|
||||
}
|
||||
|
||||
EGLNativeWindowType app_getEGLNativeWindow(void)
|
||||
{
|
||||
return g_state.ds->getEGLNativeWindow();
|
||||
}
|
||||
|
||||
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface)
|
||||
{
|
||||
g_state.ds->eglSwapBuffers(display, surface);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
LG_DSGLContext app_glCreateContext(void)
|
||||
{
|
||||
return g_state.ds->glCreateContext();
|
||||
}
|
||||
|
||||
void app_glDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
g_state.ds->glDeleteContext(context);
|
||||
}
|
||||
|
||||
void app_glMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
g_state.ds->glMakeCurrent(context);
|
||||
}
|
||||
|
||||
void app_glSetSwapInterval(int interval)
|
||||
{
|
||||
g_state.ds->glSetSwapInterval(interval);
|
||||
}
|
||||
|
||||
void app_glSwapBuffers(void)
|
||||
{
|
||||
g_state.ds->glSwapBuffers();
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
{
|
||||
if (!state.lgr || !params.showAlerts)
|
||||
if (!g_state.lgr || !g_params.showAlerts)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
@@ -36,8 +457,8 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
vsnprintf(buffer, length + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
state.lgr->on_alert(
|
||||
state.lgrData,
|
||||
g_state.lgr->on_alert(
|
||||
g_state.lgrData,
|
||||
type,
|
||||
buffer,
|
||||
NULL
|
||||
@@ -46,30 +467,101 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque)
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
|
||||
{
|
||||
// don't allow duplicate binds
|
||||
if (state.bindings[key])
|
||||
if (g_state.bindings[sc])
|
||||
{
|
||||
DEBUG_INFO("Key already bound");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle));
|
||||
handle->key = key;
|
||||
handle->sc = sc;
|
||||
handle->callback = callback;
|
||||
handle->opaque = opaque;
|
||||
|
||||
state.bindings[key] = handle;
|
||||
g_state.bindings[sc] = handle;
|
||||
g_state.keyDescription[sc] = description;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void app_release_keybind(KeybindHandle * handle)
|
||||
void app_releaseKeybind(KeybindHandle * handle)
|
||||
{
|
||||
if (!*handle)
|
||||
return;
|
||||
|
||||
state.bindings[(*handle)->key] = NULL;
|
||||
g_state.bindings[(*handle)->sc] = NULL;
|
||||
free(*handle);
|
||||
*handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void app_releaseAllKeybinds(void)
|
||||
{
|
||||
for(int i = 0; i < KEY_MAX; ++i)
|
||||
if (g_state.bindings[i])
|
||||
{
|
||||
free(g_state.bindings[i]);
|
||||
g_state.bindings[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char * build_help_str()
|
||||
{
|
||||
size_t size = 50;
|
||||
size_t offset = 0;
|
||||
char * buffer = malloc(size);
|
||||
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
const char * escapeName = xfree86_to_display[g_params.escapeKey];
|
||||
|
||||
offset += snprintf(buffer, size, "%s %-10s Toggle capture mode\n", escapeName, "");
|
||||
if (offset >= size)
|
||||
{
|
||||
DEBUG_ERROR("Help string somehow overflowed. This should be impossible.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < KEY_MAX; ++i)
|
||||
{
|
||||
if (g_state.keyDescription[i])
|
||||
{
|
||||
const char * keyName = xfree86_to_display[i];
|
||||
const char * desc = g_state.keyDescription[i];
|
||||
int needed = snprintf(buffer + offset, size - offset, "%s+%-10s %s\n", escapeName, keyName, desc);
|
||||
if (offset + needed < size)
|
||||
offset += needed;
|
||||
else
|
||||
{
|
||||
size = size * 2 + needed;
|
||||
void * new = realloc(buffer, size);
|
||||
if (!new) {
|
||||
free(buffer);
|
||||
DEBUG_ERROR("Out of memory when constructing help text");
|
||||
return NULL;
|
||||
}
|
||||
buffer = new;
|
||||
offset += snprintf(buffer + offset, size - offset, "%s+%-10s %s\n", escapeName, keyName, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void app_showHelp(bool show)
|
||||
{
|
||||
char * help = show ? build_help_str() : NULL;
|
||||
g_state.lgr->on_help(g_state.lgrData, help);
|
||||
free(help);
|
||||
}
|
||||
|
||||
void app_showFPS(bool showFPS)
|
||||
{
|
||||
if (!g_state.lgr)
|
||||
return;
|
||||
|
||||
g_state.lgr->on_show_fps(g_state.lgrData, showFPS);
|
||||
}
|
||||
|
||||
116
client/src/clipboard.c
Normal file
116
client/src/clipboard.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "clipboard.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break;
|
||||
case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break;
|
||||
case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break;
|
||||
case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break;
|
||||
case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break;
|
||||
default:
|
||||
DEBUG_ERROR("invalid spice data type");
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break;
|
||||
case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break;
|
||||
case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break;
|
||||
case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break;
|
||||
case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard data type");
|
||||
return SPICE_DATA_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void cb_spiceNotice(const SpiceDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (!g_state.cbAvailable)
|
||||
return;
|
||||
|
||||
g_state.cbType = type;
|
||||
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
|
||||
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (type == SPICE_DATA_TEXT)
|
||||
{
|
||||
// dos2unix
|
||||
uint8_t * p = buffer;
|
||||
uint32_t newSize = size;
|
||||
for(uint32_t i = 0; i < size; ++i)
|
||||
{
|
||||
uint8_t c = buffer[i];
|
||||
if (c == '\r')
|
||||
{
|
||||
--newSize;
|
||||
continue;
|
||||
}
|
||||
*p++ = c;
|
||||
}
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
struct CBRequest * cbr;
|
||||
if (ll_shift(g_state.cbRequestList, (void **)&cbr))
|
||||
{
|
||||
cbr->replyFn(cbr->opaque, cb_spiceTypeToLGType(type), buffer, size);
|
||||
free(cbr);
|
||||
}
|
||||
}
|
||||
|
||||
void cb_spiceRelease(void)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
|
||||
if (g_state.cbAvailable)
|
||||
g_state.ds->cbRelease();
|
||||
}
|
||||
|
||||
void cb_spiceRequest(const SpiceDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
if (g_state.cbAvailable)
|
||||
g_state.ds->cbRequest(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
29
client/src/clipboard.h
Normal file
29
client/src/clipboard.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "spice/spice.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type);
|
||||
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
|
||||
|
||||
void cb_spiceNotice(const SpiceDataType type);
|
||||
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size);
|
||||
void cb_spiceRelease(void);
|
||||
void cb_spiceRequest(const SpiceDataType type);
|
||||
@@ -19,6 +19,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include "main.h"
|
||||
#include "config.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "common/option.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
@@ -26,18 +28,21 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
// forwards
|
||||
static bool optRendererParse (struct Option * opt, const char * str);
|
||||
static StringList optRendererValues (struct Option * opt);
|
||||
static char * optRendererToString (struct Option * opt);
|
||||
static bool optPosParse (struct Option * opt, const char * str);
|
||||
static StringList optPosValues (struct Option * opt);
|
||||
static char * optPosToString (struct Option * opt);
|
||||
static bool optSizeParse (struct Option * opt, const char * str);
|
||||
static StringList optSizeValues (struct Option * opt);
|
||||
static char * optSizeToString (struct Option * opt);
|
||||
static char * optScancodeToString (struct Option * opt);
|
||||
static bool optRendererParse (struct Option * opt, const char * str);
|
||||
static StringList optRendererValues (struct Option * opt);
|
||||
static char * optRendererToString(struct Option * opt);
|
||||
static bool optPosParse (struct Option * opt, const char * str);
|
||||
static StringList optPosValues (struct Option * opt);
|
||||
static char * optPosToString (struct Option * opt);
|
||||
static bool optSizeParse (struct Option * opt, const char * str);
|
||||
static StringList optSizeValues (struct Option * opt);
|
||||
static char * optSizeToString (struct Option * opt);
|
||||
static bool optScancodeValidate(struct Option * opt, const char ** error);
|
||||
static char * optScancodeToString(struct Option * opt);
|
||||
static bool optRotateValidate (struct Option * opt, const char ** error);
|
||||
|
||||
static void doLicense();
|
||||
|
||||
@@ -84,6 +89,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 1000
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "allowDMA",
|
||||
.description = "Allow direct DMA transfers if possible (VM-VM only for now)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
|
||||
// window options
|
||||
{
|
||||
@@ -142,6 +154,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "dontUpscale",
|
||||
.description = "Never try to upscale the window",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "borderless",
|
||||
@@ -175,8 +194,8 @@ static struct Option options[] =
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "fpsLimit",
|
||||
.description = "Frame rate limit (0 = disable - not recommended, -1 = auto detect)",
|
||||
.name = "fpsMin",
|
||||
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
|
||||
.shortopt = 'K',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = -1,
|
||||
@@ -205,6 +224,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "autoScreensaver",
|
||||
.description = "Prevent the screensaver from starting when guest requests it",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "alerts",
|
||||
@@ -213,6 +239,21 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "quickSplash",
|
||||
.description = "Skip fading out the splash screen when a connection is established",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "rotate",
|
||||
.description = "Rotate the displayed image (0, 90, 180, 270)",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.validator = optRotateValidate,
|
||||
.value.x_int = 0,
|
||||
},
|
||||
|
||||
// input options
|
||||
{
|
||||
@@ -223,14 +264,36 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "grabKeyboardOnFocus",
|
||||
.description = "Grab the keyboard when focused",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "releaseKeysOnFocusLoss",
|
||||
.description = "On focus loss, send key up events to guest for all held keys",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "escapeKey",
|
||||
.description = "Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values",
|
||||
.description = "Specify the escape key, see <linux/input-event-codes.h> for valid values",
|
||||
.shortopt = 'm',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = SDL_SCANCODE_SCROLLLOCK,
|
||||
.toString = optScancodeToString
|
||||
.value.x_int = KEY_SCROLLLOCK,
|
||||
.validator = optScancodeValidate,
|
||||
.toString = optScancodeToString,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "ignoreWindowsKeys",
|
||||
.description = "Do not pass events for the windows keys to the guest",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
@@ -247,6 +310,41 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "mouseSmoothing",
|
||||
.description = "Apply simple mouse smoothing when rawMouse is not in use (helps reduce aliasing)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "rawMouse",
|
||||
.description = "Use RAW mouse input when in capture mode (good for gaming)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "mouseRedraw",
|
||||
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "autoCapture",
|
||||
.description = "Try to keep the mouse captured when needed",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "captureOnly",
|
||||
.description = "Only enable input via SPICE if in capture mode",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
|
||||
// spice options
|
||||
{
|
||||
@@ -309,14 +407,28 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "captureOnStart",
|
||||
.description = "Capture mouse and keyboard on start",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "alwaysShowCursor",
|
||||
.description = "Always show host cursor",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
void config_init()
|
||||
void config_init(void)
|
||||
{
|
||||
params.center = true;
|
||||
params.w = 1024;
|
||||
params.h = 768;
|
||||
g_params.center = true;
|
||||
g_params.w = 1024;
|
||||
g_params.h = 768;
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
@@ -371,59 +483,88 @@ bool config_load(int argc, char * argv[])
|
||||
}
|
||||
|
||||
// setup the application params for the basic types
|
||||
params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
|
||||
params.framePollInterval = option_get_int ("app", "framePollInterval" );
|
||||
g_params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
|
||||
g_params.framePollInterval = option_get_int ("app", "framePollInterval" );
|
||||
g_params.allowDMA = option_get_bool ("app", "allowDMA" );
|
||||
|
||||
params.windowTitle = option_get_string("win", "title" );
|
||||
params.autoResize = option_get_bool ("win", "autoResize" );
|
||||
params.allowResize = option_get_bool ("win", "allowResize" );
|
||||
params.keepAspect = option_get_bool ("win", "keepAspect" );
|
||||
params.forceAspect = option_get_bool ("win", "forceAspect" );
|
||||
params.borderless = option_get_bool ("win", "borderless" );
|
||||
params.fullscreen = option_get_bool ("win", "fullScreen" );
|
||||
params.maximize = option_get_bool ("win", "maximize" );
|
||||
params.fpsLimit = option_get_int ("win", "fpsLimit" );
|
||||
params.showFPS = option_get_bool ("win", "showFPS" );
|
||||
params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
|
||||
params.noScreensaver = option_get_bool ("win", "noScreensaver");
|
||||
params.showAlerts = option_get_bool ("win", "alerts" );
|
||||
g_params.windowTitle = option_get_string("win", "title" );
|
||||
g_params.autoResize = option_get_bool ("win", "autoResize" );
|
||||
g_params.allowResize = option_get_bool ("win", "allowResize" );
|
||||
g_params.keepAspect = option_get_bool ("win", "keepAspect" );
|
||||
g_params.forceAspect = option_get_bool ("win", "forceAspect" );
|
||||
g_params.dontUpscale = option_get_bool ("win", "dontUpscale" );
|
||||
g_params.borderless = option_get_bool ("win", "borderless" );
|
||||
g_params.fullscreen = option_get_bool ("win", "fullScreen" );
|
||||
g_params.maximize = option_get_bool ("win", "maximize" );
|
||||
g_params.fpsMin = option_get_int ("win", "fpsMin" );
|
||||
g_params.showFPS = option_get_bool ("win", "showFPS" );
|
||||
g_params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
|
||||
g_params.noScreensaver = option_get_bool ("win", "noScreensaver" );
|
||||
g_params.autoScreensaver = option_get_bool ("win", "autoScreensaver");
|
||||
g_params.showAlerts = option_get_bool ("win", "alerts" );
|
||||
g_params.quickSplash = option_get_bool ("win", "quickSplash" );
|
||||
|
||||
params.grabKeyboard = option_get_bool ("input", "grabKeyboard");
|
||||
params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
||||
params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||
if (g_params.noScreensaver && g_params.autoScreensaver)
|
||||
{
|
||||
fprintf(stderr, "win:noScreensaver (-S) and win:autoScreensaver "
|
||||
"can't be used simultaneously\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
switch(option_get_int("win", "rotate"))
|
||||
{
|
||||
case 0 : g_params.winRotate = LG_ROTATE_0 ; break;
|
||||
case 90 : g_params.winRotate = LG_ROTATE_90 ; break;
|
||||
case 180: g_params.winRotate = LG_ROTATE_180; break;
|
||||
case 270: g_params.winRotate = LG_ROTATE_270; break;
|
||||
}
|
||||
|
||||
g_params.grabKeyboard = option_get_bool("input", "grabKeyboard" );
|
||||
g_params.grabKeyboardOnFocus = option_get_bool("input", "grabKeyboardOnFocus" );
|
||||
g_params.releaseKeysOnFocusLoss = option_get_bool("input", "releaseKeysOnFocusLoss");
|
||||
g_params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||
g_params.ignoreWindowsKeys = option_get_bool("input", "ignoreWindowsKeys" );
|
||||
g_params.hideMouse = option_get_bool("input", "hideCursor" );
|
||||
g_params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||
g_params.mouseSmoothing = option_get_bool("input", "mouseSmoothing" );
|
||||
g_params.rawMouse = option_get_bool("input", "rawMouse" );
|
||||
g_params.mouseRedraw = option_get_bool("input", "mouseRedraw" );
|
||||
g_params.autoCapture = option_get_bool("input", "autoCapture" );
|
||||
g_params.captureInputOnly = option_get_bool("input", "captureOnly" );
|
||||
|
||||
g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
|
||||
if (option_get_bool("spice", "enable"))
|
||||
{
|
||||
params.spiceHost = option_get_string("spice", "host");
|
||||
params.spicePort = option_get_int ("spice", "port");
|
||||
g_params.spiceHost = option_get_string("spice", "host");
|
||||
g_params.spicePort = option_get_int ("spice", "port");
|
||||
|
||||
params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
g_params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
|
||||
if (params.useSpiceClipboard)
|
||||
if (g_params.useSpiceClipboard)
|
||||
{
|
||||
params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
|
||||
if (!params.clipboardToVM && !params.clipboardToLocal)
|
||||
params.useSpiceClipboard = false;
|
||||
if (!g_params.clipboardToVM && !g_params.clipboardToLocal)
|
||||
g_params.useSpiceClipboard = false;
|
||||
}
|
||||
|
||||
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||
g_params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||
g_params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
||||
g_params.alwaysShowCursor = option_get_bool("spice", "alwaysShowCursor");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void config_free()
|
||||
void config_free(void)
|
||||
{
|
||||
option_free();
|
||||
}
|
||||
|
||||
static void doLicense()
|
||||
static void doLicense(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\n"
|
||||
@@ -449,17 +590,20 @@ static void doLicense()
|
||||
|
||||
static bool optRendererParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (strcasecmp(str, "auto") == 0)
|
||||
{
|
||||
params.forceRenderer = false;
|
||||
g_params.forceRenderer = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
||||
if (strcasecmp(str, LG_Renderers[i]->get_name()) == 0)
|
||||
{
|
||||
params.forceRenderer = true;
|
||||
params.forceRendererIndex = i;
|
||||
g_params.forceRenderer = true;
|
||||
g_params.forceRendererIndex = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -479,26 +623,29 @@ static StringList optRendererValues(struct Option * opt)
|
||||
|
||||
static char * optRendererToString(struct Option * opt)
|
||||
{
|
||||
if (!params.forceRenderer)
|
||||
if (!g_params.forceRenderer)
|
||||
return strdup("auto");
|
||||
|
||||
if (params.forceRendererIndex >= LG_RENDERER_COUNT)
|
||||
if (g_params.forceRendererIndex >= LG_RENDERER_COUNT)
|
||||
return NULL;
|
||||
|
||||
return strdup(LG_Renderers[params.forceRendererIndex]->get_name());
|
||||
return strdup(LG_Renderers[g_params.forceRendererIndex]->get_name());
|
||||
}
|
||||
|
||||
static bool optPosParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (strcmp(str, "center") == 0)
|
||||
{
|
||||
params.center = true;
|
||||
g_params.center = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sscanf(str, "%dx%d", ¶ms.x, ¶ms.y) == 2)
|
||||
if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2)
|
||||
{
|
||||
params.center = false;
|
||||
g_params.center = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -515,21 +662,24 @@ static StringList optPosValues(struct Option * opt)
|
||||
|
||||
static char * optPosToString(struct Option * opt)
|
||||
{
|
||||
if (params.center)
|
||||
if (g_params.center)
|
||||
return strdup("center");
|
||||
|
||||
int len = snprintf(NULL, 0, "%dx%d", params.x, params.y);
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", params.x, params.y);
|
||||
sprintf(str, "%dx%d", g_params.x, g_params.y);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool optSizeParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (sscanf(str, "%dx%d", ¶ms.w, ¶ms.h) == 2)
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (sscanf(str, "%dx%d", &g_params.w, &g_params.h) == 2)
|
||||
{
|
||||
if (params.w < 1 || params.h < 1)
|
||||
if (g_params.w < 1 || g_params.h < 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -546,16 +696,41 @@ static StringList optSizeValues(struct Option * opt)
|
||||
|
||||
static char * optSizeToString(struct Option * opt)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "%dx%d", params.w, params.h);
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.w, g_params.h);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", params.w, params.h);
|
||||
sprintf(str, "%dx%d", g_params.w, g_params.h);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool optScancodeValidate(struct Option * opt, const char ** error)
|
||||
{
|
||||
if (opt->value.x_int >= 0 && opt->value.x_int < KEY_MAX)
|
||||
return true;
|
||||
|
||||
*error = "Out of range";
|
||||
return false;
|
||||
}
|
||||
|
||||
static char * optScancodeToString(struct Option * opt)
|
||||
{
|
||||
char * str;
|
||||
alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int));
|
||||
alloc_sprintf(&str, "%d = %s", opt->value.x_int,
|
||||
xfree86_to_str[opt->value.x_int]);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
static bool optRotateValidate(struct Option * opt, const char ** error)
|
||||
{
|
||||
switch(opt->value.x_int)
|
||||
{
|
||||
case 0:
|
||||
case 90:
|
||||
case 180:
|
||||
case 270:
|
||||
return true;
|
||||
}
|
||||
|
||||
*error = "Rotation angle must be one of 0, 90, 180 or 270";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,4 +21,4 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
void config_init();
|
||||
bool config_load(int argc, char * argv[]);
|
||||
void config_free();
|
||||
void config_free();
|
||||
|
||||
499
client/src/core.c
Normal file
499
client/src/core.c
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "core.h"
|
||||
#include "main.h"
|
||||
#include "app.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "common/time.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
||||
|
||||
bool core_inputEnabled(void)
|
||||
{
|
||||
return g_params.useSpiceInput && !g_state.ignoreInput &&
|
||||
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly);
|
||||
}
|
||||
|
||||
void core_setCursorInView(bool enable)
|
||||
{
|
||||
// if the state has not changed, don't do anything else
|
||||
if (g_cursor.inView == enable)
|
||||
return;
|
||||
|
||||
if (enable && !g_state.focused)
|
||||
return;
|
||||
|
||||
// do not allow the view to become active if any mouse buttons are being held,
|
||||
// this fixes issues with meta window resizing.
|
||||
if (enable && g_cursor.buttons)
|
||||
return;
|
||||
|
||||
g_cursor.inView = enable;
|
||||
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
|
||||
? true : enable;
|
||||
g_cursor.redraw = true;
|
||||
|
||||
/* if the display server does not support warp, then we can not operate in
|
||||
* always relative mode and we should not grab the pointer */
|
||||
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
|
||||
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
||||
|
||||
g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
if (g_params.hideMouse)
|
||||
g_state.ds->showPointer(false);
|
||||
|
||||
if (warpSupport != LG_DS_WARP_NONE && !g_params.captureInputOnly)
|
||||
g_state.ds->grabPointer();
|
||||
|
||||
if (g_params.grabKeyboardOnFocus)
|
||||
g_state.ds->grabKeyboard();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_params.hideMouse)
|
||||
g_state.ds->showPointer(true);
|
||||
|
||||
if (warpSupport != LG_DS_WARP_NONE)
|
||||
g_state.ds->ungrabPointer();
|
||||
|
||||
g_state.ds->ungrabKeyboard();
|
||||
}
|
||||
|
||||
g_cursor.warpState = WARP_STATE_ON;
|
||||
}
|
||||
|
||||
void core_setGrab(bool enable)
|
||||
{
|
||||
core_setGrabQuiet(enable);
|
||||
|
||||
app_alert(
|
||||
g_cursor.grab ? LG_ALERT_SUCCESS : LG_ALERT_WARNING,
|
||||
g_cursor.grab ? "Capture Enabled" : "Capture Disabled"
|
||||
);
|
||||
}
|
||||
|
||||
void core_setGrabQuiet(bool enable)
|
||||
{
|
||||
/* we always do this so that at init the cursor is in the right state */
|
||||
if (g_params.captureInputOnly && g_params.hideMouse)
|
||||
g_state.ds->showPointer(!enable);
|
||||
|
||||
if (g_cursor.grab == enable)
|
||||
return;
|
||||
|
||||
g_cursor.grab = enable;
|
||||
g_cursor.acc.x = 0.0;
|
||||
g_cursor.acc.y = 0.0;
|
||||
|
||||
/* if the display server does not support warp we need to ungrab the pointer
|
||||
* here instead of in the move handler */
|
||||
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
|
||||
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
||||
|
||||
if (enable)
|
||||
{
|
||||
core_setCursorInView(true);
|
||||
g_state.ignoreInput = false;
|
||||
|
||||
if (g_params.grabKeyboard)
|
||||
g_state.ds->grabKeyboard();
|
||||
|
||||
g_state.ds->grabPointer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_params.grabKeyboard)
|
||||
{
|
||||
if (!g_params.grabKeyboardOnFocus ||
|
||||
!g_state.focused || g_params.captureInputOnly)
|
||||
g_state.ds->ungrabKeyboard();
|
||||
}
|
||||
|
||||
if (warpSupport != LG_DS_WARP_NONE || g_params.captureInputOnly || !g_state.formatValid)
|
||||
g_state.ds->ungrabPointer();
|
||||
|
||||
// if exiting capture when input on capture only, we want to show the cursor
|
||||
if (g_params.captureInputOnly || !g_params.hideMouse)
|
||||
core_alignToGuest();
|
||||
}
|
||||
}
|
||||
|
||||
bool core_warpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (!g_cursor.inWindow && !exiting)
|
||||
return false;
|
||||
|
||||
if (g_cursor.warpState == WARP_STATE_OFF)
|
||||
return false;
|
||||
|
||||
if (exiting)
|
||||
g_cursor.warpState = WARP_STATE_OFF;
|
||||
|
||||
if (g_cursor.pos.x == x && g_cursor.pos.y == y)
|
||||
return true;
|
||||
|
||||
g_state.ds->warpPointer(x, y, exiting);
|
||||
return true;
|
||||
}
|
||||
|
||||
void core_updatePositionInfo(void)
|
||||
{
|
||||
if (!g_state.haveSrcSize)
|
||||
goto done;
|
||||
|
||||
float srcW;
|
||||
float srcH;
|
||||
switch(g_params.winRotate)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
case LG_ROTATE_180:
|
||||
srcW = g_state.srcSize.x;
|
||||
srcH = g_state.srcSize.y;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
srcW = g_state.srcSize.y;
|
||||
srcH = g_state.srcSize.x;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"unreachable");
|
||||
}
|
||||
|
||||
if (g_params.keepAspect)
|
||||
{
|
||||
const float srcAspect = srcH / srcW;
|
||||
const float wndAspect = (float)g_state.windowH / (float)g_state.windowW;
|
||||
bool force = true;
|
||||
|
||||
if (g_params.dontUpscale &&
|
||||
srcW <= g_state.windowW &&
|
||||
srcH <= g_state.windowH)
|
||||
{
|
||||
force = false;
|
||||
g_state.dstRect.w = srcW;
|
||||
g_state.dstRect.h = srcH;
|
||||
g_state.dstRect.x = g_state.windowCX - srcW / 2;
|
||||
g_state.dstRect.y = g_state.windowCY - srcH / 2;
|
||||
}
|
||||
else
|
||||
if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000))
|
||||
{
|
||||
force = false;
|
||||
g_state.dstRect.w = g_state.windowW;
|
||||
g_state.dstRect.h = g_state.windowH;
|
||||
g_state.dstRect.x = 0;
|
||||
g_state.dstRect.y = 0;
|
||||
}
|
||||
else
|
||||
if (wndAspect < srcAspect)
|
||||
{
|
||||
g_state.dstRect.w = (float)g_state.windowH / srcAspect;
|
||||
g_state.dstRect.h = g_state.windowH;
|
||||
g_state.dstRect.x = (g_state.windowW >> 1) - (g_state.dstRect.w >> 1);
|
||||
g_state.dstRect.y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_state.dstRect.w = g_state.windowW;
|
||||
g_state.dstRect.h = (float)g_state.windowW * srcAspect;
|
||||
g_state.dstRect.x = 0;
|
||||
g_state.dstRect.y = (g_state.windowH >> 1) - (g_state.dstRect.h >> 1);
|
||||
}
|
||||
|
||||
if (force && g_params.forceAspect)
|
||||
{
|
||||
g_state.resizeTimeout = microtime() + RESIZE_TIMEOUT;
|
||||
g_state.resizeDone = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_state.dstRect.x = 0;
|
||||
g_state.dstRect.y = 0;
|
||||
g_state.dstRect.w = g_state.windowW;
|
||||
g_state.dstRect.h = g_state.windowH;
|
||||
}
|
||||
g_state.dstRect.valid = true;
|
||||
|
||||
g_cursor.useScale = (
|
||||
srcH != g_state.dstRect.h ||
|
||||
srcW != g_state.dstRect.w ||
|
||||
g_cursor.guest.dpiScale != 100);
|
||||
|
||||
g_cursor.scale.x = (float)srcW / (float)g_state.dstRect.w;
|
||||
g_cursor.scale.y = (float)srcH / (float)g_state.dstRect.h;
|
||||
g_cursor.dpiScale = g_cursor.guest.dpiScale / 100.0f;
|
||||
|
||||
if (!g_state.posInfoValid)
|
||||
{
|
||||
g_state.posInfoValid = true;
|
||||
g_state.ds->realignPointer();
|
||||
}
|
||||
|
||||
done:
|
||||
atomic_fetch_add(&g_state.lgrResize, 1);
|
||||
}
|
||||
|
||||
void core_alignToGuest(void)
|
||||
{
|
||||
if (!g_cursor.guest.valid || !g_state.focused)
|
||||
return;
|
||||
|
||||
struct DoublePoint local;
|
||||
if (util_guestCurToLocal(&local))
|
||||
if (core_warpPointer(round(local.x), round(local.y), false))
|
||||
core_setCursorInView(true);
|
||||
}
|
||||
|
||||
bool core_isValidPointerPos(int x, int y)
|
||||
{
|
||||
return g_state.ds->isValidPointerPos(x, y);
|
||||
}
|
||||
|
||||
bool core_startFrameThread(void)
|
||||
{
|
||||
if (g_state.frameThread)
|
||||
return true;
|
||||
|
||||
g_state.stopVideo = false;
|
||||
if (!lgCreateThread("frameThread", main_frameThread, NULL,
|
||||
&g_state.frameThread))
|
||||
{
|
||||
DEBUG_ERROR("frame create thread failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void core_stopFrameThread(void)
|
||||
{
|
||||
g_state.stopVideo = true;
|
||||
if (g_state.frameThread)
|
||||
lgJoinThread(g_state.frameThread, NULL);
|
||||
|
||||
g_state.frameThread = NULL;
|
||||
}
|
||||
|
||||
void core_handleMouseGrabbed(double ex, double ey)
|
||||
{
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
int x, y;
|
||||
if (g_params.rawMouse && !g_cursor.sens)
|
||||
{
|
||||
/* raw unscaled input are always round numbers */
|
||||
x = floor(ex);
|
||||
y = floor(ey);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* apply sensitivity */
|
||||
ex = (ex / 10.0) * (g_cursor.sens + 10);
|
||||
ey = (ey / 10.0) * (g_cursor.sens + 10);
|
||||
util_cursorToInt(ex, ey, &x, &y);
|
||||
}
|
||||
|
||||
if (x == 0 && y == 0)
|
||||
return;
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
static bool isInView(void)
|
||||
{
|
||||
return
|
||||
g_cursor.pos.x >= g_state.dstRect.x &&
|
||||
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
|
||||
g_cursor.pos.y >= g_state.dstRect.y &&
|
||||
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
|
||||
}
|
||||
|
||||
void core_handleMouseNormal(double ex, double ey)
|
||||
{
|
||||
// prevent cursor handling outside of capture if the position is not known
|
||||
if (!g_cursor.guest.valid)
|
||||
return;
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
/* scale the movement to the guest */
|
||||
if (g_cursor.useScale && g_params.scaleMouseInput)
|
||||
{
|
||||
ex *= g_cursor.scale.x;
|
||||
ey *= g_cursor.scale.y;
|
||||
}
|
||||
|
||||
bool testExit = true;
|
||||
if (!g_cursor.inView)
|
||||
{
|
||||
const bool inView = isInView();
|
||||
core_setCursorInView(inView);
|
||||
if (inView)
|
||||
g_cursor.realign = true;
|
||||
}
|
||||
|
||||
/* nothing to do if we are outside the viewport */
|
||||
if (!g_cursor.inView)
|
||||
return;
|
||||
|
||||
/*
|
||||
* do not pass mouse events to the guest if we do not have focus, this must be
|
||||
* done after the inView test has been performed so that when focus is gained
|
||||
* we know if we should be drawing the cursor.
|
||||
*/
|
||||
if (!g_state.focused)
|
||||
return;
|
||||
|
||||
/* if we have been instructed to realign */
|
||||
if (g_cursor.realign)
|
||||
{
|
||||
g_cursor.realign = false;
|
||||
|
||||
struct DoublePoint guest;
|
||||
util_localCurToGuest(&guest);
|
||||
|
||||
/* add the difference to the offset */
|
||||
ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx);
|
||||
ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy);
|
||||
|
||||
/* don't test for an exit as we just entered, we can get into a enter/exit
|
||||
* loop otherwise */
|
||||
testExit = false;
|
||||
}
|
||||
|
||||
/* if we are in "autoCapture" and the delta was large don't test for exit */
|
||||
if (g_params.autoCapture &&
|
||||
(fabs(ex) > 100.0 / g_cursor.scale.x || fabs(ey) > 100.0 / g_cursor.scale.y))
|
||||
testExit = false;
|
||||
|
||||
/* if any buttons are held we should not allow exit to happen */
|
||||
if (g_cursor.buttons)
|
||||
testExit = false;
|
||||
|
||||
if (testExit)
|
||||
{
|
||||
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
|
||||
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
||||
|
||||
/* translate the move to the guests orientation */
|
||||
struct DoublePoint move = {.x = ex, .y = ey};
|
||||
util_rotatePoint(&move);
|
||||
|
||||
/* translate the guests position to our coordinate space */
|
||||
struct DoublePoint local;
|
||||
util_guestCurToLocal(&local);
|
||||
|
||||
/* check if the move would push the cursor outside the guest's viewport */
|
||||
if (
|
||||
local.x + move.x < g_state.dstRect.x ||
|
||||
local.y + move.y < g_state.dstRect.y ||
|
||||
local.x + move.x >= g_state.dstRect.x + g_state.dstRect.w ||
|
||||
local.y + move.y >= g_state.dstRect.y + g_state.dstRect.h)
|
||||
{
|
||||
local.x += move.x;
|
||||
local.y += move.y;
|
||||
const int tx = (local.x <= 0.0) ? floor(local.x) : ceil(local.x);
|
||||
const int ty = (local.y <= 0.0) ? floor(local.y) : ceil(local.y);
|
||||
|
||||
switch (warpSupport)
|
||||
{
|
||||
case LG_DS_WARP_NONE:
|
||||
break;
|
||||
|
||||
case LG_DS_WARP_SURFACE:
|
||||
g_state.ds->ungrabPointer();
|
||||
core_warpPointer(tx, ty, true);
|
||||
|
||||
if (!isInView() && tx >= 0 && tx < g_state.windowW && ty >= 0 && ty < g_state.windowH)
|
||||
core_setCursorInView(false);
|
||||
break;
|
||||
|
||||
case LG_DS_WARP_SCREEN:
|
||||
if (core_isValidPointerPos(
|
||||
g_state.windowPos.x + g_state.border.left + tx,
|
||||
g_state.windowPos.y + g_state.border.top + ty))
|
||||
{
|
||||
core_setCursorInView(false);
|
||||
|
||||
/* preempt the window leave flag if the warp will leave our window */
|
||||
if (tx < 0 || ty < 0 || tx > g_state.windowW || ty > g_state.windowH)
|
||||
g_cursor.inWindow = false;
|
||||
|
||||
/* ungrab the pointer and move the local cursor to the exit point */
|
||||
g_state.ds->ungrabPointer();
|
||||
core_warpPointer(tx, ty, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (warpSupport == LG_DS_WARP_SURFACE && isInView())
|
||||
{
|
||||
/* regrab the pointer in case the user did not move off the surface */
|
||||
g_state.ds->grabPointer();
|
||||
g_cursor.warpState = WARP_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
int x, y;
|
||||
util_cursorToInt(ex, ey, &x, &y);
|
||||
|
||||
if (x == 0 && y == 0)
|
||||
return;
|
||||
|
||||
if (g_params.autoCapture)
|
||||
{
|
||||
g_cursor.delta.x += x;
|
||||
g_cursor.delta.y += y;
|
||||
|
||||
if (fabs(g_cursor.delta.x) > 50.0 || fabs(g_cursor.delta.y) > 50.0)
|
||||
{
|
||||
g_cursor.delta.x = 0;
|
||||
g_cursor.delta.y = 0;
|
||||
core_warpPointer(g_state.windowCX, g_state.windowCY, false);
|
||||
}
|
||||
|
||||
g_cursor.guest.x = g_state.srcSize.x / 2;
|
||||
g_cursor.guest.y = g_state.srcSize.y / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* assume the mouse will move to the location we attempt to move it to so we
|
||||
* avoid warp out of window issues. The cursorThread will correct this if
|
||||
* wrong after the movement has ocurred on the guest */
|
||||
g_cursor.guest.x += x;
|
||||
g_cursor.guest.y += y;
|
||||
}
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
39
client/src/core.h
Normal file
39
client/src/core.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_CORE_
|
||||
#define _H_LG_CORE_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool core_inputEnabled(void);
|
||||
void core_setCursorInView(bool enable);
|
||||
void core_setGrab(bool enable);
|
||||
void core_setGrabQuiet(bool enable);
|
||||
bool core_warpPointer(int x, int y, bool exiting);
|
||||
void core_updatePositionInfo(void);
|
||||
void core_alignToGuest(void);
|
||||
bool core_isValidPointerPos(int x, int y);
|
||||
bool core_startFrameThread(void);
|
||||
void core_stopFrameThread(void);
|
||||
void core_handleMouseGrabbed(double ex, double ey);
|
||||
void core_handleMouseNormal(double ex, double ey);
|
||||
|
||||
|
||||
#endif
|
||||
36
client/src/egl_dynprocs.c
Normal file
36
client/src/egl_dynprocs.c
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
#include "egl_dynprocs.h"
|
||||
|
||||
struct EGLDynProcs g_egl_dynProcs = {0};
|
||||
|
||||
void egl_dynProcsInit(void)
|
||||
{
|
||||
g_egl_dynProcs.eglGetPlatformDisplay = (eglGetPlatformDisplayEXT_t)
|
||||
eglGetProcAddress("eglGetPlatformDisplay");
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_t)
|
||||
eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||
g_egl_dynProcs.glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOES_t)
|
||||
eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
||||
};
|
||||
|
||||
#endif
|
||||
394
client/src/kb.c
Normal file
394
client/src/kb.c
Normal file
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_KB_
|
||||
#define _H_LG_KB_
|
||||
|
||||
#include "kb.h"
|
||||
|
||||
const uint32_t xfree86_to_ps2[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] /* = USB 0 */ = 0x000000,
|
||||
[KEY_ESC] /* = USB 41 */ = 0x000001,
|
||||
[KEY_1] /* = USB 30 */ = 0x000002,
|
||||
[KEY_2] /* = USB 31 */ = 0x000003,
|
||||
[KEY_3] /* = USB 32 */ = 0x000004,
|
||||
[KEY_4] /* = USB 33 */ = 0x000005,
|
||||
[KEY_5] /* = USB 34 */ = 0x000006,
|
||||
[KEY_6] /* = USB 35 */ = 0x000007,
|
||||
[KEY_7] /* = USB 36 */ = 0x000008,
|
||||
[KEY_8] /* = USB 37 */ = 0x000009,
|
||||
[KEY_9] /* = USB 38 */ = 0x00000A,
|
||||
[KEY_0] /* = USB 39 */ = 0x00000B,
|
||||
[KEY_MINUS] /* = USB 45 */ = 0x00000C,
|
||||
[KEY_EQUAL] /* = USB 46 */ = 0x00000D,
|
||||
[KEY_BACKSPACE] /* = USB 42 */ = 0x00000E,
|
||||
[KEY_TAB] /* = USB 43 */ = 0x00000F,
|
||||
[KEY_Q] /* = USB 20 */ = 0x000010,
|
||||
[KEY_W] /* = USB 26 */ = 0x000011,
|
||||
[KEY_E] /* = USB 8 */ = 0x000012,
|
||||
[KEY_R] /* = USB 21 */ = 0x000013,
|
||||
[KEY_T] /* = USB 23 */ = 0x000014,
|
||||
[KEY_Y] /* = USB 28 */ = 0x000015,
|
||||
[KEY_U] /* = USB 24 */ = 0x000016,
|
||||
[KEY_I] /* = USB 12 */ = 0x000017,
|
||||
[KEY_O] /* = USB 18 */ = 0x000018,
|
||||
[KEY_P] /* = USB 19 */ = 0x000019,
|
||||
[KEY_LEFTBRACE] /* = USB 47 */ = 0x00001A,
|
||||
[KEY_RIGHTBRACE] /* = USB 48 */ = 0x00001B,
|
||||
[KEY_ENTER] /* = USB 40 */ = 0x00001C,
|
||||
[KEY_LEFTCTRL] /* = USB 224 */ = 0x00001D,
|
||||
[KEY_A] /* = USB 4 */ = 0x00001E,
|
||||
[KEY_S] /* = USB 22 */ = 0x00001F,
|
||||
[KEY_D] /* = USB 7 */ = 0x000020,
|
||||
[KEY_F] /* = USB 9 */ = 0x000021,
|
||||
[KEY_G] /* = USB 10 */ = 0x000022,
|
||||
[KEY_H] /* = USB 11 */ = 0x000023,
|
||||
[KEY_J] /* = USB 13 */ = 0x000024,
|
||||
[KEY_K] /* = USB 14 */ = 0x000025,
|
||||
[KEY_L] /* = USB 15 */ = 0x000026,
|
||||
[KEY_SEMICOLON] /* = USB 51 */ = 0x000027,
|
||||
[KEY_APOSTROPHE] /* = USB 52 */ = 0x000028,
|
||||
[KEY_GRAVE] /* = USB 53 */ = 0x000029,
|
||||
[KEY_LEFTSHIFT] /* = USB 225 */ = 0x00002A,
|
||||
[KEY_BACKSLASH] /* = USB 49 */ = 0x00002B,
|
||||
[KEY_Z] /* = USB 29 */ = 0x00002C,
|
||||
[KEY_X] /* = USB 27 */ = 0x00002D,
|
||||
[KEY_C] /* = USB 6 */ = 0x00002E,
|
||||
[KEY_V] /* = USB 25 */ = 0x00002F,
|
||||
[KEY_B] /* = USB 5 */ = 0x000030,
|
||||
[KEY_N] /* = USB 17 */ = 0x000031,
|
||||
[KEY_M] /* = USB 16 */ = 0x000032,
|
||||
[KEY_COMMA] /* = USB 54 */ = 0x000033,
|
||||
[KEY_DOT] /* = USB 55 */ = 0x000034,
|
||||
[KEY_SLASH] /* = USB 56 */ = 0x000035,
|
||||
[KEY_RIGHTSHIFT] /* = USB 229 */ = 0x000036,
|
||||
[KEY_KPASTERISK] /* = USB 85 */ = 0x000037,
|
||||
[KEY_LEFTALT] /* = USB 226 */ = 0x000038,
|
||||
[KEY_SPACE] /* = USB 44 */ = 0x000039,
|
||||
[KEY_CAPSLOCK] /* = USB 57 */ = 0x00003A,
|
||||
[KEY_F1] /* = USB 58 */ = 0x00003B,
|
||||
[KEY_F2] /* = USB 59 */ = 0x00003C,
|
||||
[KEY_F3] /* = USB 60 */ = 0x00003D,
|
||||
[KEY_F4] /* = USB 61 */ = 0x00003E,
|
||||
[KEY_F5] /* = USB 62 */ = 0x00003F,
|
||||
[KEY_F6] /* = USB 63 */ = 0x000040,
|
||||
[KEY_F7] /* = USB 64 */ = 0x000041,
|
||||
[KEY_F8] /* = USB 65 */ = 0x000042,
|
||||
[KEY_F9] /* = USB 66 */ = 0x000043,
|
||||
[KEY_F10] /* = USB 67 */ = 0x000044,
|
||||
[KEY_NUMLOCK] /* = USB 83 */ = 0x000045,
|
||||
[KEY_SCROLLLOCK] /* = USB 71 */ = 0x000046,
|
||||
[KEY_KP7] /* = USB 95 */ = 0x000047,
|
||||
[KEY_KP8] /* = USB 96 */ = 0x000048,
|
||||
[KEY_KP9] /* = USB 97 */ = 0x000049,
|
||||
[KEY_KPMINUS] /* = USB 86 */ = 0x00004A,
|
||||
[KEY_KP4] /* = USB 92 */ = 0x00004B,
|
||||
[KEY_KP5] /* = USB 93 */ = 0x00004C,
|
||||
[KEY_KP6] /* = USB 94 */ = 0x00004D,
|
||||
[KEY_KPPLUS] /* = USB 87 */ = 0x00004E,
|
||||
[KEY_KP1] /* = USB 89 */ = 0x00004F,
|
||||
[KEY_KP2] /* = USB 90 */ = 0x000050,
|
||||
[KEY_KP3] /* = USB 91 */ = 0x000051,
|
||||
[KEY_KP0] /* = USB 98 */ = 0x000052,
|
||||
[KEY_KPDOT] /* = USB 99 */ = 0x000053,
|
||||
[KEY_102ND] /* = USB 100 */ = 0x000056,
|
||||
[KEY_F11] /* = USB 68 */ = 0x000057,
|
||||
[KEY_F12] /* = USB 69 */ = 0x000058,
|
||||
[KEY_RO] /* = USB 135 */ = 0x000073,
|
||||
[KEY_HENKAN] /* = USB 138 */ = 0x000079,
|
||||
[KEY_KATAKANAHIRAGANA] /* = USB 136 */ = 0x000070,
|
||||
[KEY_MUHENKAN] /* = USB 139 */ = 0x00007B,
|
||||
[KEY_KPENTER] /* = USB 88 */ = 0x00E01C,
|
||||
[KEY_RIGHTCTRL] /* = USB 228 */ = 0x00E01D,
|
||||
[KEY_KPSLASH] /* = USB 84 */ = 0x00E035,
|
||||
[KEY_SYSRQ] /* = USB 70 */ = 0x00E037,
|
||||
[KEY_RIGHTALT] /* = USB 230 */ = 0x00E038,
|
||||
[KEY_HOME] /* = USB 74 */ = 0x00E047,
|
||||
[KEY_UP] /* = USB 82 */ = 0x00E048,
|
||||
[KEY_PAGEUP] /* = USB 75 */ = 0x00E049,
|
||||
[KEY_LEFT] /* = USB 80 */ = 0x00E04B,
|
||||
[KEY_RIGHT] /* = USB 79 */ = 0x00E04D,
|
||||
[KEY_END] /* = USB 77 */ = 0x00E04F,
|
||||
[KEY_DOWN] /* = USB 81 */ = 0x00E050,
|
||||
[KEY_PAGEDOWN] /* = USB 78 */ = 0x00E051,
|
||||
[KEY_INSERT] /* = USB 73 */ = 0x00E052,
|
||||
[KEY_DELETE] /* = USB 76 */ = 0x00E053,
|
||||
[KEY_KPEQUAL] /* = USB 103 */ = 0x000059,
|
||||
[KEY_PAUSE] /* = USB 72 */ = 0x00E046,
|
||||
[KEY_KPCOMMA] /* = USB 133 */ = 0x00007E,
|
||||
[KEY_HANGEUL] /* = USB 144 */ = 0x0000F2,
|
||||
[KEY_HANJA] /* = USB 145 */ = 0x0000F1,
|
||||
[KEY_YEN] /* = USB 137 */ = 0x00007D,
|
||||
[KEY_LEFTMETA] /* = USB 227 */ = 0x00E05B,
|
||||
[KEY_RIGHTMETA] /* = USB 231 */ = 0x00E05C,
|
||||
[KEY_COMPOSE] /* = USB 101 */ = 0x00E05D,
|
||||
[KEY_F13] /* = USB 104 */ = 0x00005D,
|
||||
[KEY_F14] /* = USB 105 */ = 0x00005E,
|
||||
[KEY_F15] /* = USB 106 */ = 0x00005F,
|
||||
[KEY_PRINT] /* = USB 70 */ = 0x00E037,
|
||||
};
|
||||
|
||||
const char * xfree86_to_str[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] = "KEY_RESERVED",
|
||||
[KEY_ESC] = "KEY_ESC",
|
||||
[KEY_1] = "KEY_1",
|
||||
[KEY_2] = "KEY_2",
|
||||
[KEY_3] = "KEY_3",
|
||||
[KEY_4] = "KEY_4",
|
||||
[KEY_5] = "KEY_5",
|
||||
[KEY_6] = "KEY_6",
|
||||
[KEY_7] = "KEY_7",
|
||||
[KEY_8] = "KEY_8",
|
||||
[KEY_9] = "KEY_9",
|
||||
[KEY_0] = "KEY_0",
|
||||
[KEY_MINUS] = "KEY_MINUS",
|
||||
[KEY_EQUAL] = "KEY_EQUAL",
|
||||
[KEY_BACKSPACE] = "KEY_BACKSPACE",
|
||||
[KEY_TAB] = "KEY_TAB",
|
||||
[KEY_Q] = "KEY_Q",
|
||||
[KEY_W] = "KEY_W",
|
||||
[KEY_E] = "KEY_E",
|
||||
[KEY_R] = "KEY_R",
|
||||
[KEY_T] = "KEY_T",
|
||||
[KEY_Y] = "KEY_Y",
|
||||
[KEY_U] = "KEY_U",
|
||||
[KEY_I] = "KEY_I",
|
||||
[KEY_O] = "KEY_O",
|
||||
[KEY_P] = "KEY_P",
|
||||
[KEY_LEFTBRACE] = "KEY_LEFTBRACE",
|
||||
[KEY_RIGHTBRACE] = "KEY_RIGHTBRACE",
|
||||
[KEY_ENTER] = "KEY_ENTER",
|
||||
[KEY_LEFTCTRL] = "KEY_LEFTCTRL",
|
||||
[KEY_A] = "KEY_A",
|
||||
[KEY_S] = "KEY_S",
|
||||
[KEY_D] = "KEY_D",
|
||||
[KEY_F] = "KEY_F",
|
||||
[KEY_G] = "KEY_G",
|
||||
[KEY_H] = "KEY_H",
|
||||
[KEY_J] = "KEY_J",
|
||||
[KEY_K] = "KEY_K",
|
||||
[KEY_L] = "KEY_L",
|
||||
[KEY_SEMICOLON] = "KEY_SEMICOLON",
|
||||
[KEY_APOSTROPHE] = "KEY_APOSTROPHE",
|
||||
[KEY_GRAVE] = "KEY_GRAVE",
|
||||
[KEY_LEFTSHIFT] = "KEY_LEFTSHIFT",
|
||||
[KEY_BACKSLASH] = "KEY_BACKSLASH",
|
||||
[KEY_Z] = "KEY_Z",
|
||||
[KEY_X] = "KEY_X",
|
||||
[KEY_C] = "KEY_C",
|
||||
[KEY_V] = "KEY_V",
|
||||
[KEY_B] = "KEY_B",
|
||||
[KEY_N] = "KEY_N",
|
||||
[KEY_M] = "KEY_M",
|
||||
[KEY_COMMA] = "KEY_COMMA",
|
||||
[KEY_DOT] = "KEY_DOT",
|
||||
[KEY_SLASH] = "KEY_SLASH",
|
||||
[KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT",
|
||||
[KEY_KPASTERISK] = "KEY_KPASTERISK",
|
||||
[KEY_LEFTALT] = "KEY_LEFTALT",
|
||||
[KEY_SPACE] = "KEY_SPACE",
|
||||
[KEY_CAPSLOCK] = "KEY_CAPSLOCK",
|
||||
[KEY_F1] = "KEY_F1",
|
||||
[KEY_F2] = "KEY_F2",
|
||||
[KEY_F3] = "KEY_F3",
|
||||
[KEY_F4] = "KEY_F4",
|
||||
[KEY_F5] = "KEY_F5",
|
||||
[KEY_F6] = "KEY_F6",
|
||||
[KEY_F7] = "KEY_F7",
|
||||
[KEY_F8] = "KEY_F8",
|
||||
[KEY_F9] = "KEY_F9",
|
||||
[KEY_F10] = "KEY_F10",
|
||||
[KEY_NUMLOCK] = "KEY_NUMLOCK",
|
||||
[KEY_SCROLLLOCK] = "KEY_SCROLLLOCK",
|
||||
[KEY_KP7] = "KEY_KP7",
|
||||
[KEY_KP8] = "KEY_KP8",
|
||||
[KEY_KP9] = "KEY_KP9",
|
||||
[KEY_KPMINUS] = "KEY_KPMINUS",
|
||||
[KEY_KP4] = "KEY_KP4",
|
||||
[KEY_KP5] = "KEY_KP5",
|
||||
[KEY_KP6] = "KEY_KP6",
|
||||
[KEY_KPPLUS] = "KEY_KPPLUS",
|
||||
[KEY_KP1] = "KEY_KP1",
|
||||
[KEY_KP2] = "KEY_KP2",
|
||||
[KEY_KP3] = "KEY_KP3",
|
||||
[KEY_KP0] = "KEY_KP0",
|
||||
[KEY_KPDOT] = "KEY_KPDOT",
|
||||
[KEY_102ND] = "KEY_102ND",
|
||||
[KEY_F11] = "KEY_F11",
|
||||
[KEY_F12] = "KEY_F12",
|
||||
[KEY_RO] = "KEY_RO",
|
||||
[KEY_HENKAN] = "KEY_HENKAN",
|
||||
[KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA",
|
||||
[KEY_MUHENKAN] = "KEY_MUHENKAN",
|
||||
[KEY_KPENTER] = "KEY_KPENTER",
|
||||
[KEY_RIGHTCTRL] = "KEY_RIGHTCTRL",
|
||||
[KEY_KPSLASH] = "KEY_KPSLASH",
|
||||
[KEY_SYSRQ] = "KEY_SYSRQ",
|
||||
[KEY_RIGHTALT] = "KEY_RIGHTALT",
|
||||
[KEY_HOME] = "KEY_HOME",
|
||||
[KEY_UP] = "KEY_UP",
|
||||
[KEY_PAGEUP] = "KEY_PAGEUP",
|
||||
[KEY_LEFT] = "KEY_LEFT",
|
||||
[KEY_RIGHT] = "KEY_RIGHT",
|
||||
[KEY_END] = "KEY_END",
|
||||
[KEY_DOWN] = "KEY_DOWN",
|
||||
[KEY_PAGEDOWN] = "KEY_PAGEDOWN",
|
||||
[KEY_INSERT] = "KEY_INSERT",
|
||||
[KEY_DELETE] = "KEY_DELETE",
|
||||
[KEY_KPEQUAL] = "KEY_KPEQUAL",
|
||||
[KEY_PAUSE] = "KEY_PAUSE",
|
||||
[KEY_KPCOMMA] = "KEY_KPCOMMA",
|
||||
[KEY_HANGEUL] = "KEY_HANGEUL",
|
||||
[KEY_HANJA] = "KEY_HANJA",
|
||||
[KEY_YEN] = "KEY_YEN",
|
||||
[KEY_LEFTMETA] = "KEY_LEFTMETA",
|
||||
[KEY_RIGHTMETA] = "KEY_RIGHTMETA",
|
||||
[KEY_COMPOSE] = "KEY_COMPOSE",
|
||||
[KEY_F13] = "KEY_F13",
|
||||
[KEY_F14] = "KEY_F14",
|
||||
[KEY_F15] = "KEY_F15",
|
||||
[KEY_PRINT] = "KEY_PRINT",
|
||||
};
|
||||
|
||||
const char * xfree86_to_display[KEY_MAX] =
|
||||
{
|
||||
[KEY_RESERVED] = "Reserved",
|
||||
[KEY_ESC] = "Esc",
|
||||
[KEY_1] = "1",
|
||||
[KEY_2] = "2",
|
||||
[KEY_3] = "3",
|
||||
[KEY_4] = "4",
|
||||
[KEY_5] = "5",
|
||||
[KEY_6] = "6",
|
||||
[KEY_7] = "7",
|
||||
[KEY_8] = "8",
|
||||
[KEY_9] = "9",
|
||||
[KEY_0] = "0",
|
||||
[KEY_MINUS] = "-",
|
||||
[KEY_EQUAL] = "=",
|
||||
[KEY_BACKSPACE] = "Backspace",
|
||||
[KEY_TAB] = "Tab",
|
||||
[KEY_Q] = "Q",
|
||||
[KEY_W] = "W",
|
||||
[KEY_E] = "E",
|
||||
[KEY_R] = "R",
|
||||
[KEY_T] = "T",
|
||||
[KEY_Y] = "Y",
|
||||
[KEY_U] = "U",
|
||||
[KEY_I] = "I",
|
||||
[KEY_O] = "O",
|
||||
[KEY_P] = "P",
|
||||
[KEY_LEFTBRACE] = "{",
|
||||
[KEY_RIGHTBRACE] = "}",
|
||||
[KEY_ENTER] = "Enter",
|
||||
[KEY_LEFTCTRL] = "LCtrl",
|
||||
[KEY_A] = "A",
|
||||
[KEY_S] = "S",
|
||||
[KEY_D] = "D",
|
||||
[KEY_F] = "F",
|
||||
[KEY_G] = "G",
|
||||
[KEY_H] = "H",
|
||||
[KEY_J] = "J",
|
||||
[KEY_K] = "K",
|
||||
[KEY_L] = "L",
|
||||
[KEY_SEMICOLON] = ";",
|
||||
[KEY_APOSTROPHE] = "'",
|
||||
[KEY_GRAVE] = "`",
|
||||
[KEY_LEFTSHIFT] = "LShift",
|
||||
[KEY_BACKSLASH] = "\\",
|
||||
[KEY_Z] = "Z",
|
||||
[KEY_X] = "X",
|
||||
[KEY_C] = "C",
|
||||
[KEY_V] = "V",
|
||||
[KEY_B] = "B",
|
||||
[KEY_N] = "N",
|
||||
[KEY_M] = "M",
|
||||
[KEY_COMMA] = ",",
|
||||
[KEY_DOT] = ".",
|
||||
[KEY_SLASH] = "/",
|
||||
[KEY_RIGHTSHIFT] = "RShift",
|
||||
[KEY_KPASTERISK] = "*",
|
||||
[KEY_LEFTALT] = "LAlt",
|
||||
[KEY_SPACE] = "Space",
|
||||
[KEY_CAPSLOCK] = "CapsLock",
|
||||
[KEY_F1] = "F1",
|
||||
[KEY_F2] = "F2",
|
||||
[KEY_F3] = "F3",
|
||||
[KEY_F4] = "F4",
|
||||
[KEY_F5] = "F5",
|
||||
[KEY_F6] = "F6",
|
||||
[KEY_F7] = "F7",
|
||||
[KEY_F8] = "F8",
|
||||
[KEY_F9] = "F9",
|
||||
[KEY_F10] = "F10",
|
||||
[KEY_NUMLOCK] = "NumLock",
|
||||
[KEY_SCROLLLOCK] = "ScrollLock",
|
||||
[KEY_KP7] = "KP7",
|
||||
[KEY_KP8] = "KP8",
|
||||
[KEY_KP9] = "KP9",
|
||||
[KEY_KPMINUS] = "KPMinus",
|
||||
[KEY_KP4] = "KP4",
|
||||
[KEY_KP5] = "KP5",
|
||||
[KEY_KP6] = "KP6",
|
||||
[KEY_KPPLUS] = "KPPlus",
|
||||
[KEY_KP1] = "KP1",
|
||||
[KEY_KP2] = "KP2",
|
||||
[KEY_KP3] = "KP3",
|
||||
[KEY_KP0] = "KP0",
|
||||
[KEY_KPDOT] = "KPDOT",
|
||||
[KEY_102ND] = "102ND",
|
||||
[KEY_F11] = "F11",
|
||||
[KEY_F12] = "F12",
|
||||
[KEY_RO] = "RO",
|
||||
[KEY_HENKAN] = "Henkan",
|
||||
[KEY_KATAKANAHIRAGANA] = "Kana",
|
||||
[KEY_MUHENKAN] = "Muhenkan",
|
||||
[KEY_KPENTER] = "KPEnter",
|
||||
[KEY_RIGHTCTRL] = "RCtrl",
|
||||
[KEY_KPSLASH] = "KPSlash",
|
||||
[KEY_SYSRQ] = "SysRQ",
|
||||
[KEY_RIGHTALT] = "RAlt",
|
||||
[KEY_HOME] = "Home",
|
||||
[KEY_UP] = "↑",
|
||||
[KEY_PAGEUP] = "PageUp",
|
||||
[KEY_LEFT] = "←",
|
||||
[KEY_RIGHT] = "→",
|
||||
[KEY_END] = "End",
|
||||
[KEY_DOWN] = "↓",
|
||||
[KEY_PAGEDOWN] = "PageDown",
|
||||
[KEY_INSERT] = "Insert",
|
||||
[KEY_DELETE] = "Delete",
|
||||
[KEY_KPEQUAL] = "KPEqual",
|
||||
[KEY_PAUSE] = "Pause",
|
||||
[KEY_KPCOMMA] = "KPComma",
|
||||
[KEY_HANGEUL] = "Hangul",
|
||||
[KEY_HANJA] = "Hanja",
|
||||
[KEY_YEN] = "Yen",
|
||||
[KEY_LEFTMETA] = "LWin",
|
||||
[KEY_RIGHTMETA] = "RWin",
|
||||
[KEY_COMPOSE] = "Compose",
|
||||
[KEY_F13] = "F13",
|
||||
[KEY_F14] = "F14",
|
||||
[KEY_F15] = "F15",
|
||||
[KEY_PRINT] = "Print",
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
@@ -17,40 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
static uint32_t usb_to_ps2[] =
|
||||
{
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x00001e, 0x000030, 0x00002e,
|
||||
0x000020, 0x000012, 0x000021, 0x000022, 0x000023, 0x000017, 0x000024,
|
||||
0x000025, 0x000026, 0x000032, 0x000031, 0x000018, 0x000019, 0x000010,
|
||||
0x000013, 0x00001f, 0x000014, 0x000016, 0x00002f, 0x000011, 0x00002d,
|
||||
0x000015, 0x00002c, 0x000002, 0x000003, 0x000004, 0x000005, 0x000006,
|
||||
0x000007, 0x000008, 0x000009, 0x00000a, 0x00000b, 0x00001c, 0x000001,
|
||||
0x00000e, 0x00000f, 0x000039, 0x00000c, 0x00000d, 0x00001a, 0x00001b,
|
||||
0x00002b, 0x00002b, 0x000027, 0x000028, 0x000029, 0x000033, 0x000034,
|
||||
0x000035, 0x00003a, 0x00003b, 0x00003c, 0x00003d, 0x00003e, 0x00003f,
|
||||
0x000040, 0x000041, 0x000042, 0x000043, 0x000044, 0x000057, 0x000058,
|
||||
0x00e037, 0x000046, 0x00e046, 0x00e052, 0x00e047, 0x00e049, 0x00e053,
|
||||
0x00e04f, 0x00e051, 0x00e04d, 0x00e04b, 0x00e050, 0x00e048, 0x000045,
|
||||
0x00e035, 0x000037, 0x00004a, 0x00004e, 0x00e01c, 0x00004f, 0x000050,
|
||||
0x000051, 0x00004b, 0x00004c, 0x00004d, 0x000047, 0x000048, 0x000049,
|
||||
0x000052, 0x000053, 0x000056, 0x00e05d, 0x000000, 0x000059, 0x00005d,
|
||||
0x00005e, 0x00005f, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x00007e, 0x000000, 0x000073, 0x000070, 0x00007d, 0x000079, 0x00007b,
|
||||
0x00005c, 0x000000, 0x000000, 0x000000, 0x0000f2, 0x0000f1, 0x000078,
|
||||
0x000077, 0x000076, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x00001d, 0x00002a, 0x000038, 0x00e05b, 0x00e01d, 0x000036, 0x00e038,
|
||||
0x00e05c
|
||||
};
|
||||
#include <linux/input.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern const uint32_t xfree86_to_ps2[KEY_MAX];
|
||||
extern const char * xfree86_to_str[KEY_MAX];
|
||||
extern const char * xfree86_to_display[KEY_MAX];
|
||||
|
||||
161
client/src/keybind.c
Normal file
161
client/src/keybind.c
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "keybind.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "app.h"
|
||||
#include "core.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "spice/spice.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static void bind_fullscreen(int sc, void * opaque)
|
||||
{
|
||||
app_setFullscreen(!app_getFullscreen());
|
||||
}
|
||||
|
||||
static void bind_video(int sc, void * opaque)
|
||||
{
|
||||
g_state.stopVideo = !g_state.stopVideo;
|
||||
app_alert(
|
||||
LG_ALERT_INFO,
|
||||
g_state.stopVideo ? "Video Stream Disabled" : "Video Stream Enabled"
|
||||
);
|
||||
|
||||
if (g_state.stopVideo)
|
||||
core_stopFrameThread();
|
||||
else
|
||||
core_startFrameThread();
|
||||
}
|
||||
|
||||
static void bind_showFPS(int sc, void * opaque)
|
||||
{
|
||||
g_state.showFPS = !g_state.showFPS;
|
||||
app_showFPS(g_state.showFPS);
|
||||
}
|
||||
|
||||
static void bind_rotate(int sc, void * opaque)
|
||||
{
|
||||
if (g_params.winRotate == LG_ROTATE_MAX-1)
|
||||
g_params.winRotate = 0;
|
||||
else
|
||||
++g_params.winRotate;
|
||||
core_updatePositionInfo();
|
||||
}
|
||||
|
||||
static void bind_input(int sc, void * opaque)
|
||||
{
|
||||
g_state.ignoreInput = !g_state.ignoreInput;
|
||||
|
||||
if (g_state.ignoreInput)
|
||||
core_setCursorInView(false);
|
||||
else
|
||||
g_state.ds->realignPointer();
|
||||
|
||||
app_alert(
|
||||
LG_ALERT_INFO,
|
||||
g_state.ignoreInput ? "Input Disabled" : "Input Enabled"
|
||||
);
|
||||
}
|
||||
|
||||
static void bind_quit(int sc, void * opaque)
|
||||
{
|
||||
g_state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
static void bind_mouseSens(int sc, void * opaque)
|
||||
{
|
||||
bool inc = (bool)opaque;
|
||||
|
||||
if (inc)
|
||||
{
|
||||
if (g_cursor.sens < 9)
|
||||
++g_cursor.sens;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_cursor.sens > -9)
|
||||
--g_cursor.sens;
|
||||
}
|
||||
|
||||
char msg[20];
|
||||
snprintf(msg, sizeof(msg), "Sensitivity: %s%d",
|
||||
g_cursor.sens > 0 ? "+" : "", g_cursor.sens);
|
||||
|
||||
app_alert(
|
||||
LG_ALERT_INFO,
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
static void bind_ctrlAltFn(int sc, void * opaque)
|
||||
{
|
||||
const uint32_t ctrl = xfree86_to_ps2[KEY_LEFTCTRL];
|
||||
const uint32_t alt = xfree86_to_ps2[KEY_LEFTALT ];
|
||||
const uint32_t fn = xfree86_to_ps2[sc];
|
||||
spice_key_down(ctrl);
|
||||
spice_key_down(alt );
|
||||
spice_key_down(fn );
|
||||
|
||||
spice_key_up(ctrl);
|
||||
spice_key_up(alt );
|
||||
spice_key_up(fn );
|
||||
}
|
||||
|
||||
static void bind_passthrough(int sc, void * opaque)
|
||||
{
|
||||
sc = xfree86_to_ps2[sc];
|
||||
spice_key_down(sc);
|
||||
spice_key_up (sc);
|
||||
}
|
||||
|
||||
void keybind_register(void)
|
||||
{
|
||||
app_registerKeybind(KEY_F, bind_fullscreen, NULL, "Full screen toggle");
|
||||
app_registerKeybind(KEY_V, bind_video , NULL, "Video stream toggle");
|
||||
app_registerKeybind(KEY_D, bind_showFPS , NULL, "FPS display toggle");
|
||||
app_registerKeybind(KEY_R, bind_rotate , NULL, "Rotate the output clockwise by 90° increments");
|
||||
app_registerKeybind(KEY_Q, bind_quit , NULL, "Quit");
|
||||
|
||||
if (g_params.useSpiceInput)
|
||||
{
|
||||
app_registerKeybind(KEY_I , bind_input , NULL , "Spice keyboard & mouse toggle");
|
||||
app_registerKeybind(KEY_INSERT, bind_mouseSens, (void*)true , "Increase mouse sensitivity in capture mode");
|
||||
app_registerKeybind(KEY_DELETE, bind_mouseSens, (void*)false, "Descrease mouse sensitivity in capture mode");
|
||||
|
||||
app_registerKeybind(KEY_F1 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F1 to the guest");
|
||||
app_registerKeybind(KEY_F2 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F2 to the guest");
|
||||
app_registerKeybind(KEY_F3 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F3 to the guest");
|
||||
app_registerKeybind(KEY_F4 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F4 to the guest");
|
||||
app_registerKeybind(KEY_F5 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F5 to the guest");
|
||||
app_registerKeybind(KEY_F6 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F6 to the guest");
|
||||
app_registerKeybind(KEY_F7 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F7 to the guest");
|
||||
app_registerKeybind(KEY_F8 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F8 to the guest");
|
||||
app_registerKeybind(KEY_F9 , bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F9 to the guest");
|
||||
app_registerKeybind(KEY_F10, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F10 to the guest");
|
||||
app_registerKeybind(KEY_F11, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F11 to the guest");
|
||||
app_registerKeybind(KEY_F12, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F12 to the guest");
|
||||
|
||||
app_registerKeybind(KEY_LEFTMETA , bind_passthrough, NULL, "Send LWin to the guest");
|
||||
app_registerKeybind(KEY_RIGHTMETA, bind_passthrough, NULL, "Send RWin to the guest");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user