mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-18 00:29:48 +00:00
Compare commits
1303 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 | ||
|
|
7de030bb69 | ||
|
|
b5d91ccc21 | ||
|
|
0eafa7de5d | ||
|
|
e554635e48 | ||
|
|
5e915dd1ff | ||
|
|
13f55011c0 | ||
|
|
05dc713dac | ||
|
|
80f3c7934a | ||
|
|
1341bf8fbd | ||
|
|
5b163063c3 | ||
|
|
c2a15ad89d | ||
|
|
c92312a6c6 | ||
|
|
3253e7fd10 | ||
|
|
e5178793b3 | ||
|
|
bec4f83778 | ||
|
|
22f04a926f | ||
|
|
76fa390e3d | ||
|
|
1ef406bbaf | ||
|
|
0aa8711796 | ||
|
|
bea7c94cae | ||
|
|
e7239c53fd | ||
|
|
6f551c770c | ||
|
|
2d755a45e0 | ||
|
|
7a98a886b6 | ||
|
|
b0fb7177bb | ||
|
|
73e8bc41cd | ||
|
|
0b8f1a18b2 | ||
|
|
8caa220ad5 | ||
|
|
b8203bec53 | ||
|
|
5db4c32035 | ||
|
|
9282ed19b2 | ||
|
|
45ee79014d | ||
|
|
0dc0e6490c | ||
|
|
127113a59b | ||
|
|
49bf115c84 | ||
|
|
2196516e2b | ||
|
|
899dbff7e9 | ||
|
|
4345d94d68 | ||
|
|
074af5d16c | ||
|
|
89d6ea0b5d | ||
|
|
c5baf212c8 | ||
|
|
ba31c78412 | ||
|
|
1c1d2a0568 | ||
|
|
0c6ff6822d | ||
|
|
491ffc3576 | ||
|
|
da5ebee3f7 | ||
|
|
6530ca62da | ||
|
|
0bd19cfd38 | ||
|
|
8ada29e25f | ||
|
|
3b5c1bd09c | ||
|
|
c82a5e0523 | ||
|
|
9c5f9906fa | ||
|
|
db2f5b85a9 | ||
|
|
547598c61c | ||
|
|
711fbc549a | ||
|
|
f85c017184 | ||
|
|
85d46ed2b0 | ||
|
|
2d9f578719 | ||
|
|
e75f3a7278 | ||
|
|
26fa5c8860 | ||
|
|
ed5140568a | ||
|
|
70110b4a5a | ||
|
|
a6f23f00b4 | ||
|
|
30e3a43311 | ||
|
|
dce6aaefea | ||
|
|
4843a278ff | ||
|
|
fe7d611fb9 | ||
|
|
0e7e918e2c | ||
|
|
7d6e061ade | ||
|
|
66891aa536 | ||
|
|
1d7a2ccf82 | ||
|
|
e1bfb1234b | ||
|
|
9377fdfc37 | ||
|
|
5f1d17ba1f | ||
|
|
4c0ca1c8e7 | ||
|
|
8ef1aee35c | ||
|
|
4168cc8d78 | ||
|
|
bca54ab1f6 | ||
|
|
6d2c464436 | ||
|
|
e93bd7a3bf | ||
|
|
da94075e7b | ||
|
|
69522495de | ||
|
|
fce88fc72c | ||
|
|
163a2e5d0a | ||
|
|
b979752989 | ||
|
|
8ad2d5f949 | ||
|
|
745ba66119 | ||
|
|
4cf6dec592 | ||
|
|
d7fa0aeff9 | ||
|
|
2def6346e6 | ||
|
|
607539a2af | ||
|
|
6d24dd52d6 | ||
|
|
e3343cbd01 | ||
|
|
71ffa0a137 | ||
|
|
2b4f8091f9 | ||
|
|
113da121e9 | ||
|
|
dd7413f973 | ||
|
|
0851fd13e6 | ||
|
|
97024041f3 | ||
|
|
22238c3200 | ||
|
|
780bb248f7 | ||
|
|
026bdb00f2 | ||
|
|
373d4ac932 | ||
|
|
7d26027752 | ||
|
|
3d426ccef8 | ||
|
|
b31e8e1cee | ||
|
|
f0923c4ed7 | ||
|
|
aabf19e63b | ||
|
|
f4fc1eb5f6 | ||
|
|
5e201a32ca | ||
|
|
438e9e0969 | ||
|
|
9554e82c47 | ||
|
|
4cf2c7a350 | ||
|
|
664d7dccdb | ||
|
|
21b02efb4d | ||
|
|
d07aa4b29e | ||
|
|
9f33043d17 | ||
|
|
2e6301fca1 | ||
|
|
83c5df2c47 | ||
|
|
759b4ef811 | ||
|
|
437ebf6265 | ||
|
|
bffd02b8c7 | ||
|
|
196b27ee9c | ||
|
|
ff08540fd3 | ||
|
|
07be380f34 | ||
|
|
76d58deefa | ||
|
|
dba9764c5e | ||
|
|
ee5d6c7c3e | ||
|
|
1492196bbf | ||
|
|
9378f69653 | ||
|
|
d2d427b533 | ||
|
|
78a6af8dae | ||
|
|
3585e02993 | ||
|
|
5af88ae61e | ||
|
|
f946117dac | ||
|
|
666a6a218f | ||
|
|
1b031582a4 | ||
|
|
afe072adf1 | ||
|
|
09d4fea9e2 | ||
|
|
58c3fba6b9 | ||
|
|
773dd7773b | ||
|
|
732ce05866 | ||
|
|
108c7d3aaa | ||
|
|
86f4256b5a | ||
|
|
84b2917706 | ||
|
|
fc66a4a19c | ||
|
|
087387087e | ||
|
|
3f404905d2 | ||
|
|
67595d6deb | ||
|
|
77f942711a | ||
|
|
e3c98ddc35 | ||
|
|
db0d966102 | ||
|
|
a29639fceb | ||
|
|
0605b7df8c | ||
|
|
51ca08719e | ||
|
|
ce9b94e93d | ||
|
|
7cc0f7cb99 | ||
|
|
06c229dfd4 | ||
|
|
2d5f6d65ce | ||
|
|
b9841351b4 | ||
|
|
d9b6d115d1 | ||
|
|
cc6dd58778 | ||
|
|
0ba931cbed | ||
|
|
a7daeb2a12 | ||
|
|
2fe9dc7ca1 | ||
|
|
b662128708 | ||
|
|
e22f33a44b | ||
|
|
5d69d2aba9 | ||
|
|
0090580a64 | ||
|
|
538a6dc08e | ||
|
|
5b199d8f25 | ||
|
|
51ddb62126 | ||
|
|
785bc33192 | ||
|
|
522bacb1f0 | ||
|
|
cf030f6f0c | ||
|
|
823164a924 | ||
|
|
2ddae623b8 | ||
|
|
86c7286aad | ||
|
|
9886316e07 | ||
|
|
8a3356859c | ||
|
|
32d5f1db85 | ||
|
|
b5975e0f05 | ||
|
|
53ade56b4e | ||
|
|
5677117c0d | ||
|
|
558ae5dc45 | ||
|
|
83f63f4c42 | ||
|
|
247e92937c | ||
|
|
63314941f6 | ||
|
|
e7345b9711 | ||
|
|
22f9fa3938 | ||
|
|
4617829d41 | ||
|
|
fc907b802f | ||
|
|
ba50fbdc3e | ||
|
|
6f77ba8aea | ||
|
|
972ff93e6c | ||
|
|
338bc2e0dc | ||
|
|
8cedad8241 | ||
|
|
32bd6d96e3 | ||
|
|
611216286e | ||
|
|
d8915dbfc9 | ||
|
|
28b12c85f4 | ||
|
|
bee221c18d | ||
|
|
878eb057d1 | ||
|
|
da7c66419a | ||
|
|
d5ad53dae7 | ||
|
|
a03075416c | ||
|
|
e4d8cf2d76 | ||
|
|
8b47d740a8 | ||
|
|
0cac3e1c40 | ||
|
|
3f13485ced | ||
|
|
24c99c4ff9 | ||
|
|
4002f2716d | ||
|
|
f0758768b9 | ||
|
|
a82b1a2e2f | ||
|
|
ccd0fd8902 | ||
|
|
1fbba5cf2d | ||
|
|
d6805cfa0f | ||
|
|
4dee965fdf | ||
|
|
35094a57cb | ||
|
|
5d254c7751 | ||
|
|
10217fc8d9 | ||
|
|
226dd28be8 | ||
|
|
c6d2b6ea8a | ||
|
|
7fd4ba3aad | ||
|
|
ecfcf11c05 | ||
|
|
30ea57c644 | ||
|
|
c4001c727a | ||
|
|
fd4cfc2ff3 | ||
|
|
03cb61f746 | ||
|
|
8eed25b469 | ||
|
|
ee09594190 | ||
|
|
66c3c0115f | ||
|
|
3e021f3a6b | ||
|
|
b524c077a4 | ||
|
|
10f7efecb2 | ||
|
|
f09ee0bdb3 | ||
|
|
d5a52241b0 | ||
|
|
52c4e15c76 | ||
|
|
fdba14691c | ||
|
|
3d136a28a0 | ||
|
|
db398d41a0 | ||
|
|
7cbaf8b5be | ||
|
|
d1c0d2b5f8 | ||
|
|
909606627f | ||
|
|
80f5d3a660 | ||
|
|
182c4752d5 | ||
|
|
273ef55857 | ||
|
|
88c2e55acf | ||
|
|
496fd79714 | ||
|
|
40a1b860bf | ||
|
|
8120913acb | ||
|
|
935eb0651d | ||
|
|
925a93686b | ||
|
|
6f545483c9 | ||
|
|
a8b018d5da | ||
|
|
6e35033f2e | ||
|
|
f79a1b2533 | ||
|
|
79ce98116a | ||
|
|
942c417cbb | ||
|
|
8df850023c | ||
|
|
eedde4abcb | ||
|
|
fcc06dfad4 | ||
|
|
ff850c4251 | ||
|
|
20f8c92bb2 | ||
|
|
22dcb39adb | ||
|
|
f572a72c2a | ||
|
|
be736c48e9 | ||
|
|
67c7c79dae | ||
|
|
61108ba760 | ||
|
|
7285f9e9ad | ||
|
|
b29de8f370 | ||
|
|
7a828b3aee | ||
|
|
afc264e846 | ||
|
|
37c1d7ea58 | ||
|
|
4a72dab02a | ||
|
|
22e5b323c8 | ||
|
|
b275ac5765 | ||
|
|
1475845675 | ||
|
|
6d6034870e | ||
|
|
0a3b1e930a | ||
|
|
836e8a5654 | ||
|
|
39ac07bfde | ||
|
|
fc178b40bc | ||
|
|
9170b24fee | ||
|
|
3674b4ed96 | ||
|
|
c9d9205bb8 | ||
|
|
2c54fd2357 | ||
|
|
d881df916e | ||
|
|
6894ed7d5c | ||
|
|
25a2b2d5d3 | ||
|
|
4fd62a58bd | ||
|
|
532dc07c7b | ||
|
|
fb2a2076a2 | ||
|
|
a8622be1c6 | ||
|
|
810fb73362 | ||
|
|
6950379d94 | ||
|
|
f9020659e6 | ||
|
|
c99f4e31c5 | ||
|
|
526c09b7ff | ||
|
|
5a37a53cb0 | ||
|
|
a57d68acd5 | ||
|
|
a33734e2d3 | ||
|
|
e5921b3949 | ||
|
|
5de25f2b43 | ||
|
|
41f4166aed | ||
|
|
4f8fa6e7aa | ||
|
|
dbd09a431a | ||
|
|
8d48dd973a | ||
|
|
c7666b314b | ||
|
|
03628505ed | ||
|
|
b368873f4d | ||
|
|
dd38f3ce13 | ||
|
|
d8b01c0257 | ||
|
|
0a2fbe1f7f | ||
|
|
de0b54ae70 | ||
|
|
54e8cce33c | ||
|
|
08bf01b649 | ||
|
|
1a66c11091 | ||
|
|
689a1de69b | ||
|
|
0dfa7425c1 | ||
|
|
4098db039e | ||
|
|
a7834611d1 | ||
|
|
9dd4e4756b | ||
|
|
108369414e | ||
|
|
00e07c0384 | ||
|
|
1ebee561bc | ||
|
|
ec0db86663 | ||
|
|
3df4bb3a54 | ||
|
|
5bd748680f | ||
|
|
e09ff31c09 | ||
|
|
07e4c1c20f | ||
|
|
daf854c692 | ||
|
|
65c1e0391c | ||
|
|
769edba1a5 | ||
|
|
2567447b24 | ||
|
|
263b412fdf | ||
|
|
037ea5b1fc | ||
|
|
18634fa805 | ||
|
|
473e4716fc | ||
|
|
59cac9c0cc | ||
|
|
92d87d983b | ||
|
|
bfc4a1bc16 | ||
|
|
1ef61f6cd3 | ||
|
|
5518ccb795 | ||
|
|
027b27dda1 | ||
|
|
6e1180ce06 | ||
|
|
e4ae9134ae | ||
|
|
640bc03c6b | ||
|
|
2a86339b1d | ||
|
|
667aed635d | ||
|
|
1d3a23e051 | ||
|
|
507732587e | ||
|
|
d1e3508d55 | ||
|
|
3a8998f1f9 | ||
|
|
de5795e368 | ||
|
|
fca71e2b95 | ||
|
|
0e2b371e59 | ||
|
|
e1fa6b4057 | ||
|
|
b6c8d3fae5 | ||
|
|
eb1c61f335 | ||
|
|
5842ce23a3 | ||
|
|
692d48df87 | ||
|
|
49bd091359 | ||
|
|
5fe2db7e56 | ||
|
|
b927f991d6 | ||
|
|
4d7e1054bd | ||
|
|
42fa0e1d1f | ||
|
|
abfe3a9b4d | ||
|
|
b9f8f1a0ad | ||
|
|
608b67af77 | ||
|
|
2a65e39848 | ||
|
|
c23bf6a0c4 | ||
|
|
50c460df5a | ||
|
|
61f0577ab2 | ||
|
|
a9aab3c1ee | ||
|
|
73da86ac0e | ||
|
|
43d08df6b3 | ||
|
|
4654f317ca | ||
|
|
d2b83027b4 | ||
|
|
7be930a69c | ||
|
|
a1b1ed0060 | ||
|
|
2cb18a3f8f | ||
|
|
2a30bb718a | ||
|
|
75ffcacfe4 | ||
|
|
1beeac545d | ||
|
|
ab98c87e7c | ||
|
|
5b453d604e | ||
|
|
90fc2a8164 | ||
|
|
0ed9301ed9 | ||
|
|
d235d076c4 | ||
|
|
9f67f42f94 | ||
|
|
31a25c94c6 | ||
|
|
6a9f687eae | ||
|
|
df7e9b1184 | ||
|
|
1350ba6c4b | ||
|
|
2692ccc7b3 | ||
|
|
f36fd5ac1a | ||
|
|
0e8678b182 | ||
|
|
ce4f1be2a6 | ||
|
|
db907b1b67 | ||
|
|
d8b4d0c1ce | ||
|
|
fb37174e5f | ||
|
|
9613127162 | ||
|
|
4e7de236d3 | ||
|
|
741dfd418d | ||
|
|
1d6dfa048e | ||
|
|
8f0a6cd810 | ||
|
|
471303a179 | ||
|
|
73a2597c8a | ||
|
|
3cd152c9d5 | ||
|
|
e70928d603 | ||
|
|
e2b33348f3 | ||
|
|
3ff712fea5 | ||
|
|
2db26ae37e | ||
|
|
375b97ca6f | ||
|
|
d331a3dd5a | ||
|
|
c0c63fd93b | ||
|
|
b5a47cae25 | ||
|
|
1f1c9dfa59 | ||
|
|
0903b4a610 | ||
|
|
884ad6557b | ||
|
|
00658f3d64 | ||
|
|
fff3ec30b8 | ||
|
|
26434f7baf | ||
|
|
f75e2fe8db | ||
|
|
0674e04597 | ||
|
|
29f1d6cd42 | ||
|
|
83592f7e4a | ||
|
|
13cd50f92c | ||
|
|
a989914fef | ||
|
|
f692284f27 | ||
|
|
05bd587c74 | ||
|
|
d292d46fcb | ||
|
|
b899a65726 | ||
|
|
63b4dd633c | ||
|
|
eba99f6968 | ||
|
|
354bef94ee | ||
|
|
e515cdc8dd | ||
|
|
2a03d1c4a9 | ||
|
|
3e3c409fc4 | ||
|
|
62e3dd250b | ||
|
|
3799929f59 | ||
|
|
2019766989 | ||
|
|
58c3b37e49 | ||
|
|
c650c2e474 | ||
|
|
ef336d552c | ||
|
|
e4cdc58399 | ||
|
|
48d3403c40 | ||
|
|
af143bdd82 | ||
|
|
343983d9af | ||
|
|
5cabf155ab | ||
|
|
60070e6076 | ||
|
|
697dbc7a96 | ||
|
|
43593d8aea | ||
|
|
1f90010cbd | ||
|
|
d839026ade | ||
|
|
34de213926 | ||
|
|
b5ec4dd305 | ||
|
|
023d3f811b | ||
|
|
53c32cc5a4 | ||
|
|
eb6ee8ea46 | ||
|
|
9f8c20c3e7 | ||
|
|
a72ad4e46c | ||
|
|
b19518a1f8 | ||
|
|
8a9d0b0bfb | ||
|
|
14954cc426 | ||
|
|
32dca9ea3f | ||
|
|
d4c41d2d94 | ||
|
|
3f331f2e62 | ||
|
|
d753af9d17 | ||
|
|
b23c7808c0 | ||
|
|
792200cac4 | ||
|
|
526b607e37 | ||
|
|
298885083b | ||
|
|
26c4804892 | ||
|
|
a507dd0c51 | ||
|
|
c6830bab16 | ||
|
|
a0457a2dd9 | ||
|
|
ce60cafa19 | ||
|
|
9e02131525 | ||
|
|
6918eeca26 | ||
|
|
fbbee1cdac | ||
|
|
89959b48a7 | ||
|
|
b26a535451 | ||
|
|
86207993b8 | ||
|
|
ee9213da76 | ||
|
|
a084b2b32f | ||
|
|
afdae8efc0 | ||
|
|
64ad862116 | ||
|
|
4d81aaa763 | ||
|
|
8cb25792ba | ||
|
|
f715034fc4 | ||
|
|
c97ebb135f | ||
|
|
97749b335a | ||
|
|
a647a602bf | ||
|
|
80581a4aa2 | ||
|
|
882b31aeaa | ||
|
|
871aee2aae | ||
|
|
62e67c345c | ||
|
|
5de9a8dce6 | ||
|
|
3adcbfaa7d | ||
|
|
213c220d83 | ||
|
|
eef18dd655 | ||
|
|
a4600e7278 | ||
|
|
c42bff99e2 | ||
|
|
b29f1c62bb | ||
|
|
df7183a572 | ||
|
|
7a5bbb1e59 | ||
|
|
a3cd0385d0 | ||
|
|
8fdc11813d | ||
|
|
fb412e8440 | ||
|
|
15a337fee8 | ||
|
|
6f141fe393 | ||
|
|
9b0f974648 | ||
|
|
ceac6a60e6 | ||
|
|
cba6630aa0 | ||
|
|
6e0eac0abc | ||
|
|
b3aadccfc4 | ||
|
|
116926f7c0 | ||
|
|
d0a7b8df9c | ||
|
|
b8a1743d8f | ||
|
|
e8b1b8fbdf | ||
|
|
e9d77e6c52 | ||
|
|
56f0a8525b | ||
|
|
778af24d82 | ||
|
|
cd6caea4b0 | ||
|
|
f63c8043af | ||
|
|
3c77c1eb2b | ||
|
|
ffec6c2014 | ||
|
|
d097531926 | ||
|
|
d339ca3599 | ||
|
|
adb1ca58b9 | ||
|
|
70ffe1de43 | ||
|
|
cf4d16b528 | ||
|
|
da2bcfdf9a | ||
|
|
7f81d21aaa | ||
|
|
d0756cf00c | ||
|
|
4fd59ce8c9 | ||
|
|
adca879fb9 | ||
|
|
3a2d612b41 | ||
|
|
332d53e016 | ||
|
|
ae1344d1a0 | ||
|
|
ae382949c8 | ||
|
|
fd8d4d3d38 | ||
|
|
a2216e4b68 | ||
|
|
4fb9fc3b3f | ||
|
|
35b4d75eea | ||
|
|
d7321d5f5f | ||
|
|
35eda57cb2 | ||
|
|
78a100135b | ||
|
|
dc6932a9ba | ||
|
|
d765674913 | ||
|
|
2af522aea7 | ||
|
|
9aba969296 | ||
|
|
2114b73c11 | ||
|
|
d591e2fd36 | ||
|
|
c61d97b0ac | ||
|
|
37ea662998 | ||
|
|
3d9d275d61 | ||
|
|
a02087e5e4 | ||
|
|
2ccf17b9b7 | ||
|
|
8ccce5666c | ||
|
|
859e984827 | ||
|
|
5e84cfb3f1 | ||
|
|
5a84d3bef7 | ||
|
|
634be5b096 | ||
|
|
80c9e24604 | ||
|
|
5808089fce | ||
|
|
d6f84ddd12 | ||
|
|
c809eeb2a8 | ||
|
|
2dfb1cf1a6 | ||
|
|
50ba9b4899 | ||
|
|
a36d312844 | ||
|
|
6653340bac | ||
|
|
b9723adc30 | ||
|
|
7648ea712c | ||
|
|
3f29897506 | ||
|
|
bebbdc4089 | ||
|
|
9000fdf6fc | ||
|
|
fbf08b94aa | ||
|
|
a6d2fe73ae | ||
|
|
e854723aa3 | ||
|
|
9b7f54fa35 | ||
|
|
9ef9f60505 | ||
|
|
076a45acc5 | ||
|
|
c239306d82 | ||
|
|
b5f2092e9c | ||
|
|
03622f61b0 | ||
|
|
3d9230ac93 | ||
|
|
2d746cbfd4 | ||
|
|
2f2813037b | ||
|
|
f6f4c8070a | ||
|
|
40bfdcdf8c | ||
|
|
59fa025292 | ||
|
|
e09d7f0ad0 | ||
|
|
6a6e53f728 | ||
|
|
16e804b068 | ||
|
|
db52a55b36 | ||
|
|
0574daca13 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gnif
|
||||
patreon: gnif
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: lookingglass
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
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)
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
module/*.ko
|
||||
module/*.o
|
||||
module/*.mod.c
|
||||
module/.*
|
||||
module/Module.symvers
|
||||
module/modules.order
|
||||
*.a
|
||||
*.o
|
||||
*.exe
|
||||
*/build
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
||||
[submodule "vendor/kvm-guest-drivers-windows"]
|
||||
path = vendor/kvm-guest-drivers-windows
|
||||
url = https://github.com/virtio-win/kvm-guest-drivers-windows.git
|
||||
[submodule "LGMP"]
|
||||
path = repos/LGMP
|
||||
url = https://github.com/gnif/LGMP.git
|
||||
[submodule "repos/PureSpice"]
|
||||
path = repos/PureSpice
|
||||
url = https://github.com/gnif/PureSpice
|
||||
|
||||
@@ -60,9 +60,9 @@ Members of the community that donated the funding to obtain the remaining hardwa
|
||||
Michael Hillman
|
||||
Andreas Jacobsen
|
||||
NikkyAi
|
||||
Michael Lindman
|
||||
Michael Lindman
|
||||
|
||||
And another 41 people that whish to remain anonymous.
|
||||
And another 41 people that wish to remain anonymous.
|
||||
|
||||
Thank you everyone for making this project possible.
|
||||
- Geoffrey McRae <geoff@hostfission.com (gnif)
|
||||
|
||||
65
README.md
65
README.md
@@ -1,14 +1,67 @@
|
||||
# LookingGlass
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
|
||||
# Looking Glass
|
||||
|
||||
* Project Website: https://looking-glass.hostfission.com
|
||||
* Support Forum: https://forum.level1techs.com/t/looking-glass-guides-help-and-support/122387
|
||||
* Windows Builds of the host application: https://looking-glass.hostfission.com/downloads
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
||||
VGA PCI Passthrough.
|
||||
|
||||
* 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.
|
||||
|
||||
* [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)
|
||||
* BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13
|
||||
|
||||
## 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)
|
||||
* [host/README.md](host/README.md)
|
||||
* [module/README.md](module/README.md)
|
||||
|
||||
## 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.
|
||||
|
||||
Latest bleeding edge builds of the Windows host application can be obtained from:
|
||||
|
||||
https://looking-glass.io/downloads
|
||||
|
||||
# Help and support
|
||||
|
||||
## Web
|
||||
https://forum.level1techs.com/t/looking-glass-guides-help-and-support/122387
|
||||
|
||||
https://forum.level1techs.com/c/software/lookingglass/142
|
||||
|
||||
## Discord
|
||||
|
||||
* Looking Glass: https://discord.gg/52SMupxkvt
|
||||
* VFIO: https://discord.gg/4ahCn4c
|
||||
|
||||
## IRC
|
||||
|
||||
Join us in the #LookingGlass channel on the FreeNode network
|
||||
|
||||
## Trello
|
||||
|
||||
* https://trello.com/b/tI1Xbwsg/looking-glass
|
||||
|
||||
144
client/CMakeLists.txt
Normal file
144
client/CMakeLists.txt
Normal file
@@ -0,0 +1,144 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-client C)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CheckCCompilerFlag)
|
||||
include(FeatureSummary)
|
||||
|
||||
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON)
|
||||
if(OPTIMIZE_FOR_NATIVE)
|
||||
CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
add_compile_options("-march=native")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(ENABLE_OPENGL "Enable the OpenGL renderer" ON)
|
||||
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_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"
|
||||
"-fdata-sections"
|
||||
"-ffunction-sections"
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
find_package(GMP)
|
||||
|
||||
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_OPT_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
${PKGCONFIG_LIBRARIES} ${PKGCONFIG_OPT_LIBRARIES}
|
||||
${GMP_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
rt
|
||||
m
|
||||
)
|
||||
|
||||
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/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}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
|
||||
|
||||
add_subdirectory(displayservers)
|
||||
add_subdirectory(renderers)
|
||||
add_subdirectory(fonts)
|
||||
|
||||
add_executable(looking-glass-client ${SOURCES})
|
||||
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
|
||||
purespice
|
||||
renderers
|
||||
fonts
|
||||
)
|
||||
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
32
client/DEBUGGING.md
Normal file
32
client/DEBUGGING.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Debugging the Looking Glass Client
|
||||
|
||||
If you are asked to provide debugging information to resolve an issue please
|
||||
follow the following procedure.
|
||||
|
||||
## If you're experiencing a crash:
|
||||
|
||||
Run the program under the `gdb` debugger (you may need to install gdb), for
|
||||
example:
|
||||
|
||||
gdb ./looking-glass-client
|
||||
|
||||
If you need to set any arguments, do so now by running `set args ARGS`, for
|
||||
example:
|
||||
|
||||
set args -F -k
|
||||
|
||||
Now start the program by typing `r`. When the application crashes you will be
|
||||
dumped back into the debugger, the application may appear to be frozen. Run
|
||||
the following command:
|
||||
|
||||
thread apply all bt
|
||||
|
||||
Once you have this information please pastebin the log from looking-glass as
|
||||
well as the information resulting from this command.
|
||||
|
||||
## If you're experencing high CPU load and/or poor performance.
|
||||
|
||||
The steps here are identical to the above, except instead of waiting for the
|
||||
program to crash, in the debugger press `CTRL+C` while the program is
|
||||
exhibiting the problem, then run `thread apply all bt` and pastebin your log
|
||||
and the results of the command.
|
||||
@@ -1,39 +0,0 @@
|
||||
BINARY = looking-glass-client
|
||||
CFLAGS = -g -O3 -std=gnu99 -march=native -Wall -Werror -I./ -I../common -DDEBUG -DATOMIC_LOCKING
|
||||
LDFLAGS = -lrt
|
||||
|
||||
CFLAGS += -ffast-math
|
||||
CFLAGS += -fdata-sections -ffunction-sections
|
||||
LDFLAGS += -Wl,--gc-sections
|
||||
|
||||
LIBS = sdl2 SDL2_ttf gl glu libssl openssl spice-protocol fontconfig x11
|
||||
CFLAGS += $(shell pkg-config --cflags $(LIBS))
|
||||
LDFLAGS += $(shell pkg-config --libs $(LIBS))
|
||||
BUILD ?= .build
|
||||
BIN ?= bin
|
||||
|
||||
CFLAGS += -DBUILD_VERSION='"$(shell git describe --always --long --dirty --abbrev=10 --tags)"'
|
||||
|
||||
OBJS = main.o \
|
||||
lg-renderer.o \
|
||||
spice/spice.o \
|
||||
ivshmem/ivshmem.o \
|
||||
renderers/opengl.o
|
||||
# renderers/opengl-basic.o
|
||||
|
||||
BUILD_OBJS = $(foreach obj,$(OBJS),$(BUILD)/$(obj))
|
||||
|
||||
all: $(BIN)/$(BINARY)
|
||||
|
||||
$(BUILD)/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
gcc -c $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/$(BINARY): $(BUILD_OBJS)
|
||||
@mkdir -p $(dir $@)
|
||||
gcc -o $@ $(BUILD_OBJS) $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD) $(BIN)
|
||||
|
||||
.PHONY: clean
|
||||
205
client/README.md
Normal file
205
client/README.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Looking Glass Client
|
||||
|
||||
This is the Looking Glass client application that is designed to work in tandem with the Looking Glass Host application
|
||||
|
||||
---
|
||||
|
||||
## Building the Application
|
||||
|
||||
### Build Dependencies
|
||||
|
||||
* binutils-dev
|
||||
* cmake
|
||||
* fonts-freefont-ttf
|
||||
* libsdl2-dev
|
||||
* libsdl2-ttf-dev
|
||||
* libspice-protocol-dev
|
||||
* 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 libxss-dev libxi-dev
|
||||
|
||||
### Building
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
|
||||
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
|
||||
|
||||
### Key Bindings
|
||||
|
||||
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
|
||||
Below are a list of current key bindings:
|
||||
|
||||
| Command | Description |
|
||||
|-|-|
|
||||
| <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 |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F2</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F2</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F3</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F3</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F4</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F4</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F5</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F5</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F6</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F6</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F7</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F7</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F8</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F8</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F9</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F9</kbd> to the guest |
|
||||
| <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 |
|
||||
|
||||
|
||||
|
||||
### Setting options via command line arguments
|
||||
|
||||
The syntax is simple: `module:name=value`, for example:
|
||||
|
||||
./looking-glass-client win:fullScreen=yes egl:nvGain=1
|
||||
|
||||
### Setting options via configuration files
|
||||
|
||||
By default the application will look for and load the config files in the following locations
|
||||
|
||||
* /etc/looking-glass-client.ini
|
||||
* ~/.looking-glass-client.ini
|
||||
|
||||
The format of this file is the commonly known INI format, for example:
|
||||
|
||||
[win]
|
||||
fullScreen=yes
|
||||
|
||||
[egl]
|
||||
nvGain=1
|
||||
|
||||
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: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 | 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 | 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 |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| spice:enable | -s | yes | Enable the built in SPICE client for input and/or clipboard support |
|
||||
| spice:host | -c | 127.0.0.1 | The SPICE server host or UNIX socket |
|
||||
| spice:port | -p | 5900 | The SPICE server port (0 = unix socket) |
|
||||
| spice:input | | yes | Use SPICE to send keyboard and mouse input events to the guest |
|
||||
| spice:clipboard | | yes | Use SPICE to syncronize the clipboard contents with the guest |
|
||||
| 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: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 | | 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 |
|
||||
|-------------------------------------------------------------|
|
||||
```
|
||||
19
client/cmake/FindGMP.cmake
Normal file
19
client/cmake/FindGMP.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
# Try to find the GMP librairies
|
||||
# GMP_FOUND - system has GMP lib
|
||||
# GMP_INCLUDE_DIR - the GMP include directory
|
||||
# GMP_LIBRARIES - Libraries needed to use GMP
|
||||
|
||||
if (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
# Already in cache, be silent
|
||||
set(GMP_FIND_QUIETLY TRUE)
|
||||
endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
|
||||
|
||||
find_path(GMP_INCLUDE_DIR NAMES gmp.h )
|
||||
find_library(GMP_LIBRARIES NAMES gmp libgmp )
|
||||
find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx )
|
||||
MESSAGE(STATUS "GMP libs: " ${GMP_LIBRARIES} " " ${GMPXX_LIBRARIES} )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES)
|
||||
|
||||
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)
|
||||
34
client/cmake/MakeObject.cmake
Normal file
34
client/cmake/MakeObject.cmake
Normal file
@@ -0,0 +1,34 @@
|
||||
function(make_object out_var)
|
||||
set(result)
|
||||
set(result_h)
|
||||
foreach(in_f ${ARGN})
|
||||
file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}")
|
||||
set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h")
|
||||
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o")
|
||||
string(REGEX REPLACE "[/.]" "_" sym_in ${in_f})
|
||||
|
||||
add_custom_command(OUTPUT ${out_f}
|
||||
COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} ${in_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_start=b_${sym_in} ${out_f} ${out_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_end=b_${sym_in}_end ${out_f} ${out_f}
|
||||
COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f}
|
||||
DEPENDS ${in_f}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Creating object from ${in_f}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(WRITE ${out_h} "extern const char b_${sym_in}[];\n")
|
||||
file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n")
|
||||
file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n")
|
||||
|
||||
get_filename_component(h_dir ${out_h} DIRECTORY)
|
||||
list(APPEND result_h ${h_dir})
|
||||
list(APPEND result ${out_f})
|
||||
endforeach()
|
||||
list(REMOVE_DUPLICATES result_h)
|
||||
|
||||
set(${out_var}_OBJS "${result}" PARENT_SCOPE)
|
||||
set(${out_var}_INCS "${result_h}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
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
41
client/fonts/CMakeLists.txt
Normal file
41
client/fonts/CMakeLists.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(fonts LANGUAGES C)
|
||||
|
||||
set(FONT_H "${CMAKE_BINARY_DIR}/include/dynamic/fonts.h")
|
||||
set(FONT_C "${CMAKE_BINARY_DIR}/src/fonts.c")
|
||||
|
||||
file(WRITE ${FONT_H} "#include \"interface/font.h\"\n\n")
|
||||
file(APPEND ${FONT_H} "extern LG_Font * LG_Fonts[];\n\n")
|
||||
|
||||
file(WRITE ${FONT_C} "#include \"interface/font.h\"\n\n")
|
||||
file(APPEND ${FONT_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(FONTS "_")
|
||||
set(FONTS_LINK "_")
|
||||
function(add_font name)
|
||||
set(FONTS "${FONTS};${name}" PARENT_SCOPE)
|
||||
set(FONTS_LINK "${FONTS_LINK};font_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove fonts here!
|
||||
add_font(SDL)
|
||||
|
||||
list(REMOVE_AT FONTS 0)
|
||||
list(REMOVE_AT FONTS_LINK 0)
|
||||
|
||||
list(LENGTH FONTS FONT_COUNT)
|
||||
file(APPEND ${FONT_H} "#define LG_FONT_COUNT ${FONT_COUNT}\n")
|
||||
|
||||
foreach(font ${FONTS})
|
||||
file(APPEND ${FONT_C} "extern LG_Font LGF_${font};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${FONT_C} "\nconst LG_Font * LG_Fonts[] =\n{\n")
|
||||
foreach(font ${FONTS})
|
||||
file(APPEND ${FONT_C} " &LGF_${font},\n")
|
||||
endforeach()
|
||||
file(APPEND ${FONT_C} " NULL\n};\n\n")
|
||||
|
||||
add_library(fonts STATIC ${FONT_C})
|
||||
target_link_libraries(fonts ${FONTS_LINK})
|
||||
26
client/fonts/SDL/CMakeLists.txt
Normal file
26
client/fonts/SDL/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(font_SDL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONT_SDL_PKGCONFIG REQUIRED
|
||||
SDL2_ttf
|
||||
fontconfig
|
||||
)
|
||||
|
||||
add_library(font_SDL STATIC
|
||||
src/sdl.c
|
||||
)
|
||||
|
||||
target_link_libraries(font_SDL
|
||||
${FONT_SDL_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(font_SDL
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${FONT_SDL_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
251
client/fonts/SDL/src/sdl.c
Normal file
251
client/fonts/SDL/src/sdl.c
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
static int g_initCount = 0;
|
||||
static FcConfig * g_fontConfig = NULL;
|
||||
|
||||
struct Inst
|
||||
{
|
||||
TTF_Font * font;
|
||||
};
|
||||
|
||||
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTF_Init Failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
goto fail_init;
|
||||
}
|
||||
}
|
||||
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
goto fail_config;
|
||||
}
|
||||
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
if (!pat)
|
||||
{
|
||||
DEBUG_ERROR("FCNameParse failed");
|
||||
goto fail_opaque;
|
||||
}
|
||||
|
||||
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
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");
|
||||
goto fail_match;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
++g_initCount;
|
||||
ret = true;
|
||||
|
||||
fail_match:
|
||||
FcPatternDestroy(match);
|
||||
|
||||
fail_parse:
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
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)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_Surface * surface;
|
||||
SDL_Color color;
|
||||
color.r = (fg_color & 0xff000000) >> 24;
|
||||
color.g = (fg_color & 0x00ff0000) >> 16;
|
||||
color.b = (fg_color & 0x0000ff00) >> 8;
|
||||
color.a = (fg_color & 0x000000ff) >> 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap));
|
||||
if (!out)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
DEBUG_ERROR("Failed to allocate memory for font bitmap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out->reserved = surface;
|
||||
out->width = surface->w;
|
||||
out->height = surface->h;
|
||||
out->bpp = surface->format->BytesPerPixel;
|
||||
out->pixels = surface->pixels;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void lgf_sdl_release(LG_FontObj opaque, LG_FontBitmap * font)
|
||||
{
|
||||
LG_FontBitmap * bitmap = (LG_FontBitmap *)font;
|
||||
SDL_FreeSurface(bitmap->reserved);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
struct LG_Font LGF_SDL =
|
||||
{
|
||||
.name = "SDL",
|
||||
.create = lgf_sdl_create,
|
||||
.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
|
||||
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
|
||||
50
client/include/interface/font.h
Normal file
50
client/include/interface/font.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void * LG_FontObj;
|
||||
typedef struct LG_FontBitmap
|
||||
{
|
||||
void * reserved;
|
||||
|
||||
unsigned int width, height;
|
||||
unsigned int bpp; // bytes per pixel
|
||||
uint8_t * pixels;
|
||||
}
|
||||
LG_FontBitmap;
|
||||
|
||||
typedef bool (* LG_FontCreate )(LG_FontObj * opaque, const char * font_name, unsigned int size);
|
||||
typedef void (* LG_FontDestroy )(LG_FontObj opaque);
|
||||
typedef LG_FontBitmap * (* LG_FontRender )(LG_FontObj opaque, unsigned int fg_color, const char * text);
|
||||
typedef void (* LG_FontRelease )(LG_FontObj opaque, LG_FontBitmap * bitmap);
|
||||
|
||||
typedef struct LG_Font
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_FontCreate create;
|
||||
LG_FontDestroy destroy;
|
||||
LG_FontRender render;
|
||||
LG_FontRelease release;
|
||||
}
|
||||
LG_Font;
|
||||
146
client/include/interface/renderer.h
Normal file
146
client/include/interface/renderer.h
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
#define IS_LG_RENDERER_VALID(x) \
|
||||
((x)->get_name && \
|
||||
(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)
|
||||
|
||||
typedef struct LG_RendererParams
|
||||
{
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
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)
|
||||
LG_RendererRotate rotate; // guest rotation
|
||||
}
|
||||
LG_RendererFormat;
|
||||
|
||||
typedef struct LG_RendererRect
|
||||
{
|
||||
bool valid;
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
}
|
||||
LG_RendererRect;
|
||||
|
||||
typedef enum LG_RendererCursor
|
||||
{
|
||||
LG_CURSOR_COLOR ,
|
||||
LG_CURSOR_MONOCHROME ,
|
||||
LG_CURSOR_MASKED_COLOR
|
||||
}
|
||||
LG_RendererCursor;
|
||||
|
||||
// returns the friendly name of the renderer
|
||||
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, 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_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;
|
||||
32
client/include/ll.h
Normal file
32
client/include/ll.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
KVMGFX Client - A KVM Client for VGA Passthrough
|
||||
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 <stdbool.h>
|
||||
struct ll;
|
||||
|
||||
struct ll * ll_new();
|
||||
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);
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
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
|
||||
@@ -17,19 +17,20 @@ 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>
|
||||
#ifndef _H_LG_UTIL_
|
||||
#define _H_LG_UTIL_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "common/types.h"
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password);
|
||||
void spice_disconnect();
|
||||
bool spice_process();
|
||||
bool spice_ready();
|
||||
// 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);
|
||||
|
||||
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);
|
||||
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,548 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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 "ivshmem.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define MAX_IRQS 32
|
||||
|
||||
struct IVSHMEMServer
|
||||
{
|
||||
int64_t version;
|
||||
int64_t clientID;
|
||||
int sharedFD;
|
||||
|
||||
int irqs[MAX_IRQS];
|
||||
int irqCount;
|
||||
};
|
||||
|
||||
struct IVSHMEMClient
|
||||
{
|
||||
uint16_t clientID;
|
||||
|
||||
int irqs[MAX_IRQS];
|
||||
int irqCount;
|
||||
|
||||
struct IVSHMEMClient * last;
|
||||
struct IVSHMEMClient * next;
|
||||
};
|
||||
|
||||
struct IVSHMEM
|
||||
{
|
||||
bool connected;
|
||||
bool shutdown;
|
||||
int socket;
|
||||
struct IVSHMEMServer server;
|
||||
struct IVSHMEMClient * clients;
|
||||
|
||||
off_t mapSize;
|
||||
void * map;
|
||||
};
|
||||
|
||||
struct IVSHMEM ivshmem =
|
||||
{
|
||||
.connected = false,
|
||||
.shutdown = false,
|
||||
.socket = -1
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// internal functions
|
||||
|
||||
void ivshmem_cleanup();
|
||||
bool ivshmem_read(void * buffer, const ssize_t size);
|
||||
bool ivshmem_read_msg(int64_t * index, int *fd);
|
||||
struct IVSHMEMClient * ivshmem_get_client(uint16_t clientID);
|
||||
void ivshmem_remove_client(struct IVSHMEMClient * client);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_connect(const char * unix_socket)
|
||||
{
|
||||
ivshmem.shutdown = false;
|
||||
ivshmem.socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (ivshmem.socket < 0)
|
||||
{
|
||||
DEBUG_ERROR("socket creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, unix_socket, sizeof(addr.sun_path));
|
||||
|
||||
if (connect(ivshmem.socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
{
|
||||
DEBUG_ERROR("socket connect failed");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
ivshmem.connected = true;
|
||||
|
||||
if (!ivshmem_read(&ivshmem.server.version, sizeof(ivshmem.server.version)))
|
||||
{
|
||||
DEBUG_ERROR("read protocol version failed");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ivshmem.server.version != 0)
|
||||
{
|
||||
DEBUG_ERROR("unsupported protocol version %ld", ivshmem.server.version);
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ivshmem_read(&ivshmem.server.clientID, sizeof(ivshmem.server.clientID)))
|
||||
{
|
||||
DEBUG_ERROR("read client id failed");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_PROTO("Protocol : %ld", ivshmem.server.version );
|
||||
DEBUG_PROTO("Client ID: %ld", ivshmem.server.clientID);
|
||||
|
||||
if (!ivshmem_read_msg(NULL, &ivshmem.server.sharedFD))
|
||||
{
|
||||
DEBUG_ERROR("failed to read shared memory file descriptor");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat stat;
|
||||
if (fstat(ivshmem.server.sharedFD, &stat) != 0)
|
||||
{
|
||||
DEBUG_ERROR("failed to stat shared FD");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
ivshmem.mapSize = stat.st_size;
|
||||
|
||||
DEBUG_INFO("RAM Size : %ld", ivshmem.mapSize);
|
||||
ivshmem.map = mmap(
|
||||
NULL,
|
||||
stat.st_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
ivshmem.server.sharedFD,
|
||||
0);
|
||||
|
||||
if (!ivshmem.map)
|
||||
{
|
||||
DEBUG_ERROR("failed to map memory");
|
||||
ivshmem_cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void ivshmem_cleanup()
|
||||
{
|
||||
struct IVSHMEMClient * client, * next;
|
||||
client = ivshmem.clients;
|
||||
while(client)
|
||||
{
|
||||
for(int i = 0; i < client->irqCount; ++i)
|
||||
close(client->irqs[i]);
|
||||
|
||||
next = client->next;
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
ivshmem.clients = NULL;
|
||||
|
||||
for(int i = 0; i < ivshmem.server.irqCount; ++i)
|
||||
close(ivshmem.server.irqs[i]);
|
||||
ivshmem.server.irqCount = 0;
|
||||
|
||||
if (ivshmem.map)
|
||||
munmap(ivshmem.map, ivshmem.mapSize);
|
||||
ivshmem.map = NULL;
|
||||
ivshmem.mapSize = 0;
|
||||
|
||||
if (ivshmem.socket >= 0)
|
||||
{
|
||||
ivshmem.shutdown = true;
|
||||
shutdown(ivshmem.socket, SHUT_RDWR);
|
||||
close(ivshmem.socket);
|
||||
ivshmem.socket = -1;
|
||||
}
|
||||
|
||||
ivshmem.connected = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void ivshmem_disconnect()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_WARN("socket not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
ivshmem_cleanup();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_read(void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t len = read(ivshmem.socket, buffer, size);
|
||||
if (len != size)
|
||||
{
|
||||
DEBUG_ERROR("incomplete read");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_read_msg(int64_t * index, int * fd)
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct msghdr msg;
|
||||
struct iovec iov[1];
|
||||
union {
|
||||
struct cmsghdr cmsg;
|
||||
char control[CMSG_SPACE(sizeof(int))];
|
||||
} msg_control;
|
||||
|
||||
int64_t tmp;
|
||||
if (!index)
|
||||
index = &tmp;
|
||||
|
||||
iov[0].iov_base = index;
|
||||
iov[0].iov_len = sizeof(*index);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = &msg_control;
|
||||
msg.msg_controllen = sizeof(msg_control);
|
||||
|
||||
int ret = recvmsg(ivshmem.socket, &msg, 0);
|
||||
if (ret < sizeof(*index))
|
||||
{
|
||||
if (!ivshmem.shutdown)
|
||||
DEBUG_ERROR("failed to read message\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
if (!ivshmem.shutdown)
|
||||
DEBUG_ERROR("lost connetion to server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fd)
|
||||
return true;
|
||||
|
||||
*fd = -1;
|
||||
struct cmsghdr *cmsg;
|
||||
for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
|
||||
{
|
||||
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
uint16_t ivshmem_get_id()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ivshmem.server.clientID;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void * ivshmem_get_map()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ivshmem.map)
|
||||
{
|
||||
DEBUG_ERROR("not mapped");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ivshmem.map;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
size_t ivshmem_get_map_size()
|
||||
{
|
||||
if (!ivshmem.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ivshmem.map)
|
||||
{
|
||||
DEBUG_ERROR("not mapped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ivshmem.mapSize;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
struct IVSHMEMClient * ivshmem_get_client(uint16_t clientID)
|
||||
{
|
||||
struct IVSHMEMClient * client = NULL;
|
||||
|
||||
if (ivshmem.clients == NULL)
|
||||
{
|
||||
client = (struct IVSHMEMClient *)malloc(sizeof(struct IVSHMEMClient));
|
||||
client->clientID = clientID;
|
||||
client->last = NULL;
|
||||
client->next = NULL;
|
||||
client->irqCount = 0;
|
||||
ivshmem.clients = client;
|
||||
return client;
|
||||
}
|
||||
|
||||
client = ivshmem.clients;
|
||||
while(client)
|
||||
{
|
||||
if (client->clientID == clientID)
|
||||
return client;
|
||||
client = client->next;
|
||||
}
|
||||
|
||||
client = (struct IVSHMEMClient *)malloc(sizeof(struct IVSHMEMClient));
|
||||
client->clientID = clientID;
|
||||
client->last = NULL;
|
||||
client->next = ivshmem.clients;
|
||||
client->irqCount = 0;
|
||||
client->next->last = client;
|
||||
ivshmem.clients = client;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void ivshmem_remove_client(struct IVSHMEMClient * client)
|
||||
{
|
||||
if (client->last)
|
||||
client->last->next = client->next;
|
||||
|
||||
if (client->next)
|
||||
client->next->last = client->last;
|
||||
|
||||
if (ivshmem.clients == client)
|
||||
ivshmem.clients = client->next;
|
||||
|
||||
free(client);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_process()
|
||||
{
|
||||
int64_t index;
|
||||
int fd;
|
||||
|
||||
if (!ivshmem_read_msg(&index, &fd))
|
||||
{
|
||||
if (!ivshmem.shutdown)
|
||||
DEBUG_ERROR("failed to read message");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
DEBUG_ERROR("invalid index -1");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index > 0xFFFF)
|
||||
{
|
||||
DEBUG_ERROR("invalid index > 0xFFFF");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == ivshmem.server.clientID)
|
||||
{
|
||||
if (fd == -1)
|
||||
{
|
||||
DEBUG_ERROR("server sent disconnect");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ivshmem.server.irqCount == MAX_IRQS)
|
||||
{
|
||||
DEBUG_WARN("maximum IRQs reached, closing extra");
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
ivshmem.server.irqs[ivshmem.server.irqCount++] = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct IVSHMEMClient * client = ivshmem_get_client(index);
|
||||
if (!client)
|
||||
{
|
||||
DEBUG_ERROR("failed to get/create client record");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
DEBUG_PROTO("remove client %u", client->clientID);
|
||||
ivshmem_remove_client(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (client->irqCount == MAX_IRQS)
|
||||
{
|
||||
DEBUG_WARN("maximum client IRQs reached, closing extra");
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
client->irqs[client->irqCount++] = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
enum IVSHMEMWaitResult ivshmem_wait_irq(uint16_t vector, unsigned int timeout)
|
||||
{
|
||||
if (vector > ivshmem.server.irqCount - 1)
|
||||
return false;
|
||||
|
||||
int fd = ivshmem.server.irqs[vector];
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout / 1000000L;
|
||||
tv.tv_usec = timeout % 1000000L;
|
||||
|
||||
while(true)
|
||||
{
|
||||
int ret = select(fd+1, &fds, NULL, NULL, &tv);
|
||||
if (ret < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
DEBUG_ERROR("select error");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
return IVSHMEM_WAIT_RESULT_TIMEOUT;
|
||||
|
||||
if (FD_ISSET(fd, &fds))
|
||||
{
|
||||
uint64_t kick;
|
||||
int unused = read(fd, &kick, sizeof(kick));
|
||||
(void)unused;
|
||||
return IVSHMEM_WAIT_RESULT_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return IVSHMEM_WAIT_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool ivshmem_kick_irq(uint16_t clientID, uint16_t vector)
|
||||
{
|
||||
struct IVSHMEMClient * client = ivshmem_get_client(clientID);
|
||||
if (!client)
|
||||
{
|
||||
DEBUG_ERROR("invalid client");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vector > client->irqCount - 1)
|
||||
{
|
||||
DEBUG_ERROR("invalid vector for client");
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = client->irqs[vector];
|
||||
uint64_t kick = ivshmem.server.clientID;
|
||||
if (write(fd, &kick, sizeof(kick)) == sizeof(kick))
|
||||
return true;
|
||||
|
||||
DEBUG_ERROR("failed to send kick");
|
||||
return false;
|
||||
}
|
||||
56
client/kb.h
56
client/kb.h
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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
|
||||
*/
|
||||
|
||||
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
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#define IS_LG_RENDERER_VALID(x) \
|
||||
((x)->get_name && \
|
||||
(x)->create && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->on_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->render)
|
||||
|
||||
#define LGR_OPTION_COUNT(x) (sizeof(x) / sizeof(LG_RendererOpt))
|
||||
|
||||
typedef bool(* LG_RendererOptValidator)(const char * value);
|
||||
typedef void(* LG_RendererOptHandler )(void * opaque, const char * value);
|
||||
|
||||
typedef struct LG_RendererOpt
|
||||
{
|
||||
const char * name;
|
||||
const char * desc;
|
||||
LG_RendererOptValidator validator;
|
||||
LG_RendererOptHandler handler;
|
||||
}
|
||||
LG_RendererOpt;
|
||||
|
||||
typedef struct LG_RendererOptValue
|
||||
{
|
||||
const LG_RendererOpt * opt;
|
||||
const char * value;
|
||||
} LG_RendererOptValue;
|
||||
|
||||
typedef LG_RendererOpt * LG_RendererOptions;
|
||||
|
||||
typedef struct LG_RendererParams
|
||||
{
|
||||
TTF_Font * font;
|
||||
bool showFPS;
|
||||
}
|
||||
LG_RendererParams;
|
||||
|
||||
typedef struct LG_RendererFormat
|
||||
{
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
unsigned int stride; // scanline width
|
||||
unsigned int pitch; // scanline bytes
|
||||
unsigned int bpp; // bits per pixel
|
||||
}
|
||||
LG_RendererFormat;
|
||||
|
||||
typedef struct LG_RendererRect
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
unsigned int w;
|
||||
unsigned int h;
|
||||
}
|
||||
LG_RendererRect;
|
||||
|
||||
typedef enum LG_RendererCursor
|
||||
{
|
||||
LG_CURSOR_COLOR ,
|
||||
LG_CURSOR_MONOCHROME ,
|
||||
LG_CURSOR_MASKED_COLOR
|
||||
}
|
||||
LG_RendererCursor;
|
||||
|
||||
typedef const char * (* LG_RendererGetName )();
|
||||
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 uint8_t * data);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererCreate create;
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererOptions options;
|
||||
unsigned int option_count;
|
||||
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_RendererRender render;
|
||||
}
|
||||
LG_Renderer;
|
||||
|
||||
// generic option helpers
|
||||
bool LG_RendererValidatorBool(const char * value);
|
||||
bool LG_RendererValueToBool (const char * value);
|
||||
1242
client/main.c
1242
client/main.c
File diff suppressed because it is too large
Load Diff
46
client/renderers/CMakeLists.txt
Normal file
46
client/renderers/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderers LANGUAGES C)
|
||||
|
||||
set(RENDERER_H "${CMAKE_BINARY_DIR}/include/dynamic/renderers.h")
|
||||
set(RENDERER_C "${CMAKE_BINARY_DIR}/src/renderers.c")
|
||||
|
||||
file(WRITE ${RENDERER_H} "#include \"interface/renderer.h\"\n\n")
|
||||
file(APPEND ${RENDERER_H} "extern LG_Renderer * LG_Renderers[];\n\n")
|
||||
|
||||
file(WRITE ${RENDERER_C} "#include \"interface/renderer.h\"\n\n")
|
||||
file(APPEND ${RENDERER_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(RENDERERS "_")
|
||||
set(RENDERERS_LINK "_")
|
||||
function(add_renderer name)
|
||||
set(RENDERERS "${RENDERERS};${name}" PARENT_SCOPE)
|
||||
set(RENDERERS_LINK "${RENDERERS_LINK};renderer_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove renderers here!
|
||||
if(ENABLE_EGL)
|
||||
add_renderer(EGL)
|
||||
endif()
|
||||
if (ENABLE_OPENGL)
|
||||
add_renderer(OpenGL)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT RENDERERS 0)
|
||||
list(REMOVE_AT RENDERERS_LINK 0)
|
||||
|
||||
list(LENGTH RENDERERS RENDERER_COUNT)
|
||||
file(APPEND ${RENDERER_H} "#define LG_RENDERER_COUNT ${RENDERER_COUNT}\n")
|
||||
|
||||
foreach(renderer ${RENDERERS})
|
||||
file(APPEND ${RENDERER_C} "extern LG_Renderer LGR_${renderer};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${RENDERER_C} "\nconst LG_Renderer * LG_Renderers[] =\n{\n")
|
||||
foreach(renderer ${RENDERERS})
|
||||
file(APPEND ${RENDERER_C} " &LGR_${renderer},\n")
|
||||
endforeach()
|
||||
file(APPEND ${RENDERER_C} " NULL\n};")
|
||||
|
||||
add_library(renderers STATIC ${RENDERER_C})
|
||||
target_link_libraries(renderers ${RENDERERS_LINK})
|
||||
66
client/renderers/EGL/CMakeLists.txt
Normal file
66
client/renderers/EGL/CMakeLists.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderer_EGL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_EGL_PKGCONFIG REQUIRED
|
||||
egl
|
||||
gl
|
||||
)
|
||||
|
||||
pkg_check_modules(RENDERER_EGL_OPT_PKGCONFIG
|
||||
wayland-egl
|
||||
)
|
||||
|
||||
include(MakeObject)
|
||||
make_object(
|
||||
EGL_SHADER
|
||||
shader/desktop.vert
|
||||
shader/desktop_rgb.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
|
||||
shader/splash_bg.vert
|
||||
shader/splash_bg.frag
|
||||
shader/splash_logo.vert
|
||||
shader/splash_logo.frag
|
||||
)
|
||||
|
||||
add_library(renderer_EGL STATIC
|
||||
egl.c
|
||||
egldebug.c
|
||||
shader.c
|
||||
texture.c
|
||||
model.c
|
||||
desktop.c
|
||||
cursor.c
|
||||
fps.c
|
||||
help.c
|
||||
draw.c
|
||||
splash.c
|
||||
alert.c
|
||||
${EGL_SHADER_OBJS}
|
||||
)
|
||||
|
||||
target_link_libraries(renderer_EGL
|
||||
${RENDERER_EGL_PKGCONFIG_LIBRARIES}
|
||||
${RENDERER_EGL_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
fonts
|
||||
)
|
||||
|
||||
target_include_directories(renderer_EGL
|
||||
PRIVATE
|
||||
src
|
||||
${EGL_SHADER_INCS}
|
||||
${RENDERER_EGL_PKGCONFIG_INCLUDE_DIRS}
|
||||
${RENDERER_EGL_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
219
client/renderers/EGL/alert.c
Normal file
219
client/renderers/EGL/alert.c
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
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
|
||||
cahe 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 "alert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "alert.vert.h"
|
||||
#include "alert.frag.h"
|
||||
#include "alert_bg.frag.h"
|
||||
|
||||
struct EGL_Alert
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
LG_Lock lock;
|
||||
bool update;
|
||||
LG_FontBitmap * bmp;
|
||||
|
||||
bool ready;
|
||||
float width , height ;
|
||||
float bgWidth, bgHeight;
|
||||
float r, g, b, a;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG, uColorBG;
|
||||
};
|
||||
|
||||
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*alert = (EGL_Alert *)malloc(sizeof(EGL_Alert));
|
||||
if (!*alert)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Alert");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*alert, 0, sizeof(EGL_Alert));
|
||||
|
||||
(*alert)->font = font;
|
||||
(*alert)->fontObj = fontObj;
|
||||
LG_LOCK_INIT((*alert)->lock);
|
||||
|
||||
if (!egl_texture_init(&(*alert)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*alert)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*alert)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*alert)->shader,
|
||||
b_shader_alert_vert, b_shader_alert_vert_size,
|
||||
b_shader_alert_frag, b_shader_alert_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the alert shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*alert)->shaderBG,
|
||||
b_shader_alert_vert , b_shader_alert_vert_size,
|
||||
b_shader_alert_bg_frag, b_shader_alert_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the alert shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
(*alert)->uSize = egl_shader_get_uniform_location((*alert)->shader , "size" );
|
||||
(*alert)->uScreen = egl_shader_get_uniform_location((*alert)->shader , "screen");
|
||||
(*alert)->uSizeBG = egl_shader_get_uniform_location((*alert)->shaderBG, "size" );
|
||||
(*alert)->uScreenBG = egl_shader_get_uniform_location((*alert)->shaderBG, "screen");
|
||||
(*alert)->uColorBG = egl_shader_get_uniform_location((*alert)->shaderBG, "color" );
|
||||
|
||||
if (!egl_model_init(&(*alert)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*alert)->model);
|
||||
egl_model_set_texture((*alert)->model, (*alert)->texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_alert_free(EGL_Alert ** alert)
|
||||
{
|
||||
if (!*alert)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*alert)->texture );
|
||||
egl_shader_free (&(*alert)->shader );
|
||||
egl_shader_free (&(*alert)->shaderBG);
|
||||
egl_model_free (&(*alert)->model );
|
||||
|
||||
free(*alert);
|
||||
*alert = NULL;
|
||||
}
|
||||
|
||||
void egl_alert_set_color(EGL_Alert * alert, const uint32_t color)
|
||||
{
|
||||
alert->r = (1.0f / 0xff) * ((color >> 24) & 0xFF);
|
||||
alert->g = (1.0f / 0xff) * ((color >> 16) & 0xFF);
|
||||
alert->b = (1.0f / 0xff) * ((color >> 8) & 0xFF);
|
||||
alert->a = (1.0f / 0xff) * ((color >> 0) & 0xFF);
|
||||
}
|
||||
|
||||
void egl_alert_set_text (EGL_Alert * alert, const char * str)
|
||||
{
|
||||
LG_LOCK(alert->lock);
|
||||
alert->bmp = alert->font->render(alert->fontObj, 0xffffff00, str);
|
||||
if (!alert->bmp)
|
||||
{
|
||||
alert->update = false;
|
||||
LG_UNLOCK(alert->lock);
|
||||
DEBUG_ERROR("Failed to render alert text");
|
||||
return;
|
||||
}
|
||||
|
||||
alert->update = true;
|
||||
LG_UNLOCK(alert->lock);
|
||||
}
|
||||
|
||||
void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (alert->update)
|
||||
{
|
||||
LG_LOCK(alert->lock);
|
||||
egl_texture_setup(
|
||||
alert->texture,
|
||||
EGL_PF_BGRA,
|
||||
alert->bmp->width ,
|
||||
alert->bmp->height,
|
||||
alert->bmp->width * alert->bmp->bpp,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
egl_texture_update(alert->texture, alert->bmp->pixels);
|
||||
|
||||
alert->width = alert->bgWidth = alert->bmp->width;
|
||||
alert->height = alert->bgHeight = alert->bmp->height;
|
||||
|
||||
if (alert->bgWidth < 200)
|
||||
alert->bgWidth = 200;
|
||||
alert->bgHeight += 4;
|
||||
|
||||
alert->ready = true;
|
||||
|
||||
alert->font->release(alert->fontObj, alert->bmp);
|
||||
alert->update = false;
|
||||
alert->bmp = NULL;
|
||||
LG_UNLOCK(alert->lock);
|
||||
}
|
||||
|
||||
if (!alert->ready)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(alert->shaderBG);
|
||||
glUniform2f(alert->uScreenBG, scaleX , scaleY );
|
||||
glUniform2i(alert->uSizeBG , alert->bgWidth, alert->bgHeight);
|
||||
glUniform4f(alert->uColorBG , alert->r, alert->g, alert->b, alert->a);
|
||||
egl_model_render(alert->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(alert->shader);
|
||||
glUniform2f(alert->uScreen, scaleX , scaleY );
|
||||
glUniform2i(alert->uSize , alert->width, alert->height);
|
||||
egl_model_render(alert->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
33
client/renderers/EGL/alert.h
Normal file
33
client/renderers/EGL/alert.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 "interface/font.h"
|
||||
|
||||
typedef struct EGL_Alert EGL_Alert;
|
||||
|
||||
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_alert_free(EGL_Alert ** alert);
|
||||
|
||||
void egl_alert_set_color(EGL_Alert * alert, const uint32_t color);
|
||||
void egl_alert_set_text (EGL_Alert * alert, const char * str);
|
||||
void egl_alert_render (EGL_Alert * alert, const float scaleX, const float scaleY);
|
||||
311
client/renderers/EGL/cursor.c
Normal file
311
client/renderers/EGL/cursor.c
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
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
|
||||
cahe 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 "cursor.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/option.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "cursor.vert.h"
|
||||
#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;
|
||||
LG_RendererCursor type;
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
uint8_t * data;
|
||||
size_t dataSize;
|
||||
bool update;
|
||||
|
||||
// cursor state
|
||||
bool visible;
|
||||
float x, y, w, h;
|
||||
LG_RendererRotate rotate;
|
||||
int cbMode;
|
||||
|
||||
struct CursorTex norm;
|
||||
struct CursorTex mono;
|
||||
struct EGL_Model * model;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&t->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
*cursor = (EGL_Cursor *)malloc(sizeof(EGL_Cursor));
|
||||
if (!*cursor)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*cursor, 0, sizeof(EGL_Cursor));
|
||||
LG_LOCK_INIT((*cursor)->lock);
|
||||
|
||||
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_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_model_init(&(*cursor)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*cursor)->model);
|
||||
|
||||
(*cursor)->cbMode = option_get_int("egl", "cbMode");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_free(EGL_Cursor ** cursor)
|
||||
{
|
||||
if (!*cursor)
|
||||
return;
|
||||
|
||||
LG_LOCK_FREE((*cursor)->lock);
|
||||
if ((*cursor)->data)
|
||||
free((*cursor)->data);
|
||||
|
||||
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)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
|
||||
cursor->type = type;
|
||||
cursor->width = width;
|
||||
cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height);
|
||||
cursor->stride = stride;
|
||||
|
||||
const size_t size = height * stride;
|
||||
if (size > cursor->dataSize)
|
||||
{
|
||||
if (cursor->data)
|
||||
free(cursor->data);
|
||||
|
||||
cursor->data = (uint8_t *)malloc(size);
|
||||
if (!cursor->data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc buffer for cursor shape");
|
||||
return false;
|
||||
}
|
||||
|
||||
cursor->dataSize = size;
|
||||
}
|
||||
|
||||
memcpy(cursor->data, data, size);
|
||||
cursor->update = true;
|
||||
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_set_size(EGL_Cursor * cursor, const float w, const float h)
|
||||
{
|
||||
cursor->w = w;
|
||||
cursor->h = h;
|
||||
}
|
||||
|
||||
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y)
|
||||
{
|
||||
cursor->visible = visible;
|
||||
cursor->x = x;
|
||||
cursor->y = y;
|
||||
}
|
||||
|
||||
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
|
||||
{
|
||||
if (!cursor->visible)
|
||||
return;
|
||||
|
||||
if (cursor->update)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
cursor->update = false;
|
||||
|
||||
uint8_t * data = cursor->data;
|
||||
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
// fall through
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
uint32_t and[cursor->width * cursor->height];
|
||||
uint32_t xor[cursor->width * cursor->height];
|
||||
|
||||
for(int y = 0; y < cursor->height; ++y)
|
||||
for(int x = 0; x < cursor->width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8);
|
||||
const uint8_t * srcXor = srcAnd + cursor->stride * cursor->height;
|
||||
const uint8_t mask = 0x80 >> (x % 8);
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
and[y * cursor->width + x] = andMask;
|
||||
xor[y * cursor->width + x] = xorMask;
|
||||
}
|
||||
|
||||
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->norm.shader);
|
||||
egl_cursor_tex_uniforms(cursor, &cursor->norm, true);;
|
||||
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_model_set_texture(cursor->model, cursor->norm.texture);
|
||||
egl_model_render(cursor->model);
|
||||
|
||||
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->mono.texture);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
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);
|
||||
}
|
||||
44
client/renderers/EGL/cursor.h
Normal file
44
client/renderers/EGL/cursor.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 "interface/renderer.h"
|
||||
|
||||
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, LG_RendererRotate rotate);
|
||||
284
client/renderers/EGL/desktop.c
Normal file
284
client/renderers/EGL/desktop.c
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
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
|
||||
cahe 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 "desktop.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "desktop.vert.h"
|
||||
#include "desktop_rgb.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;
|
||||
|
||||
// night vision
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
|
||||
// colorblind mode
|
||||
int cbMode;
|
||||
};
|
||||
|
||||
// forwards
|
||||
void egl_desktop_toggle_nv(int key, void * opaque);
|
||||
|
||||
static bool egl_init_desktop_shader(
|
||||
struct DesktopShader * shader,
|
||||
const char * vertex_code , size_t vertex_size,
|
||||
const char * fragment_code, size_t fragment_size
|
||||
)
|
||||
{
|
||||
if (!egl_shader_init(&shader->shader))
|
||||
return false;
|
||||
|
||||
if (!egl_shader_compile(shader->shader,
|
||||
vertex_code , vertex_size,
|
||||
fragment_code, fragment_size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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, EGLDisplay * display)
|
||||
{
|
||||
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
||||
if (!*desktop)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*desktop, 0, sizeof(EGL_Desktop));
|
||||
(*desktop)->display = display;
|
||||
|
||||
if (!egl_texture_init(&(*desktop)->texture, display))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_init_desktop_shader(
|
||||
&(*desktop)->shader_generic,
|
||||
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
||||
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the generic desktop shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_model_init(&(*desktop)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*desktop)->model);
|
||||
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
|
||||
|
||||
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(int key, void * opaque)
|
||||
{
|
||||
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
||||
if (desktop->nvGain++ == desktop->nvMax)
|
||||
desktop->nvGain = 0;
|
||||
|
||||
if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled");
|
||||
else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled");
|
||||
else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1);
|
||||
}
|
||||
|
||||
void egl_desktop_free(EGL_Desktop ** desktop)
|
||||
{
|
||||
if (!*desktop)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*desktop)->texture );
|
||||
egl_shader_free (&(*desktop)->shader_generic.shader);
|
||||
egl_model_free (&(*desktop)->model );
|
||||
|
||||
free(*desktop);
|
||||
*desktop = NULL;
|
||||
}
|
||||
|
||||
bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bool useDMA)
|
||||
{
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
switch(format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA:
|
||||
pixFmt = EGL_PF_BGRA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA:
|
||||
pixFmt = EGL_PF_RGBA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA10:
|
||||
pixFmt = EGL_PF_RGBA10;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA16F:
|
||||
pixFmt = EGL_PF_RGBA16F;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool egl_desktop_update(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd)
|
||||
{
|
||||
if (dmaFd >= 0)
|
||||
{
|
||||
if (!egl_texture_update_from_dma(desktop->texture, frame, dmaFd))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!egl_texture_update_from_frame(desktop->texture, frame))
|
||||
return false;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus status;
|
||||
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
|
||||
{
|
||||
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,
|
||||
LG_RendererRotate rotate)
|
||||
{
|
||||
if (!desktop->shader)
|
||||
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->uRotate , rotate);
|
||||
glUniform1i(shader->uNearest , useNearest ? 1 : 0);
|
||||
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
|
||||
|
||||
if (desktop->nvGain)
|
||||
{
|
||||
glUniform1i(shader->uNV, 1);
|
||||
glUniform1f(shader->uNVGain, (float)desktop->nvGain);
|
||||
}
|
||||
else
|
||||
glUniform1i(shader->uNV, 0);
|
||||
|
||||
glUniform1i(shader->uCBMode, desktop->cbMode);
|
||||
egl_model_render(desktop->model);
|
||||
return true;
|
||||
}
|
||||
35
client/renderers/EGL/desktop.h
Normal file
35
client/renderers/EGL/desktop.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 "interface/renderer.h"
|
||||
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop, EGLDisplay * display);
|
||||
void egl_desktop_free(EGL_Desktop ** desktop);
|
||||
|
||||
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);
|
||||
66
client/renderers/EGL/draw.c
Normal file
66
client/renderers/EGL/draw.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 "draw.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
void egl_draw_torus(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer)
|
||||
{
|
||||
GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (pts + 1) * 6);
|
||||
GLfloat * dst = v;
|
||||
|
||||
for(unsigned int i = 0; i <= pts; ++i)
|
||||
{
|
||||
const float angle = (i / (float)pts) * M_PI * 2.0f;
|
||||
const float c = cos(angle);
|
||||
const float s = sin(angle);
|
||||
*dst = x + (inner * c); ++dst;
|
||||
*dst = y + (inner * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst = x + (outer * c); ++dst;
|
||||
*dst = y + (outer * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
}
|
||||
|
||||
egl_model_add_verticies(model, v, NULL, (pts + 1) * 2);
|
||||
free(v);
|
||||
}
|
||||
|
||||
void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e)
|
||||
{
|
||||
GLfloat * v = (GLfloat *)malloc(sizeof(GLfloat) * (pts + 1) * 6);
|
||||
GLfloat * dst = v;
|
||||
|
||||
for(unsigned int i = 0; i <= pts; ++i)
|
||||
{
|
||||
const float angle = s + ((i / (float)pts) * e);
|
||||
const float c = cos(angle);
|
||||
const float s = sin(angle);
|
||||
*dst = x + (inner * c); ++dst;
|
||||
*dst = y + (inner * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst = x + (outer * c); ++dst;
|
||||
*dst = y + (outer * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
}
|
||||
|
||||
egl_model_add_verticies(model, v, NULL, (pts + 1) * 2);
|
||||
free(v);
|
||||
}
|
||||
25
client/renderers/EGL/draw.h
Normal file
25
client/renderers/EGL/draw.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 "model.h"
|
||||
|
||||
void egl_draw_torus (EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer);
|
||||
void egl_draw_torus_arc(EGL_Model * model, unsigned int pts, float x, float y, float inner, float outer, float s, float e);
|
||||
766
client/renderers/EGL/egl.c
Normal file
766
client/renderers/EGL/egl.c
Normal file
@@ -0,0 +1,766 @@
|
||||
/*
|
||||
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/renderer.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/sysinfo.h"
|
||||
#include "common/time.h"
|
||||
#include "common/locking.h"
|
||||
#include "util.h"
|
||||
#include "dynamic/fonts.h"
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "desktop.h"
|
||||
#include "cursor.h"
|
||||
#include "fps.h"
|
||||
#include "splash.h"
|
||||
#include "alert.h"
|
||||
#include "help.h"
|
||||
|
||||
#define SPLASH_FADE_TIME 1000000
|
||||
#define ALERT_TIMEOUT 2000000
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool vsync;
|
||||
bool doubleBuffer;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
bool dmaSupport;
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
EGLNativeWindowType nativeWind;
|
||||
EGLDisplay display;
|
||||
EGLConfig configs;
|
||||
EGLSurface surface;
|
||||
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 formatValid;
|
||||
bool start;
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
bool showAlert;
|
||||
uint64_t alertTimeout;
|
||||
bool useCloseFlag;
|
||||
bool closeFlag;
|
||||
|
||||
int width, height;
|
||||
LG_RendererRect destRect;
|
||||
LG_RendererRotate rotate; //client side rotation
|
||||
|
||||
float translateX , translateY;
|
||||
float scaleX , scaleY;
|
||||
float splashRatio;
|
||||
float screenScaleX, screenScaleY;
|
||||
bool useNearest;
|
||||
|
||||
bool cursorVisible;
|
||||
int cursorX , cursorY;
|
||||
float mouseWidth , mouseHeight;
|
||||
float mouseScaleX, mouseScaleY;
|
||||
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
LG_FontObj helpFontObj;
|
||||
};
|
||||
|
||||
static struct Option egl_options[] =
|
||||
{
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "vsync",
|
||||
.description = "Enable vsync",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "doubleBuffer",
|
||||
.description = "Enable double buffering",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "multisample",
|
||||
.description = "Enable Multisampling",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "nvGainMax",
|
||||
.description = "The maximum night vision gain",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 1
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "nvGain",
|
||||
.description = "The initial night vision gain at startup",
|
||||
.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(void)
|
||||
{
|
||||
return "EGL";
|
||||
}
|
||||
|
||||
void egl_setup(void)
|
||||
{
|
||||
option_register(egl_options);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
|
||||
// safe off parameteres and init our default option values
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
memcpy(&this->params, ¶ms, sizeof(LG_RendererParams));
|
||||
|
||||
this->opt.vsync = option_get_bool("egl", "vsync");
|
||||
this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer");
|
||||
|
||||
this->translateX = 0;
|
||||
this->translateY = 0;
|
||||
this->scaleX = 1.0f;
|
||||
this->scaleY = 1.0f;
|
||||
this->screenScaleX = 1.0f;
|
||||
this->screenScaleY = 1.0f;
|
||||
|
||||
this->font = LG_Fonts[0];
|
||||
if (!this->font->create(&this->fontObj, NULL, 16))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create a font instance");
|
||||
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)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (destRect.valid)
|
||||
{
|
||||
this->translateX = 1.0f - (((destRect.w / 2) + destRect.x) * 2) / (float)width;
|
||||
this->translateY = 1.0f - (((destRect.h / 2) + destRect.y) * 2) / (float)height;
|
||||
this->scaleX = (float)destRect.w / (float)width;
|
||||
this->scaleY = (float)destRect.h / (float)height;
|
||||
}
|
||||
|
||||
egl_calc_mouse_size(this);
|
||||
|
||||
this->splashRatio = (float)width / (float)height;
|
||||
this->screenScaleX = 1.0f / width;
|
||||
this->screenScaleY = 1.0f / height;
|
||||
|
||||
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)
|
||||
{
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->mouseWidth = width;
|
||||
this->mouseHeight = height;
|
||||
egl_calc_mouse_size(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->cursorVisible = visible;
|
||||
this->cursorX = x;
|
||||
this->cursorY = y;
|
||||
egl_calc_mouse_state(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->formatValid = true;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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 to update the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->start = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
static const uint32_t colors[] =
|
||||
{
|
||||
0x0000CCCC, // LG_ALERT_INFO
|
||||
0x00CC00CC, // LG_ALERT_SUCCESS
|
||||
0xCC7F00CC, // LG_ALERT_WARNING
|
||||
0xFF0000CC // LG_ALERT_ERROR
|
||||
};
|
||||
|
||||
if (alert > LG_ALERT_ERROR || alert < 0)
|
||||
{
|
||||
DEBUG_ERROR("Invalid alert value");
|
||||
return;
|
||||
}
|
||||
|
||||
egl_alert_set_color(this->alert, colors[alert]);
|
||||
egl_alert_set_text (this->alert, message );
|
||||
|
||||
if (closeFlag)
|
||||
{
|
||||
this->useCloseFlag = true;
|
||||
*closeFlag = &this->closeFlag;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->useCloseFlag = false;
|
||||
this->alertTimeout = microtime() + ALERT_TIMEOUT;
|
||||
}
|
||||
|
||||
this->showAlert = true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
this->nativeWind = app_getEGLNativeWindow();
|
||||
if (!this->nativeWind)
|
||||
return false;
|
||||
|
||||
this->display = app_getEGLDisplay();
|
||||
if (this->display == EGL_NO_DISPLAY)
|
||||
return false;
|
||||
|
||||
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 , 24,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
|
||||
EGL_SAMPLES , maxSamples,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint num_config;
|
||||
if (!eglChooseConfig(this->display, attr, &this->configs, 1, &num_config))
|
||||
{
|
||||
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint ctxattr[] =
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr);
|
||||
if (this->context == EGL_NO_CONTEXT)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL context (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint rb = 0;
|
||||
eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb);
|
||||
switch(rb)
|
||||
{
|
||||
case EGL_SINGLE_BUFFER:
|
||||
DEBUG_INFO("Single buffer mode");
|
||||
break;
|
||||
|
||||
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, this->display))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_cursor_init(&this->cursor))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_fps_init(&this->fps, this->font, this->fontObj))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the FPS display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_splash_init(&this->splash))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the splash screen");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_alert_init(&this->alert, this->font, this->fontObj))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the alert display");
|
||||
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, LG_RendererRotate rotate)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (this->start && egl_desktop_render(this->desktop,
|
||||
this->translateX, this->translateY,
|
||||
this->scaleX , this->scaleY ,
|
||||
this->useNearest,
|
||||
rotate))
|
||||
{
|
||||
if (!this->waitFadeTime)
|
||||
{
|
||||
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)
|
||||
{
|
||||
float a = 1.0f;
|
||||
if (!this->waitFadeTime)
|
||||
a = 1.0f;
|
||||
else
|
||||
{
|
||||
uint64_t t = microtime();
|
||||
if (t > this->waitFadeTime)
|
||||
this->waitDone = true;
|
||||
else
|
||||
{
|
||||
uint64_t delta = this->waitFadeTime - t;
|
||||
a = 1.0f / SPLASH_FADE_TIME * delta;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
bool close = false;
|
||||
if (this->useCloseFlag)
|
||||
close = this->closeFlag;
|
||||
else if (this->alertTimeout < microtime())
|
||||
close = true;
|
||||
|
||||
if (close)
|
||||
this->showAlert = false;
|
||||
else
|
||||
egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY);
|
||||
}
|
||||
|
||||
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
|
||||
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;
|
||||
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,
|
||||
.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";
|
||||
}
|
||||
}
|
||||
29
client/renderers/EGL/egldebug.h
Normal file
29
client/renderers/EGL/egldebug.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 "common/debug.h"
|
||||
|
||||
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())
|
||||
205
client/renderers/EGL/fps.c
Normal file
205
client/renderers/EGL/fps.c
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
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
|
||||
cahe 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 "fps.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "fps.vert.h"
|
||||
#include "fps.frag.h"
|
||||
#include "fps_bg.frag.h"
|
||||
|
||||
struct EGL_FPS
|
||||
{
|
||||
const LG_Font * font;
|
||||
LG_FontObj fontObj;
|
||||
|
||||
EGL_Texture * texture;
|
||||
EGL_Shader * shader;
|
||||
EGL_Shader * shaderBG;
|
||||
EGL_Model * model;
|
||||
|
||||
bool display;
|
||||
bool ready;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
GLint uScreen , uSize;
|
||||
GLint uScreenBG, uSizeBG;
|
||||
};
|
||||
|
||||
bool egl_fps_init(EGL_FPS ** fps, const LG_Font * font, LG_FontObj fontObj)
|
||||
{
|
||||
*fps = (EGL_FPS *)malloc(sizeof(EGL_FPS));
|
||||
if (!*fps)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_FPS");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*fps, 0, sizeof(EGL_FPS));
|
||||
|
||||
(*fps)->font = font;
|
||||
(*fps)->fontObj = fontObj;
|
||||
|
||||
if (!egl_texture_init(&(*fps)->texture, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*fps)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*fps)->shaderBG))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps bg shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!egl_shader_compile((*fps)->shader,
|
||||
b_shader_fps_vert, b_shader_fps_vert_size,
|
||||
b_shader_fps_frag, b_shader_fps_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the fps shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*fps)->shaderBG,
|
||||
b_shader_fps_vert , b_shader_fps_vert_size,
|
||||
b_shader_fps_bg_frag, b_shader_fps_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the fps shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
(*fps)->uSize = egl_shader_get_uniform_location((*fps)->shader , "size" );
|
||||
(*fps)->uScreen = egl_shader_get_uniform_location((*fps)->shader , "screen");
|
||||
(*fps)->uSizeBG = egl_shader_get_uniform_location((*fps)->shaderBG, "size" );
|
||||
(*fps)->uScreenBG = egl_shader_get_uniform_location((*fps)->shaderBG, "screen");
|
||||
|
||||
if (!egl_model_init(&(*fps)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the fps model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*fps)->model);
|
||||
egl_model_set_texture((*fps)->model, (*fps)->texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_fps_free(EGL_FPS ** fps)
|
||||
{
|
||||
if (!*fps)
|
||||
return;
|
||||
|
||||
egl_texture_free(&(*fps)->texture );
|
||||
egl_shader_free (&(*fps)->shader );
|
||||
egl_shader_free (&(*fps)->shaderBG);
|
||||
egl_model_free (&(*fps)->model );
|
||||
|
||||
free(*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);
|
||||
|
||||
LG_FontBitmap * bmp = fps->font->render(fps->fontObj, 0xffffff00, str);
|
||||
if (!bmp)
|
||||
{
|
||||
DEBUG_ERROR("Failed to render fps text");
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
(
|
||||
fps->texture,
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
fps->ready = true;
|
||||
fps->font->release(fps->fontObj, bmp);
|
||||
}
|
||||
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (!fps->display || !fps->ready)
|
||||
return;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// render the background first
|
||||
egl_shader_use(fps->shaderBG);
|
||||
glUniform2f(fps->uScreenBG, scaleX , scaleY );
|
||||
glUniform2f(fps->uSizeBG , fps->width, fps->height);
|
||||
egl_model_render(fps->model);
|
||||
|
||||
// render the texture over the background
|
||||
egl_shader_use(fps->shader);
|
||||
glUniform2f(fps->uScreen, scaleX , scaleY );
|
||||
glUniform2f(fps->uSize , fps->width, fps->height);
|
||||
egl_model_render(fps->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
33
client/renderers/EGL/fps.h
Normal file
33
client/renderers/EGL/fps.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 "interface/font.h"
|
||||
|
||||
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);
|
||||
216
client/renderers/EGL/model.c
Normal file
216
client/renderers/EGL/model.c
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
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 "model.h"
|
||||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct EGL_Model
|
||||
{
|
||||
bool rebuild;
|
||||
struct ll * verticies;
|
||||
size_t vertexCount;
|
||||
bool finish;
|
||||
|
||||
bool hasBuffer;
|
||||
GLuint buffer;
|
||||
|
||||
EGL_Shader * shader;
|
||||
EGL_Texture * texture;
|
||||
};
|
||||
|
||||
struct FloatList
|
||||
{
|
||||
GLfloat * v;
|
||||
GLfloat * u;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
void update_uniform_bindings(EGL_Model * model);
|
||||
|
||||
bool egl_model_init(EGL_Model ** model)
|
||||
{
|
||||
*model = (EGL_Model *)malloc(sizeof(EGL_Model));
|
||||
if (!*model)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Model");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*model, 0, sizeof(EGL_Model));
|
||||
|
||||
(*model)->verticies = ll_new();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_model_free(EGL_Model ** model)
|
||||
{
|
||||
if (!*model)
|
||||
return;
|
||||
|
||||
struct FloatList * fl;
|
||||
while(ll_shift((*model)->verticies, (void **)&fl))
|
||||
{
|
||||
free(fl->u);
|
||||
free(fl->v);
|
||||
free(fl);
|
||||
}
|
||||
ll_free((*model)->verticies);
|
||||
|
||||
if ((*model)->hasBuffer)
|
||||
glDeleteBuffers(1, &(*model)->buffer);
|
||||
|
||||
free(*model);
|
||||
*model = NULL;
|
||||
}
|
||||
|
||||
void egl_model_set_default(EGL_Model * model)
|
||||
{
|
||||
static const GLfloat square[] =
|
||||
{
|
||||
-1.0f, -1.0f, 0.0f,
|
||||
1.0f, -1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f
|
||||
};
|
||||
|
||||
static const GLfloat uvs[] =
|
||||
{
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f
|
||||
};
|
||||
|
||||
egl_model_add_verticies(model, square, uvs, 4);
|
||||
}
|
||||
|
||||
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
|
||||
{
|
||||
struct FloatList * fl = (struct FloatList *)malloc(sizeof(struct FloatList));
|
||||
|
||||
fl->count = count;
|
||||
fl->v = (GLfloat *)malloc(sizeof(GLfloat) * count * 3);
|
||||
fl->u = (GLfloat *)malloc(sizeof(GLfloat) * count * 2);
|
||||
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
|
||||
|
||||
if (uvs)
|
||||
memcpy(fl->u, uvs, sizeof(GLfloat) * count * 2);
|
||||
else
|
||||
memset(fl->u, 0 , sizeof(GLfloat) * count * 2);
|
||||
|
||||
ll_push(model->verticies, fl);
|
||||
model->rebuild = true;
|
||||
model->vertexCount += count;
|
||||
}
|
||||
|
||||
void egl_model_render(EGL_Model * model)
|
||||
{
|
||||
if (!model->vertexCount)
|
||||
return;
|
||||
|
||||
if (model->rebuild)
|
||||
{
|
||||
if (model->hasBuffer)
|
||||
glDeleteBuffers(1, &model->buffer);
|
||||
|
||||
/* create a buffer large enough */
|
||||
glGenBuffers(1, &model->buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * (model->vertexCount * 5), NULL, GL_STATIC_DRAW);
|
||||
|
||||
GLintptr offset = 0;
|
||||
|
||||
/* buffer the verticies */
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v);
|
||||
offset += sizeof(GLfloat) * fl->count * 3;
|
||||
}
|
||||
|
||||
/* buffer the uvs */
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
|
||||
offset += sizeof(GLfloat) * fl->count * 2;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
model->rebuild = false;
|
||||
}
|
||||
|
||||
/* bind the model buffer and setup the pointers */
|
||||
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3));
|
||||
|
||||
if (model->shader)
|
||||
egl_shader_use(model->shader);
|
||||
|
||||
if (model->texture)
|
||||
egl_texture_bind(model->texture);
|
||||
|
||||
/* draw the arrays */
|
||||
GLint offset = 0;
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
{
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count);
|
||||
offset += fl->count;
|
||||
}
|
||||
|
||||
/* unbind and cleanup */
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void egl_model_set_shader(EGL_Model * model, EGL_Shader * shader)
|
||||
{
|
||||
model->shader = shader;
|
||||
update_uniform_bindings(model);
|
||||
}
|
||||
|
||||
void egl_model_set_texture(EGL_Model * model, EGL_Texture * texture)
|
||||
{
|
||||
model->texture = texture;
|
||||
update_uniform_bindings(model);
|
||||
}
|
||||
|
||||
void update_uniform_bindings(EGL_Model * model)
|
||||
{
|
||||
if (!model->shader || !model->texture)
|
||||
return;
|
||||
|
||||
const int count = egl_texture_count(model->texture);
|
||||
egl_shader_associate_textures(model->shader, count);
|
||||
}
|
||||
38
client/renderers/EGL/model.h
Normal file
38
client/renderers/EGL/model.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
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 "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef struct EGL_Model EGL_Model;
|
||||
|
||||
bool egl_model_init(EGL_Model ** model);
|
||||
void egl_model_free(EGL_Model ** model);
|
||||
|
||||
void egl_model_set_default (EGL_Model * model);
|
||||
void egl_model_add_verticies(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count);
|
||||
void egl_model_set_shader (EGL_Model * model, EGL_Shader * shader);
|
||||
void egl_model_set_texture (EGL_Model * model, EGL_Texture * texture);
|
||||
|
||||
void egl_model_render(EGL_Model * model);
|
||||
223
client/renderers/EGL/shader.c
Normal file
223
client/renderers/EGL/shader.c
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
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 "shader.h"
|
||||
#include "common/debug.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct EGL_Shader
|
||||
{
|
||||
bool hasShader;
|
||||
GLuint shader;
|
||||
};
|
||||
|
||||
bool egl_shader_init(EGL_Shader ** this)
|
||||
{
|
||||
*this = (EGL_Shader *)malloc(sizeof(EGL_Shader));
|
||||
if (!*this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*this, 0, sizeof(EGL_Shader));
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_shader_free(EGL_Shader ** this)
|
||||
{
|
||||
if (!*this)
|
||||
return;
|
||||
|
||||
if ((*this)->hasShader)
|
||||
glDeleteProgram((*this)->shader);
|
||||
|
||||
free(*this);
|
||||
*this = NULL;
|
||||
}
|
||||
|
||||
bool egl_shader_load(EGL_Shader * this, const char * vertex_file, const char * fragment_file)
|
||||
{
|
||||
char * vertex_code, * fragment_code;
|
||||
size_t vertex_size, fragment_size;
|
||||
|
||||
if (!util_fileGetContents(vertex_file, &vertex_code, &vertex_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read vertex shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Loaded vertex shader: %s", vertex_file);
|
||||
|
||||
if (!util_fileGetContents(fragment_file, &fragment_code, &fragment_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read fragment shader");
|
||||
free(vertex_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Loaded fragment shader: %s", fragment_file);
|
||||
|
||||
bool ret = egl_shader_compile(this, vertex_code, vertex_size, fragment_code, fragment_size);
|
||||
free(vertex_code);
|
||||
free(fragment_code);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool egl_shader_compile(EGL_Shader * this, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size)
|
||||
{
|
||||
if (this->hasShader)
|
||||
{
|
||||
glDeleteProgram(this->shader);
|
||||
this->hasShader = false;
|
||||
}
|
||||
|
||||
GLint length;
|
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
|
||||
length = vertex_size;
|
||||
glShaderSource(vertexShader, 1, (const char**)&vertex_code, &length);
|
||||
glCompileShader(vertexShader);
|
||||
|
||||
GLint result = GL_FALSE;
|
||||
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
|
||||
if (result == GL_FALSE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile vertex shader");
|
||||
|
||||
int logLength;
|
||||
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
|
||||
length = fragment_size;
|
||||
glShaderSource(fragmentShader, 1, (const char**)&fragment_code, &length);
|
||||
glCompileShader(fragmentShader);
|
||||
|
||||
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
|
||||
if (result == GL_FALSE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile fragment shader");
|
||||
|
||||
int logLength;
|
||||
glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
|
||||
glDeleteShader(fragmentShader);
|
||||
glDeleteShader(vertexShader );
|
||||
return false;
|
||||
}
|
||||
|
||||
this->shader = glCreateProgram();
|
||||
glAttachShader(this->shader, vertexShader );
|
||||
glAttachShader(this->shader, fragmentShader);
|
||||
glLinkProgram(this->shader);
|
||||
|
||||
glGetProgramiv(this->shader, GL_LINK_STATUS, &result);
|
||||
if (result == GL_FALSE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to link shader program");
|
||||
|
||||
int logLength;
|
||||
glGetProgramiv(this->shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetProgramInfoLog(this->shader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
|
||||
glDetachShader(this->shader, vertexShader );
|
||||
glDetachShader(this->shader, fragmentShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
glDeleteShader(vertexShader );
|
||||
glDeleteProgram(this->shader );
|
||||
return false;
|
||||
}
|
||||
|
||||
glDetachShader(this->shader, vertexShader );
|
||||
glDetachShader(this->shader, fragmentShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
glDeleteShader(vertexShader );
|
||||
|
||||
this->hasShader = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_shader_use(EGL_Shader * this)
|
||||
{
|
||||
if (this->hasShader)
|
||||
glUseProgram(this->shader);
|
||||
else
|
||||
DEBUG_ERROR("Shader program has not been compiled");
|
||||
}
|
||||
|
||||
void egl_shader_associate_textures(EGL_Shader * this, const int count)
|
||||
{
|
||||
char name[] = "sampler1";
|
||||
glUseProgram(this->shader);
|
||||
for(int i = 0; i < count; ++i, name[7]++)
|
||||
{
|
||||
GLint loc = glGetUniformLocation(this->shader, name);
|
||||
if (loc == -1)
|
||||
{
|
||||
DEBUG_WARN("Shader uniform location `%s` not found", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
glUniform1i(loc, i);
|
||||
}
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
GLint egl_shader_get_uniform_location(EGL_Shader * this, const char * name)
|
||||
{
|
||||
if (!this->shader)
|
||||
{
|
||||
DEBUG_ERROR("Shader program has not been compiled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return glGetUniformLocation(this->shader, name);
|
||||
}
|
||||
37
client/renderers/EGL/shader.h
Normal file
37
client/renderers/EGL/shader.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
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 <stddef.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef struct EGL_Shader EGL_Shader;
|
||||
|
||||
bool egl_shader_init(EGL_Shader ** shader);
|
||||
void egl_shader_free(EGL_Shader ** shader);
|
||||
|
||||
bool egl_shader_load (EGL_Shader * model, const char * vertex_file, const char * fragment_file);
|
||||
bool egl_shader_compile(EGL_Shader * model, const char * vertex_code, size_t vertex_size, const char * fragment_code, size_t fragment_size);
|
||||
void egl_shader_use (EGL_Shader * shader);
|
||||
|
||||
void egl_shader_associate_textures(EGL_Shader * shader, const int count);
|
||||
GLint egl_shader_get_uniform_location(EGL_Shader * shader, const char * name);
|
||||
12
client/renderers/EGL/shader/alert.frag
Normal file
12
client/renderers/EGL/shader/alert.frag
Normal file
@@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
in highp vec2 sz;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texelFetch(sampler1, ivec2(uv * sz), 0);
|
||||
}
|
||||
24
client/renderers/EGL/shader/alert.vert
Normal file
24
client/renderers/EGL/shader/alert.vert
Normal file
@@ -0,0 +1,24 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec2 screen;
|
||||
uniform ivec2 size;
|
||||
uniform vec4 color;
|
||||
|
||||
out highp vec2 uv;
|
||||
out highp vec2 sz;
|
||||
out highp vec4 c;
|
||||
|
||||
void main()
|
||||
{
|
||||
sz = vec2(size) + 0.5;
|
||||
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.xy *= screen.xy * sz;
|
||||
|
||||
uv = vertexUV;
|
||||
c = color;
|
||||
}
|
||||
9
client/renderers/EGL/shader/alert_bg.frag
Normal file
9
client/renderers/EGL/shader/alert_bg.frag
Normal file
@@ -0,0 +1,9 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec4 c;
|
||||
out highp vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = c;
|
||||
}
|
||||
43
client/renderers/EGL/shader/cursor.vert
Normal file
43
client/renderers/EGL/shader/cursor.vert
Normal file
@@ -0,0 +1,43 @@
|
||||
#version 300 es
|
||||
|
||||
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()
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
14
client/renderers/EGL/shader/cursor_mono.frag
Normal file
14
client/renderers/EGL/shader/cursor_mono.frag
Normal file
@@ -0,0 +1,14 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
highp vec4 tmp = texture(sampler1, uv);
|
||||
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
|
||||
discard;
|
||||
color = tmp;
|
||||
}
|
||||
51
client/renderers/EGL/shader/cursor_rgb.frag
Normal file
51
client/renderers/EGL/shader/cursor_rgb.frag
Normal file
@@ -0,0 +1,51 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
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);
|
||||
}
|
||||
}
|
||||
20
client/renderers/EGL/shader/desktop.vert
Normal file
20
client/renderers/EGL/shader/desktop.vert
Normal file
@@ -0,0 +1,20 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec4 position;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
gl_Position.x -= position.x;
|
||||
gl_Position.y -= position.y;
|
||||
gl_Position.x *= position.z;
|
||||
gl_Position.y *= position.w;
|
||||
|
||||
uv = vertexUV;
|
||||
}
|
||||
89
client/renderers/EGL/shader/desktop_rgb.frag
Normal file
89
client/renderers/EGL/shader/desktop_rgb.frag
Normal file
@@ -0,0 +1,89 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec2 uv;
|
||||
out highp vec4 color;
|
||||
|
||||
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, ruv);
|
||||
else
|
||||
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)
|
||||
{
|
||||
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
|
||||
color *= 1.0 + lumi;
|
||||
color *= nvGain;
|
||||
}
|
||||
|
||||
color.a = 1.0;
|
||||
}
|
||||
11
client/renderers/EGL/shader/fps.frag
Normal file
11
client/renderers/EGL/shader/fps.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/fps.vert
Normal file
22
client/renderers/EGL/shader/fps.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/fps_bg.frag
Normal file
8
client/renderers/EGL/shader/fps_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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
12
client/renderers/EGL/shader/splash_bg.frag
Normal file
12
client/renderers/EGL/shader/splash_bg.frag
Normal file
@@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
|
||||
in highp vec3 pos;
|
||||
out highp vec4 color;
|
||||
|
||||
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, 1);
|
||||
}
|
||||
14
client/renderers/EGL/shader/splash_bg.vert
Normal file
14
client/renderers/EGL/shader/splash_bg.vert
Normal file
@@ -0,0 +1,14 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
out highp vec3 pos;
|
||||
out highp float a;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
pos = vertexPosition_modelspace;
|
||||
}
|
||||
10
client/renderers/EGL/shader/splash_logo.frag
Normal file
10
client/renderers/EGL/shader/splash_logo.frag
Normal file
@@ -0,0 +1,10 @@
|
||||
#version 300 es
|
||||
|
||||
out highp vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
12
client/renderers/EGL/shader/splash_logo.vert
Normal file
12
client/renderers/EGL/shader/splash_logo.vert
Normal file
@@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
uniform float scale;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.y *= scale;
|
||||
gl_Position.w = 1.0;
|
||||
}
|
||||
177
client/renderers/EGL/splash.c
Normal file
177
client/renderers/EGL/splash.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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
|
||||
cahe 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 "splash.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "draw.h"
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "splash_bg.vert.h"
|
||||
#include "splash_bg.frag.h"
|
||||
#include "splash_logo.vert.h"
|
||||
#include "splash_logo.frag.h"
|
||||
|
||||
struct EGL_Splash
|
||||
{
|
||||
EGL_Shader * bgShader;
|
||||
EGL_Model * bg;
|
||||
|
||||
EGL_Shader * logoShader;
|
||||
EGL_Model * logo;
|
||||
|
||||
// uniforms
|
||||
GLint uScale;
|
||||
};
|
||||
|
||||
bool egl_splash_init(EGL_Splash ** splash)
|
||||
{
|
||||
*splash = (EGL_Splash *)malloc(sizeof(EGL_Splash));
|
||||
if (!*splash)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Splash");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*splash, 0, sizeof(EGL_Splash));
|
||||
|
||||
if (!egl_shader_init(&(*splash)->bgShader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the splash bgShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*splash)->bgShader,
|
||||
b_shader_splash_bg_vert, b_shader_splash_bg_vert_size,
|
||||
b_shader_splash_bg_frag, b_shader_splash_bg_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the splash bgShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_model_init(&(*splash)->bg))
|
||||
{
|
||||
DEBUG_ERROR("Failed to intiailize the splash bg model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*splash)->bg);
|
||||
|
||||
if (!egl_shader_init(&(*splash)->logoShader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the splash logoShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile((*splash)->logoShader,
|
||||
b_shader_splash_logo_vert, b_shader_splash_logo_vert_size,
|
||||
b_shader_splash_logo_frag, b_shader_splash_logo_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the splash logoShader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*splash)->uScale = egl_shader_get_uniform_location((*splash)->logoShader, "scale");
|
||||
|
||||
if (!egl_model_init(&(*splash)->logo))
|
||||
{
|
||||
DEBUG_ERROR("Failed to intiailize the splash model");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* build the splash model */
|
||||
#define P(x) ((1.0f/800.0f)*(float)(x))
|
||||
egl_draw_torus_arc((*splash)->logo, 30, P( 0 ), P(0), P(102), P(98), 0.0f, -M_PI);
|
||||
egl_draw_torus ((*splash)->logo, 30, P(-100), P(8), P(8 ), P(4 ));
|
||||
egl_draw_torus ((*splash)->logo, 30, P( 100), P(8), P(8 ), P(4 ));
|
||||
|
||||
egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(83), P(79));
|
||||
egl_draw_torus ((*splash)->logo, 60, P(0), P(0), P(67), P(63));
|
||||
|
||||
static const GLfloat lines[][12] =
|
||||
{
|
||||
{
|
||||
P( -2), P(-140), 0.0f,
|
||||
P( -2), P(-100), 0.0f,
|
||||
P( 2), P(-140), 0.0f,
|
||||
P( 2), P(-100), 0.0f
|
||||
},
|
||||
{
|
||||
P(-26), P(-144), 0.0f,
|
||||
P(-26), P(-140), 0.0f,
|
||||
P( 26), P(-144), 0.0f,
|
||||
P( 26), P(-140), 0.0f
|
||||
},
|
||||
{
|
||||
P(-40), P(-156), 0.0f,
|
||||
P(-40), P(-152), 0.0f,
|
||||
P( 40), P(-156), 0.0f,
|
||||
P( 40), P(-152), 0.0f
|
||||
}
|
||||
};
|
||||
|
||||
egl_model_add_verticies((*splash)->logo, lines[0], NULL, 4);
|
||||
egl_model_add_verticies((*splash)->logo, lines[1], NULL, 4);
|
||||
egl_model_add_verticies((*splash)->logo, lines[2], NULL, 4);
|
||||
|
||||
egl_draw_torus_arc((*splash)->logo, 10, P(-26), P(-154), P(10), P(14), M_PI , -M_PI / 2.0);
|
||||
egl_draw_torus_arc((*splash)->logo, 10, P( 26), P(-154), P(10), P(14), M_PI / 2.0f, -M_PI / 2.0);
|
||||
#undef P
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY)
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
glBlendColor(0, 0, 0, alpha);
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
|
||||
egl_shader_use(splash->bgShader);
|
||||
egl_model_render(splash->bg);
|
||||
|
||||
egl_shader_use(splash->logoShader);
|
||||
glUniform1f(splash->uScale, scaleY);
|
||||
egl_model_render(splash->logo);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 Geoffrey McRae <geoff@hostfission.com>
|
||||
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
|
||||
@@ -18,16 +18,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "lg-renderer.h"
|
||||
|
||||
extern const LG_Renderer LGR_OpenGL;
|
||||
//extern const LG_Renderer LGR_OpenGLBasic;
|
||||
#include <stdbool.h>
|
||||
|
||||
const LG_Renderer * LG_Renderers[] =
|
||||
{
|
||||
&LGR_OpenGL,
|
||||
// &LGR_OpenGLBasic,
|
||||
NULL // end of array sentinal
|
||||
};
|
||||
typedef struct EGL_Splash EGL_Splash;
|
||||
|
||||
#define LG_RENDERER_COUNT ((sizeof(LG_Renderers) / sizeof(LG_Renderer *)) - 1)
|
||||
bool egl_splash_init(EGL_Splash ** splash);
|
||||
void egl_splash_free(EGL_Splash ** splash);
|
||||
|
||||
void egl_splash_render(EGL_Splash * splash, float alpha, float scaleY);
|
||||
578
client/renderers/EGL/texture.c
Normal file
578
client/renderers/EGL/texture.c
Normal file
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
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 "texture.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "egldebug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.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 bpp;
|
||||
bool streaming;
|
||||
bool dma;
|
||||
bool ready;
|
||||
|
||||
GLuint sampler;
|
||||
size_t width, height, stride, pitch;
|
||||
GLenum intFormat;
|
||||
GLenum format;
|
||||
GLenum dataType;
|
||||
unsigned int fourcc;
|
||||
size_t pboBufferSize;
|
||||
|
||||
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, EGLDisplay * display)
|
||||
{
|
||||
*texture = (EGL_Texture *)malloc(sizeof(EGL_Texture));
|
||||
if (!*texture)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*texture, 0, sizeof(EGL_Texture));
|
||||
(*texture)->display = display;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_texture_free(EGL_Texture ** texture)
|
||||
{
|
||||
if (!*texture)
|
||||
return;
|
||||
|
||||
glDeleteSamplers(1, &(*texture)->sampler);
|
||||
|
||||
for(int i = 0; i < (*texture)->bufferCount; ++i)
|
||||
{
|
||||
struct Buffer * b = &(*texture)->buf[i];
|
||||
if (b->hasPBO)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
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);
|
||||
|
||||
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:
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_BGRA;
|
||||
texture->intFormat = GL_BGRA;
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->fourcc = DRM_FORMAT_ARGB8888;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_RGBA:
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RGBA;
|
||||
texture->intFormat = GL_BGRA;
|
||||
texture->dataType = GL_UNSIGNED_BYTE;
|
||||
texture->fourcc = DRM_FORMAT_ABGR8888;
|
||||
texture->pboBufferSize = height * stride;
|
||||
break;
|
||||
|
||||
case EGL_PF_RGBA10:
|
||||
texture->bpp = 4;
|
||||
texture->format = GL_RGBA;
|
||||
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_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:
|
||||
DEBUG_ERROR("Unsupported pixel format");
|
||||
return false;
|
||||
}
|
||||
|
||||
texture->pitch = stride / texture->bpp;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (useDMA)
|
||||
{
|
||||
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)
|
||||
return true;
|
||||
|
||||
for(int i = 0; i < texture->bufferCount; ++i)
|
||||
{
|
||||
glGenBuffers(1, &texture->buf[i].pbo);
|
||||
texture->buf[i].hasPBO = true;
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
const uint8_t b = sw % BUFFER_COUNT;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
||||
{
|
||||
if (!texture->streaming)
|
||||
return EGL_TEX_STATUS_OK;
|
||||
|
||||
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;
|
||||
|
||||
const uint8_t b = su % BUFFER_COUNT;
|
||||
|
||||
/* update the texture */
|
||||
if (!texture->dma)
|
||||
{
|
||||
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);
|
||||
|
||||
/* 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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
|
||||
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
|
||||
|
||||
if (texture->streaming)
|
||||
{
|
||||
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 1;
|
||||
}
|
||||
57
client/renderers/EGL/texture.h
Normal file
57
client/renderers/EGL/texture.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 "shader.h"
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
typedef struct EGL_Texture EGL_Texture;
|
||||
|
||||
enum EGL_PixelFormat
|
||||
{
|
||||
EGL_PF_RGBA,
|
||||
EGL_PF_BGRA,
|
||||
EGL_PF_RGBA10,
|
||||
EGL_PF_RGBA16F,
|
||||
EGL_PF_YUV420
|
||||
};
|
||||
|
||||
enum EGL_TexStatus
|
||||
{
|
||||
EGL_TEX_STATUS_NOTREADY,
|
||||
EGL_TEX_STATUS_OK,
|
||||
EGL_TEX_STATUS_ERROR
|
||||
};
|
||||
|
||||
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 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_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);
|
||||
23
client/renderers/OpenGL/CMakeLists.txt
Normal file
23
client/renderers/OpenGL/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(renderer_Opengl LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(RENDERER_OPENGL_PKGCONFIG REQUIRED
|
||||
gl
|
||||
)
|
||||
|
||||
add_library(renderer_OpenGL STATIC
|
||||
opengl.c
|
||||
)
|
||||
|
||||
target_link_libraries(renderer_OpenGL
|
||||
${RENDERER_OPENGL_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
fonts
|
||||
)
|
||||
|
||||
target_include_directories(renderer_OpenGL
|
||||
PRIVATE
|
||||
src
|
||||
${RENDERER_OPENGL_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
1397
client/renderers/OpenGL/opengl.c
Normal file
1397
client/renderers/OpenGL/opengl.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,814 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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-renderer.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define FRAME_TEXTURE 0
|
||||
#define FPS_TEXTURE 1
|
||||
#define MOUSE_TEXTURE 2
|
||||
#define TEXTURE_COUNT 3
|
||||
|
||||
static PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI = NULL;
|
||||
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI = NULL;
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool mipmap;
|
||||
bool vsync;
|
||||
bool preventBuffer;
|
||||
bool splitMouse;
|
||||
};
|
||||
|
||||
static struct Options defaultOptions =
|
||||
{
|
||||
.mipmap = true,
|
||||
.vsync = true,
|
||||
.preventBuffer = true,
|
||||
.splitMouse = false
|
||||
};
|
||||
|
||||
struct LGR_OpenGLBasic
|
||||
{
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
bool configured;
|
||||
SDL_Window * sdlWindow;
|
||||
SDL_GLContext glContext;
|
||||
bool doneInfo;
|
||||
|
||||
SDL_Point window;
|
||||
bool resizeWindow;
|
||||
bool frameUpdate;
|
||||
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
size_t texSize;
|
||||
|
||||
uint64_t drawStart;
|
||||
int texList;
|
||||
int fpsList;
|
||||
int mouseList;
|
||||
LG_RendererRect destRect;
|
||||
|
||||
bool hasTextures;
|
||||
GLuint textures[TEXTURE_COUNT];
|
||||
|
||||
uint gpuFrameCount;
|
||||
bool fpsTexture;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
SDL_Rect fpsRect;
|
||||
|
||||
bool mouseUpdate;
|
||||
bool newShape;
|
||||
uint64_t lastMouseDraw;
|
||||
LG_RendererCursor mouseType;
|
||||
bool mouseVisible;
|
||||
SDL_Rect mousePos;
|
||||
};
|
||||
|
||||
bool lgr_opengl_basic_check_error(const char * name)
|
||||
{
|
||||
GLenum error = glGetError();
|
||||
if (error == GL_NO_ERROR)
|
||||
return false;
|
||||
|
||||
const GLubyte * errStr = gluErrorString(error);
|
||||
DEBUG_ERROR("%s = %d (%s)", name, error, errStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char * lgr_opengl_basic_get_name()
|
||||
{
|
||||
return "OpenGL-Basic";
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_create(void ** opaque, const LG_RendererParams params)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct LGR_OpenGLBasic));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct LGR_OpenGLBasic));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct LGR_OpenGLBasic));
|
||||
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)*opaque;
|
||||
memcpy(&this->params, ¶ms , sizeof(LG_RendererParams));
|
||||
memcpy(&this->opt , &defaultOptions, sizeof(struct Options ));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (!glXGetVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC )glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI" );
|
||||
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddress((const GLubyte *)"glXWaitVideoSyncSGI");
|
||||
|
||||
if (!glXGetVideoSyncSGI || !glXWaitVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = NULL;
|
||||
DEBUG_ERROR("Failed to get proc addresses");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_configure(void * opaque, SDL_Window *window, const LG_RendererFormat format)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (this->configured)
|
||||
{
|
||||
DEBUG_ERROR("Renderer already configured, call deconfigure first");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->sdlWindow = window;
|
||||
this->glContext = SDL_GL_CreateContext(window);
|
||||
if (!this->glContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the OpenGL context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->doneInfo)
|
||||
{
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
this->doneInfo = true;
|
||||
}
|
||||
|
||||
if (SDL_GL_MakeCurrent(window, this->glContext) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to make the GL context current");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
|
||||
// check if the GPU supports GL_ARB_buffer_storage first
|
||||
// there is no advantage to this renderer if it is not present.
|
||||
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
|
||||
if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions))
|
||||
{
|
||||
DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage");
|
||||
return false;
|
||||
}
|
||||
|
||||
// assume 24 and 32 bit formats are RGB and RGBA
|
||||
switch(format.bpp)
|
||||
{
|
||||
case 24:
|
||||
this->intFormat = GL_RGB8;
|
||||
this->vboFormat = GL_BGR;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
this->intFormat = GL_RGBA8;
|
||||
this->vboFormat = GL_BGRA;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_INFO("%d bpp not supported", format.bpp);
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate the texture size in bytes
|
||||
this->texSize = format.height * format.pitch;
|
||||
|
||||
// generate lists for drawing
|
||||
this->texList = glGenLists(1);
|
||||
this->fpsList = glGenLists(1);
|
||||
this->mouseList = glGenLists(1);
|
||||
|
||||
// create the textures
|
||||
glGenTextures(TEXTURE_COUNT, this->textures);
|
||||
if (lgr_opengl_basic_check_error("glGenTextures"))
|
||||
return false;
|
||||
this->hasTextures = true;
|
||||
|
||||
// create the frame texture
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
if (lgr_opengl_basic_check_error("glBindTexture"))
|
||||
return false;
|
||||
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
this->intFormat,
|
||||
format.width,
|
||||
format.height,
|
||||
0,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(void*)0
|
||||
);
|
||||
if (lgr_opengl_basic_check_error("glTexImage2D"))
|
||||
return false;
|
||||
|
||||
// configure the texture
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
// create the display list
|
||||
glNewList(this->texList, GL_COMPILE);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(format.width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , format.height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(format.width, format.height);
|
||||
glEnd();
|
||||
glEndList();
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_COLOR_MATERIAL);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
this->resizeWindow = true;
|
||||
this->drawStart = nanotime();
|
||||
glXGetVideoSyncSGI(&this->gpuFrameCount);
|
||||
|
||||
// copy the format into the local storage
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->configured = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void lgr_opengl_basic_deconfigure(void * opaque)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return;
|
||||
|
||||
if (this->hasTextures)
|
||||
{
|
||||
glDeleteTextures(TEXTURE_COUNT, this->textures);
|
||||
this->hasTextures = false;
|
||||
}
|
||||
|
||||
if (this->glContext)
|
||||
{
|
||||
SDL_GL_DeleteContext(this->glContext);
|
||||
this->glContext = NULL;
|
||||
}
|
||||
|
||||
this->configured = false;
|
||||
}
|
||||
|
||||
void lgr_opengl_basic_deinitialize(void * opaque)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
if (this->configured)
|
||||
lgr_opengl_basic_deconfigure(opaque);
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_is_compatible(void * opaque, const LG_RendererFormat format)
|
||||
{
|
||||
const struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
return (memcmp(&this->format, &format, sizeof(LG_RendererFormat)) == 0);
|
||||
}
|
||||
|
||||
void lgr_opengl_basic_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return;
|
||||
|
||||
this->window.x = width;
|
||||
this->window.y = height;
|
||||
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
||||
|
||||
this->resizeWindow = true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
this->mouseType = cursor;
|
||||
switch(cursor)
|
||||
{
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_BGRA, // windows cursors are in BGRA format
|
||||
GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = height;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_BLEND);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height);
|
||||
glEnd();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_BLEND);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
const int hheight = height / 2;
|
||||
uint32_t d[width * height];
|
||||
for(int y = 0; y < hheight; ++y)
|
||||
for(int x = 0; x < width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (pitch * y) + (x / 8);
|
||||
const uint8_t * srcXor = srcAnd + pitch * hheight;
|
||||
const uint8_t mask = 0x80 >> (x % 8);
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
d[y * width + x ] = andMask;
|
||||
d[y * width + x + width * hheight] = xorMask;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
d
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = hheight;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glLogicOp(GL_AND);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glLogicOp(GL_XOR);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
//TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->mouseUpdate = true;
|
||||
this->newShape = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible)
|
||||
return true;
|
||||
|
||||
this->mouseVisible = visible;
|
||||
this->mousePos.x = x;
|
||||
this->mousePos.y = y;
|
||||
this->mouseUpdate = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_on_frame_event(void * opaque, const uint8_t * data)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Invalid opaque pointer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->configured)
|
||||
{
|
||||
DEBUG_ERROR("Not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->params.showFPS && this->renderTime > 1e9)
|
||||
{
|
||||
char str[128];
|
||||
const float avgFPS = 1000.0f / (((float)this->renderTime / this->frameCount ) / 1e6f);
|
||||
const float renderFPS = 1000.0f / (((float)this->renderTime / this->renderCount) / 1e6f);
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
SDL_Color color = {0xff, 0xff, 0xff};
|
||||
SDL_Surface *textSurface = NULL;
|
||||
if (!(textSurface = TTF_RenderText_Blended(this->params.font, str, color)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text");
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FPS_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->w );
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
textSurface->format->BytesPerPixel,
|
||||
textSurface->w,
|
||||
textSurface->h,
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
textSurface->pixels
|
||||
);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->fpsRect.x = 5;
|
||||
this->fpsRect.y = 5;
|
||||
this->fpsRect.w = textSurface->w;
|
||||
this->fpsRect.h = textSurface->h;
|
||||
|
||||
SDL_FreeSurface(textSurface);
|
||||
|
||||
this->renderTime = 0;
|
||||
this->frameCount = 0;
|
||||
this->renderCount = 0;
|
||||
this->fpsTexture = true;
|
||||
|
||||
glNewList(this->fpsList, GL_COMPILE);
|
||||
glEnable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
glEndList();
|
||||
}
|
||||
|
||||
// bind the texture and update it
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FRAME_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride );
|
||||
|
||||
// update the texture
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
this->format.width ,
|
||||
this->format.height,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
lgr_opengl_basic_check_error("glTexSubImage2D");
|
||||
|
||||
// unbind the buffer
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
const bool mipmap = this->opt.mipmap && (
|
||||
(this->format.width > this->destRect.w) ||
|
||||
(this->format.height > this->destRect.h));
|
||||
|
||||
if (mipmap)
|
||||
{
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
++this->frameCount;
|
||||
this->frameUpdate = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void lgr_opengl_basic_draw_mouse(struct LGR_OpenGLBasic * this)
|
||||
{
|
||||
if (!this->mouseVisible)
|
||||
return;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
|
||||
glCallList(this->mouseList);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
bool lgr_opengl_basic_render(void * opaque)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this || !this->configured)
|
||||
return false;
|
||||
|
||||
if (this->resizeWindow)
|
||||
{
|
||||
// setup the projection matrix
|
||||
glViewport(0, 0, this->window.x, this->window.y);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, this->window.x, this->window.y, 0);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
|
||||
glScalef(
|
||||
(float)this->destRect.w / (float)this->format.width,
|
||||
(float)this->destRect.h / (float)this->format.height,
|
||||
1.0f
|
||||
);
|
||||
|
||||
// update the scissor rect to prevent drawing outside of the frame
|
||||
glScissor(
|
||||
this->destRect.x,
|
||||
this->destRect.y,
|
||||
this->destRect.w,
|
||||
this->destRect.h
|
||||
);
|
||||
|
||||
this->resizeWindow = false;
|
||||
}
|
||||
|
||||
|
||||
if (this->opt.splitMouse)
|
||||
{
|
||||
if (!this->frameUpdate)
|
||||
{
|
||||
if (!this->mouseUpdate)
|
||||
return true;
|
||||
|
||||
if (!this->newShape)
|
||||
{
|
||||
// don't update the mouse too fast
|
||||
const uint64_t delta = nanotime() - this->lastMouseDraw;
|
||||
if (delta < 5e6)
|
||||
return true;
|
||||
}
|
||||
this->newShape = false;
|
||||
|
||||
glDrawBuffer(GL_FRONT);
|
||||
glCallList(this->texList);
|
||||
lgr_opengl_basic_draw_mouse(this);
|
||||
if (this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
glDrawBuffer(GL_BACK);
|
||||
glFlush();
|
||||
|
||||
this->mouseUpdate = false;
|
||||
this->lastMouseDraw = nanotime();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
glCallList(this->texList);
|
||||
lgr_opengl_basic_draw_mouse(this);
|
||||
if (this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
|
||||
if (this->opt.preventBuffer)
|
||||
{
|
||||
unsigned int before, after;
|
||||
glXGetVideoSyncSGI(&before);
|
||||
SDL_GL_SwapWindow(this->sdlWindow);
|
||||
|
||||
// wait for the swap to happen to ensure we dont buffer frames
|
||||
glXGetVideoSyncSGI(&after);
|
||||
if (before == after)
|
||||
glXWaitVideoSyncSGI(1, 0, &before);
|
||||
}
|
||||
else
|
||||
SDL_GL_SwapWindow(this->sdlWindow);
|
||||
|
||||
const uint64_t t = nanotime();
|
||||
this->renderTime += t - this->lastFrameTime;
|
||||
this->lastFrameTime = t;
|
||||
++this->renderCount;
|
||||
|
||||
this->frameUpdate = false;
|
||||
this->mouseUpdate = false;
|
||||
this->lastMouseDraw = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_opt_mipmap(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.mipmap = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_vsync(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.vsync = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_prevent_buffer(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.preventBuffer = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_split_mouse(void * opaque, const char *value)
|
||||
{
|
||||
struct LGR_OpenGLBasic * this = (struct LGR_OpenGLBasic *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.splitMouse = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static LG_RendererOpt lgr_opengl_basic_options[] =
|
||||
{
|
||||
{
|
||||
.name = "mipmap",
|
||||
.desc = "Enable or disable mipmapping [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_mipmap
|
||||
},
|
||||
{
|
||||
.name = "vsync",
|
||||
.desc ="Enable or disable vsync [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_vsync
|
||||
},
|
||||
{
|
||||
.name = "preventBuffer",
|
||||
.desc = "Prevent the driver from buffering frames [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_prevent_buffer
|
||||
},
|
||||
{
|
||||
.name = "splitMouse",
|
||||
.desc = "Draw mouse updates directly to the front buffer [default: disabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_split_mouse
|
||||
}
|
||||
};
|
||||
|
||||
const LG_Renderer LGR_OpenGLBasic =
|
||||
{
|
||||
.get_name = lgr_opengl_basic_get_name,
|
||||
.options = lgr_opengl_basic_options,
|
||||
.option_count = LGR_OPTION_COUNT(lgr_opengl_basic_options),
|
||||
.create = lgr_opengl_basic_create,
|
||||
.initialize = lgr_opengl_basic_initialize,
|
||||
.configure = lgr_opengl_basic_configure,
|
||||
.deconfigure = lgr_opengl_basic_deconfigure,
|
||||
.deinitialize = lgr_opengl_basic_deinitialize,
|
||||
.is_compatible = lgr_opengl_basic_is_compatible,
|
||||
.on_resize = lgr_opengl_basic_on_resize,
|
||||
.on_mouse_shape = lgr_opengl_basic_on_mouse_shape,
|
||||
.on_mouse_event = lgr_opengl_basic_on_mouse_event,
|
||||
.on_frame_event = lgr_opengl_basic_on_frame_event,
|
||||
.render = lgr_opengl_basic_render
|
||||
};
|
||||
@@ -1,951 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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-renderer.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <GL/glx.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define BUFFER_COUNT 2
|
||||
|
||||
#define FRAME_TEXTURE 0
|
||||
#define FPS_TEXTURE 1
|
||||
#define MOUSE_TEXTURE 2
|
||||
#define TEXTURE_COUNT 3
|
||||
|
||||
static PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI = NULL;
|
||||
static PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI = NULL;
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool mipmap;
|
||||
bool vsync;
|
||||
bool preventBuffer;
|
||||
};
|
||||
|
||||
static struct Options defaultOptions =
|
||||
{
|
||||
.mipmap = true,
|
||||
.vsync = true,
|
||||
.preventBuffer = true,
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
bool configured;
|
||||
bool reconfigure;
|
||||
SDL_GLContext glContext;
|
||||
bool doneInfo;
|
||||
|
||||
SDL_Point window;
|
||||
bool resizeWindow;
|
||||
bool frameUpdate;
|
||||
|
||||
LG_Lock formatLock;
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
size_t texSize;
|
||||
|
||||
uint64_t drawStart;
|
||||
bool hasBuffers;
|
||||
GLuint vboID[1];
|
||||
uint8_t * texPixels[BUFFER_COUNT];
|
||||
LG_Lock syncLock;
|
||||
int texIndex, wTexIndex;
|
||||
int texList;
|
||||
int fpsList;
|
||||
int mouseList;
|
||||
LG_RendererRect destRect;
|
||||
|
||||
bool hasTextures;
|
||||
GLuint textures[TEXTURE_COUNT];
|
||||
|
||||
uint gpuFrameCount;
|
||||
bool fpsTexture;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
SDL_Rect fpsRect;
|
||||
|
||||
LG_Lock mouseLock;
|
||||
LG_RendererCursor mouseCursor;
|
||||
int mouseWidth;
|
||||
int mouseHeight;
|
||||
int mousePitch;
|
||||
uint8_t * mouseData;
|
||||
size_t mouseDataSize;
|
||||
|
||||
bool mouseUpdate;
|
||||
bool newShape;
|
||||
uint64_t lastMouseDraw;
|
||||
LG_RendererCursor mouseType;
|
||||
bool mouseVisible;
|
||||
SDL_Rect mousePos;
|
||||
};
|
||||
|
||||
static bool _check_gl_error(unsigned int line, const char * name);
|
||||
#define check_gl_error(name) _check_gl_error(__LINE__, name)
|
||||
|
||||
static void deconfigure(struct Inst * this);
|
||||
static bool configure(struct Inst * this, SDL_Window *window);
|
||||
static void update_mouse_shape(struct Inst * this, bool * newShape);
|
||||
static bool draw_frame(struct Inst * this, bool * frameUpdate);
|
||||
static void draw_mouse(struct Inst * this);
|
||||
|
||||
const char * opengl_get_name()
|
||||
{
|
||||
return "OpenGL";
|
||||
}
|
||||
|
||||
bool opengl_create(void ** opaque, const LG_RendererParams params)
|
||||
{
|
||||
// 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;
|
||||
memcpy(&this->params, ¶ms , sizeof(LG_RendererParams));
|
||||
memcpy(&this->opt , &defaultOptions, sizeof(struct Options ));
|
||||
|
||||
LG_LOCK_INIT(this->formatLock);
|
||||
LG_LOCK_INIT(this->syncLock );
|
||||
LG_LOCK_INIT(this->mouseLock );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (!glXGetVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC )glXGetProcAddress((const GLubyte *)"glXGetVideoSyncSGI" );
|
||||
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddress((const GLubyte *)"glXWaitVideoSyncSGI");
|
||||
|
||||
if (!glXGetVideoSyncSGI || !glXWaitVideoSyncSGI)
|
||||
{
|
||||
glXGetVideoSyncSGI = NULL;
|
||||
DEBUG_ERROR("Failed to get proc addresses");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
deconfigure(this);
|
||||
if (this->mouseData)
|
||||
free(this->mouseData);
|
||||
|
||||
LG_LOCK_FREE(this->formatLock);
|
||||
LG_LOCK_FREE(this->syncLock );
|
||||
LG_LOCK_FREE(this->mouseLock );
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->window.x = width;
|
||||
this->window.y = height;
|
||||
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
||||
|
||||
this->resizeWindow = true;
|
||||
}
|
||||
|
||||
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)
|
||||
return false;
|
||||
|
||||
LG_LOCK(this->mouseLock);
|
||||
this->mouseCursor = cursor;
|
||||
this->mouseWidth = width;
|
||||
this->mouseHeight = height;
|
||||
this->mousePitch = pitch;
|
||||
|
||||
const size_t size = height * pitch;
|
||||
if (size > this->mouseDataSize)
|
||||
{
|
||||
if (this->mouseData)
|
||||
free(this->mouseData);
|
||||
this->mouseData = (uint8_t *)malloc(size);
|
||||
this->mouseDataSize = size;
|
||||
}
|
||||
|
||||
memcpy(this->mouseData, data, size);
|
||||
this->newShape = true;
|
||||
LG_UNLOCK(this->mouseLock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (this->mousePos.x == x && this->mousePos.y == y && this->mouseVisible == visible)
|
||||
return true;
|
||||
|
||||
this->mouseVisible = visible;
|
||||
this->mousePos.x = x;
|
||||
this->mousePos.y = y;
|
||||
this->mouseUpdate = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
|
||||
{
|
||||
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 || memcmp(&this->format, &format, sizeof(LG_RendererFormat)) != 0)
|
||||
{
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->reconfigure = true;
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
LG_UNLOCK(this->formatLock);
|
||||
|
||||
// lock, perform the update, then unlock
|
||||
LG_LOCK(this->syncLock);
|
||||
memcpySSE(this->texPixels[this->wTexIndex], data, this->texSize);
|
||||
this->frameUpdate = true;
|
||||
LG_UNLOCK(this->syncLock);
|
||||
|
||||
++this->frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_render(void * opaque, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
if (!configure(this, window))
|
||||
return true;
|
||||
|
||||
if (this->resizeWindow)
|
||||
{
|
||||
// setup the projection matrix
|
||||
glViewport(0, 0, this->window.x, this->window.y);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, this->window.x, this->window.y, 0);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
|
||||
glScalef(
|
||||
(float)this->destRect.w / (float)this->format.width,
|
||||
(float)this->destRect.h / (float)this->format.height,
|
||||
1.0f
|
||||
);
|
||||
|
||||
// update the scissor rect to prevent drawing outside of the frame
|
||||
glScissor(
|
||||
this->destRect.x,
|
||||
this->destRect.y,
|
||||
this->destRect.w,
|
||||
this->destRect.h
|
||||
);
|
||||
|
||||
this->resizeWindow = false;
|
||||
}
|
||||
|
||||
if (this->params.showFPS && this->renderTime > 1e9)
|
||||
{
|
||||
char str[128];
|
||||
const float avgFPS = 1000.0f / (((float)this->renderTime / this->frameCount ) / 1e6f);
|
||||
const float renderFPS = 1000.0f / (((float)this->renderTime / this->renderCount) / 1e6f);
|
||||
snprintf(str, sizeof(str), "UPS: %8.4f, FPS: %8.4f", avgFPS, renderFPS);
|
||||
SDL_Color color = {0xff, 0xff, 0xff};
|
||||
SDL_Surface *textSurface = NULL;
|
||||
if (!(textSurface = TTF_RenderText_Blended(this->params.font, str, color)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text");
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FPS_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, textSurface->w );
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
textSurface->format->BytesPerPixel,
|
||||
textSurface->w,
|
||||
textSurface->h,
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
textSurface->pixels
|
||||
);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->fpsRect.x = 5;
|
||||
this->fpsRect.y = 5;
|
||||
this->fpsRect.w = textSurface->w;
|
||||
this->fpsRect.h = textSurface->h;
|
||||
|
||||
SDL_FreeSurface(textSurface);
|
||||
|
||||
this->renderTime = 0;
|
||||
this->frameCount = 0;
|
||||
this->renderCount = 0;
|
||||
this->fpsTexture = true;
|
||||
|
||||
glNewList(this->fpsList, GL_COMPILE);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FPS_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f , 0.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y );
|
||||
glTexCoord2f(1.0f , 0.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y );
|
||||
glTexCoord2f(0.0f , 1.0f); glVertex2i(this->fpsRect.x , this->fpsRect.y + this->fpsRect.h);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->fpsRect.x + this->fpsRect.w, this->fpsRect.y + this->fpsRect.h);
|
||||
glEnd();
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
glPopMatrix();
|
||||
glEndList();
|
||||
}
|
||||
|
||||
bool frameUpdate;
|
||||
if (!draw_frame(this, &frameUpdate))
|
||||
return false;
|
||||
|
||||
bool newShape;
|
||||
update_mouse_shape(this, &newShape);
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
glCallList(this->texList + this->texIndex);
|
||||
draw_mouse(this);
|
||||
if (this->fpsTexture)
|
||||
glCallList(this->fpsList);
|
||||
|
||||
if (this->opt.preventBuffer)
|
||||
{
|
||||
unsigned int before, after;
|
||||
glXGetVideoSyncSGI(&before);
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
// wait for the swap to happen to ensure we dont buffer frames //
|
||||
glXGetVideoSyncSGI(&after);
|
||||
if (before == after)
|
||||
glXWaitVideoSyncSGI(1, 0, &before);
|
||||
}
|
||||
else
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
const uint64_t t = nanotime();
|
||||
this->renderTime += t - this->lastFrameTime;
|
||||
this->lastFrameTime = t;
|
||||
++this->renderCount;
|
||||
|
||||
this->mouseUpdate = false;
|
||||
this->lastMouseDraw = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_opt_mipmap(void * opaque, const char *value)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.mipmap = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_vsync(void * opaque, const char *value)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.vsync = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static void handle_opt_prevent_buffer(void * opaque, const char *value)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->opt.preventBuffer = LG_RendererValueToBool(value);
|
||||
}
|
||||
|
||||
static LG_RendererOpt opengl_options[] =
|
||||
{
|
||||
{
|
||||
.name = "mipmap",
|
||||
.desc = "Enable or disable mipmapping [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_mipmap
|
||||
},
|
||||
{
|
||||
.name = "vsync",
|
||||
.desc ="Enable or disable vsync [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_vsync
|
||||
},
|
||||
{
|
||||
.name = "preventBuffer",
|
||||
.desc = "Prevent the driver from buffering frames [default: enabled]",
|
||||
.validator = LG_RendererValidatorBool,
|
||||
.handler = handle_opt_prevent_buffer
|
||||
}
|
||||
};
|
||||
|
||||
const LG_Renderer LGR_OpenGL =
|
||||
{
|
||||
.get_name = opengl_get_name,
|
||||
.options = opengl_options,
|
||||
.option_count = LGR_OPTION_COUNT(opengl_options),
|
||||
.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,
|
||||
.render = opengl_render
|
||||
};
|
||||
|
||||
static bool _check_gl_error(unsigned int line, const char * name)
|
||||
{
|
||||
GLenum error = glGetError();
|
||||
if (error == GL_NO_ERROR)
|
||||
return false;
|
||||
|
||||
const GLubyte * errStr = gluErrorString(error);
|
||||
DEBUG_ERROR("%d: %s = %d (%s)", line, name, error, errStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool configure(struct Inst * this, SDL_Window *window)
|
||||
{
|
||||
LG_LOCK(this->formatLock);
|
||||
if (!this->reconfigure)
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return this->configured;
|
||||
}
|
||||
|
||||
if (this->configured)
|
||||
deconfigure(this);
|
||||
|
||||
this->glContext = SDL_GL_CreateContext(window);
|
||||
if (!this->glContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the OpenGL context");
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->doneInfo)
|
||||
{
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
this->doneInfo = true;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(this->opt.vsync ? 1 : 0);
|
||||
|
||||
// check if the GPU supports GL_ARB_buffer_storage first
|
||||
// there is no advantage to this renderer if it is not present.
|
||||
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
|
||||
if (!gluCheckExtension((const GLubyte *)"GL_ARB_buffer_storage", extensions))
|
||||
{
|
||||
DEBUG_INFO("The GPU doesn't support GL_ARB_buffer_storage");
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// assume 24 and 32 bit formats are RGB and RGBA
|
||||
switch(this->format.bpp)
|
||||
{
|
||||
case 24:
|
||||
this->intFormat = GL_RGB8;
|
||||
this->vboFormat = GL_BGR;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
this->intFormat = GL_RGBA8;
|
||||
this->vboFormat = GL_BGRA;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_INFO("%d bpp not supported", this->format.bpp);
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate the texture size in bytes
|
||||
this->texSize = this->format.height * this->format.pitch;
|
||||
|
||||
// generate lists for drawing
|
||||
this->texList = glGenLists(BUFFER_COUNT);
|
||||
this->fpsList = glGenLists(1);
|
||||
this->mouseList = glGenLists(1);
|
||||
|
||||
// generate the pixel unpack buffers
|
||||
glGenBuffers(1, this->vboID);
|
||||
if (check_gl_error("glGenBuffers"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
this->hasBuffers = true;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[0]);
|
||||
if (check_gl_error("glBindBuffer"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
glBufferStorage(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
this->texSize * BUFFER_COUNT,
|
||||
NULL,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_PERSISTENT_BIT
|
||||
);
|
||||
if (check_gl_error("glBufferStorage"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->texPixels[0] = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
this->texSize * BUFFER_COUNT,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_FLUSH_EXPLICIT_BIT
|
||||
);
|
||||
|
||||
if (check_gl_error("glMapBufferRange"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 1; i < BUFFER_COUNT; ++i)
|
||||
this->texPixels[i] = this->texPixels[i-1] + this->texSize;
|
||||
|
||||
// create the textures
|
||||
glGenTextures(TEXTURE_COUNT, this->textures);
|
||||
if (check_gl_error("glGenTextures"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
this->hasTextures = true;
|
||||
|
||||
// create the frame texture
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
if (check_gl_error("glBindTexture"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
this->intFormat,
|
||||
this->format.width,
|
||||
this->format.height * BUFFER_COUNT,
|
||||
0,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(void*)0
|
||||
);
|
||||
if (check_gl_error("glTexImage2D"))
|
||||
{
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
// configure the texture
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; ++i)
|
||||
{
|
||||
const float ts = (1.0f / BUFFER_COUNT) * i;
|
||||
const float te = (1.0f / BUFFER_COUNT) + ts;
|
||||
|
||||
// create the display lists
|
||||
glNewList(this->texList + i, GL_COMPILE);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[FRAME_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, ts); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, ts); glVertex2i(this->format.width, 0 );
|
||||
glTexCoord2f(0.0f, te); glVertex2i(0 , this->format.height);
|
||||
glTexCoord2f(1.0f, te); glVertex2i(this->format.width, this->format.height);
|
||||
glEnd();
|
||||
glEndList();
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_COLOR_MATERIAL);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
this->resizeWindow = true;
|
||||
this->drawStart = nanotime();
|
||||
glXGetVideoSyncSGI(&this->gpuFrameCount);
|
||||
|
||||
this->configured = true;
|
||||
this->reconfigure = false;
|
||||
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void deconfigure(struct Inst * this)
|
||||
{
|
||||
if (!this->configured)
|
||||
return;
|
||||
|
||||
if (this->hasTextures)
|
||||
{
|
||||
glDeleteTextures(TEXTURE_COUNT, this->textures);
|
||||
this->hasTextures = false;
|
||||
}
|
||||
|
||||
if (this->hasBuffers)
|
||||
{
|
||||
glDeleteBuffers(1, this->vboID);
|
||||
this->hasBuffers = false;
|
||||
}
|
||||
|
||||
if (this->glContext)
|
||||
{
|
||||
SDL_GL_DeleteContext(this->glContext);
|
||||
this->glContext = NULL;
|
||||
}
|
||||
|
||||
this->configured = false;
|
||||
}
|
||||
|
||||
static void update_mouse_shape(struct Inst * this, bool * newShape)
|
||||
{
|
||||
LG_LOCK(this->mouseLock);
|
||||
*newShape = this->newShape;
|
||||
if (!this->newShape)
|
||||
{
|
||||
LG_UNLOCK(this->mouseLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const LG_RendererCursor cursor = this->mouseCursor;
|
||||
const int width = this->mouseWidth;
|
||||
const int height = this->mouseHeight;
|
||||
const int pitch = this->mousePitch;
|
||||
const uint8_t * data = this->mouseData;
|
||||
|
||||
this->mouseType = cursor;
|
||||
switch(cursor)
|
||||
{
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_BGRA, // windows cursors are in BGRA format
|
||||
GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = height;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_BLEND);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, height);
|
||||
glEnd();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_BLEND);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
const int hheight = height / 2;
|
||||
uint32_t d[width * height];
|
||||
for(int y = 0; y < hheight; ++y)
|
||||
for(int x = 0; x < width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (pitch * y) + (x / 8);
|
||||
const uint8_t * srcXor = srcAnd + pitch * hheight;
|
||||
const uint8_t mask = 0x80 >> (x % 8);
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
d[y * width + x ] = andMask;
|
||||
d[y * width + x + width * hheight] = xorMask;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
|
||||
glTexImage2D
|
||||
(
|
||||
GL_TEXTURE_2D,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
width ,
|
||||
height ,
|
||||
0 ,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
d
|
||||
);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
this->mousePos.w = width;
|
||||
this->mousePos.h = hheight;
|
||||
|
||||
glNewList(this->mouseList, GL_COMPILE);
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
glBindTexture(GL_TEXTURE_2D, this->textures[MOUSE_TEXTURE]);
|
||||
glLogicOp(GL_AND);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glLogicOp(GL_XOR);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.5f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.5f); glVertex2i(width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , hheight);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(width, hheight);
|
||||
glEnd();
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
glEndList();
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
//TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this->mouseUpdate = true;
|
||||
LG_UNLOCK(this->mouseLock);
|
||||
}
|
||||
|
||||
static bool draw_frame(struct Inst * this, bool * frameUpdate)
|
||||
{
|
||||
LG_LOCK(this->syncLock);
|
||||
*frameUpdate = this->frameUpdate;
|
||||
if (!this->frameUpdate)
|
||||
{
|
||||
LG_UNLOCK(this->syncLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
this->texIndex = this->wTexIndex;
|
||||
if (++this->wTexIndex == BUFFER_COUNT)
|
||||
this->wTexIndex = 0;
|
||||
this->frameUpdate = false;
|
||||
LG_UNLOCK(this->syncLock);
|
||||
|
||||
LG_LOCK(this->formatLock);
|
||||
|
||||
// bind the texture and update it
|
||||
glBindTexture(GL_TEXTURE_2D , this->textures[FRAME_TEXTURE]);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[0] );
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , 4 );
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride );
|
||||
|
||||
// copy the buffer to the texture
|
||||
glFlushMappedBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
this->texSize * this->texIndex,
|
||||
this->texSize
|
||||
);
|
||||
|
||||
// update the texture
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
this->texIndex * this->format.height,
|
||||
this->format.width ,
|
||||
this->format.height,
|
||||
this->vboFormat,
|
||||
GL_UNSIGNED_BYTE,
|
||||
(void*)(this->texIndex * this->texSize)
|
||||
);
|
||||
if (check_gl_error("glTexSubImage2D"))
|
||||
DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
|
||||
this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
|
||||
);
|
||||
|
||||
// unbind the buffer
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
const bool mipmap = this->opt.mipmap && (
|
||||
(this->format.width > this->destRect.w) ||
|
||||
(this->format.height > this->destRect.h));
|
||||
|
||||
if (mipmap)
|
||||
{
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
LG_UNLOCK(this->formatLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void draw_mouse(struct Inst * this)
|
||||
{
|
||||
if (!this->mouseVisible)
|
||||
return;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
|
||||
glCallList(this->mouseList);
|
||||
glPopMatrix();
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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 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;
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -1,900 +0,0 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017 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.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <spice/protocol.h>
|
||||
#include <spice/error_codes.h>
|
||||
|
||||
#include "messages.h"
|
||||
|
||||
#ifdef DEBUG_SPICE_MOUSE
|
||||
#define DEBUG_MOUSE(fmt, args...) DEBUG_PRINT("[M]", fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_MOUSE(fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_SPICE_KEYBOARD
|
||||
#define DEBUG_KEYBOARD(fmt, args...) DEBUG_PRINT("[K]", fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_KEYBOARD(fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// internal structures
|
||||
struct SpiceChannel
|
||||
{
|
||||
bool connected;
|
||||
bool initDone;
|
||||
uint8_t channelType;
|
||||
int socket;
|
||||
uint32_t ackFrequency;
|
||||
uint32_t ackCount;
|
||||
uint32_t serial;
|
||||
};
|
||||
|
||||
struct SpiceKeyboard
|
||||
{
|
||||
uint32_t modifiers;
|
||||
};
|
||||
|
||||
#define SPICE_MOUSE_QUEUE_SIZE 32
|
||||
|
||||
struct SpiceMouse
|
||||
{
|
||||
uint32_t buttonState;
|
||||
|
||||
int sentCount;
|
||||
SpiceMsgcMouseMotion queue[SPICE_MOUSE_QUEUE_SIZE];
|
||||
int rpos, wpos;
|
||||
int queueLen;
|
||||
};
|
||||
|
||||
struct Spice
|
||||
{
|
||||
char password[32];
|
||||
struct sockaddr_in addr;
|
||||
|
||||
uint32_t sessionID;
|
||||
uint32_t channelID;
|
||||
struct SpiceChannel scMain;
|
||||
struct SpiceChannel scInputs;
|
||||
|
||||
struct SpiceKeyboard kb;
|
||||
struct SpiceMouse mouse;
|
||||
};
|
||||
|
||||
// globals
|
||||
struct Spice spice =
|
||||
{
|
||||
.sessionID = 0,
|
||||
.scMain .connected = false,
|
||||
.scMain .channelType = SPICE_CHANNEL_MAIN,
|
||||
.scInputs.connected = false,
|
||||
.scInputs.channelType = SPICE_CHANNEL_INPUTS,
|
||||
};
|
||||
|
||||
// internal forward decls
|
||||
bool spice_connect_channel (struct SpiceChannel * channel);
|
||||
void spice_disconnect_channel(struct SpiceChannel * channel);
|
||||
|
||||
bool spice_process_ack(struct SpiceChannel * channel);
|
||||
|
||||
bool spice_on_common_read (struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled);
|
||||
bool spice_on_main_channel_read ();
|
||||
bool spice_on_inputs_channel_read();
|
||||
|
||||
bool spice_read (const struct SpiceChannel * channel, void * buffer, const ssize_t size);
|
||||
ssize_t spice_write (const struct SpiceChannel * channel, const void * buffer, const ssize_t size);
|
||||
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size);
|
||||
bool spice_discard (const struct SpiceChannel * channel, ssize_t size);
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password)
|
||||
{
|
||||
strncpy(spice.password, password, sizeof(spice.password));
|
||||
memset(&spice.addr, 0, sizeof(struct sockaddr_in));
|
||||
inet_pton(AF_INET, host, &spice.addr.sin_addr);
|
||||
spice.addr.sin_family = AF_INET;
|
||||
spice.addr.sin_port = htons(port);
|
||||
|
||||
spice.channelID = 0;
|
||||
if (!spice_connect_channel(&spice.scMain))
|
||||
{
|
||||
DEBUG_ERROR("connect main channel failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void spice_disconnect()
|
||||
{
|
||||
spice_disconnect_channel(&spice.scMain );
|
||||
spice_disconnect_channel(&spice.scInputs);
|
||||
|
||||
spice.sessionID = 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_ready()
|
||||
{
|
||||
return spice.scMain.connected &&
|
||||
spice.scInputs.connected;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_process()
|
||||
{
|
||||
fd_set readSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_SET(spice.scMain.socket , &readSet);
|
||||
FD_SET(spice.scInputs.socket, &readSet);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout);
|
||||
if (rc < 0)
|
||||
{
|
||||
DEBUG_ERROR("select failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < FD_SETSIZE; ++i)
|
||||
if (FD_ISSET(i, &readSet))
|
||||
{
|
||||
if (i == spice.scMain.socket)
|
||||
{
|
||||
if (spice_on_main_channel_read())
|
||||
{
|
||||
if (spice.scMain.connected && !spice_process_ack(&spice.scMain))
|
||||
{
|
||||
DEBUG_ERROR("failed to process ack on main channel");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("failed to perform read on main channel");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (spice.scInputs.connected && i == spice.scInputs.socket)
|
||||
{
|
||||
if (spice_on_inputs_channel_read())
|
||||
{
|
||||
if (!spice_process_ack(&spice.scInputs))
|
||||
{
|
||||
DEBUG_ERROR("failed to process ack on inputs channel");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("failed to perform read on inputs channel");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_process_ack(struct SpiceChannel * channel)
|
||||
{
|
||||
if (channel->ackFrequency == 0)
|
||||
return true;
|
||||
|
||||
if (channel->ackCount++ != channel->ackFrequency)
|
||||
return true;
|
||||
|
||||
channel->ackCount = 0;
|
||||
return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_common_read(struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled)
|
||||
{
|
||||
if (!spice_read(channel, header, sizeof(SpiceDataHeader)))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
*handled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf("socket: %d, serial: %6u, type: %2u, size %6u, sub_list %4u\n",
|
||||
channel->socket,
|
||||
header->serial, header->type, header->size, header->sub_list);
|
||||
#endif
|
||||
|
||||
if (!channel->initDone)
|
||||
{
|
||||
*handled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
switch(header->type)
|
||||
{
|
||||
case SPICE_MSG_MIGRATE:
|
||||
case SPICE_MSG_MIGRATE_DATA:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA");
|
||||
|
||||
*handled = true;
|
||||
DEBUG_WARN("migration is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
case SPICE_MSG_SET_ACK:
|
||||
{
|
||||
DEBUG_INFO("SPICE_MSG_SET_ACK");
|
||||
*handled = true;
|
||||
|
||||
SpiceMsgSetAck in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
channel->ackFrequency = in.window;
|
||||
|
||||
SpiceMsgcAckSync out;
|
||||
out.generation = in.generation;
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_PING:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_PING");
|
||||
*handled = true;
|
||||
|
||||
SpiceMsgPing in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
if (!spice_discard(channel, header->size - sizeof(in)))
|
||||
{
|
||||
DEBUG_ERROR("failed discarding enough bytes from the ping packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcPong out;
|
||||
out.id = in.id;
|
||||
out.timestamp = in.timestamp;
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_WAIT_FOR_CHANNELS:
|
||||
case SPICE_MSG_DISCONNECTING :
|
||||
{
|
||||
*handled = true;
|
||||
DEBUG_FIXME("ignored wait-for-channels or disconnect message");
|
||||
return false;
|
||||
}
|
||||
|
||||
case SPICE_MSG_NOTIFY:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_NOTIFY");
|
||||
SpiceMsgNotify in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
char msg[in.message_len+1];
|
||||
if (!spice_read(channel, msg, in.message_len+1))
|
||||
return false;
|
||||
|
||||
DEBUG_INFO("notify message: %s", msg);
|
||||
*handled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
*handled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_main_channel_read()
|
||||
{
|
||||
struct SpiceChannel *channel = &spice.scMain;
|
||||
|
||||
SpiceDataHeader header;
|
||||
bool handled;
|
||||
|
||||
if (!spice_on_common_read(channel, &header, &handled))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
if (!channel->initDone)
|
||||
{
|
||||
if (header.type != SPICE_MSG_MAIN_INIT)
|
||||
{
|
||||
spice_disconnect();
|
||||
DEBUG_ERROR("expected main init message but got type %u", header.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_PROTO("SPICE_MSG_MAIN_INIT");
|
||||
|
||||
channel->initDone = true;
|
||||
SpiceMsgMainInit msg;
|
||||
if (!spice_read(channel, &msg, sizeof(msg)))
|
||||
{
|
||||
spice_disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
spice.sessionID = msg.session_id;
|
||||
if (!spice_connect_channel(&spice.scInputs))
|
||||
{
|
||||
DEBUG_ERROR("failed to connect inputs channel");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false))
|
||||
{
|
||||
DEBUG_ERROR("failed to set mouse mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DEBUG_WARN("main channel unhandled message type %u", header.type);
|
||||
spice_discard(channel, header.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_inputs_channel_read()
|
||||
{
|
||||
struct SpiceChannel *channel = &spice.scInputs;
|
||||
|
||||
SpiceDataHeader header;
|
||||
bool handled;
|
||||
|
||||
if (!spice_on_common_read(channel, &header, &handled))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
switch(header.type)
|
||||
{
|
||||
case SPICE_MSG_INPUTS_INIT:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_INIT");
|
||||
|
||||
if (channel->initDone)
|
||||
{
|
||||
DEBUG_ERROR("input init message already done");
|
||||
return false;
|
||||
}
|
||||
|
||||
channel->initDone = true;
|
||||
|
||||
SpiceMsgInputsInit in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_INPUTS_KEY_MODIFIERS:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS");
|
||||
SpiceMsgInputsInit in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
spice.kb.modifiers = in.modifiers;
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK");
|
||||
int sent = 0;
|
||||
while(spice.mouse.queueLen && sent < 4)
|
||||
{
|
||||
SpiceMsgcMouseMotion *msg = &spice.mouse.queue[spice.mouse.rpos];
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_INPUTS_MOUSE_MOTION, msg, sizeof(SpiceMsgcMouseMotion)))
|
||||
{
|
||||
DEBUG_ERROR("failed to send post ack");
|
||||
spice.mouse.sentCount = sent;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (++spice.mouse.rpos == SPICE_MOUSE_QUEUE_SIZE)
|
||||
spice.mouse.rpos = 0;
|
||||
|
||||
++sent;
|
||||
--spice.mouse.queueLen;
|
||||
}
|
||||
|
||||
spice.mouse.sentCount = sent;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_WARN("inputs channel unhandled message type %u", header.type);
|
||||
spice_discard(channel, header.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_connect_channel(struct SpiceChannel * channel)
|
||||
{
|
||||
channel->initDone = false;
|
||||
channel->ackFrequency = 0;
|
||||
channel->ackCount = 0;
|
||||
channel->serial = 0;
|
||||
|
||||
channel->socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (channel->socket == -1)
|
||||
return false;
|
||||
|
||||
int flag = 1;
|
||||
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
|
||||
|
||||
if (connect(channel->socket, (const struct sockaddr *)&spice.addr, sizeof(spice.addr)) == -1)
|
||||
{
|
||||
DEBUG_ERROR("socket connect failure");
|
||||
close(channel->socket);
|
||||
return false;
|
||||
}
|
||||
channel->connected = true;
|
||||
|
||||
SpiceLinkHeader header =
|
||||
{
|
||||
.magic = SPICE_MAGIC ,
|
||||
.major_version = SPICE_VERSION_MAJOR,
|
||||
.minor_version = SPICE_VERSION_MINOR,
|
||||
.size = sizeof(SpiceLinkMess)
|
||||
};
|
||||
|
||||
SpiceLinkMess message =
|
||||
{
|
||||
.connection_id = spice.sessionID,
|
||||
.channel_type = channel->channelType,
|
||||
.channel_id = spice.channelID,
|
||||
.num_common_caps = 0,
|
||||
.num_channel_caps = 0,
|
||||
.caps_offset = sizeof(SpiceLinkMess)
|
||||
};
|
||||
|
||||
spice_write(channel, &header , sizeof(header ));
|
||||
spice_write(channel, &message, sizeof(message));
|
||||
|
||||
if (!spice_read(channel, &header, sizeof(header)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkHeader");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR)
|
||||
{
|
||||
DEBUG_ERROR("invalid or unsupported protocol version");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.size < sizeof(SpiceLinkReply))
|
||||
{
|
||||
DEBUG_ERROR("reported data size too small");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceLinkReply reply;
|
||||
if (!spice_read(channel, &reply, sizeof(reply)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkReply");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reply.error != SPICEC_ERROR_CODE_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("server replied with error %u", reply.error);
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t capsCommon [reply.num_common_caps ];
|
||||
uint32_t capsChannel[reply.num_channel_caps];
|
||||
spice_read(channel, &capsCommon , sizeof(capsCommon ));
|
||||
spice_read(channel, &capsChannel, sizeof(capsChannel));
|
||||
|
||||
BIO *bioKey = BIO_new(BIO_s_mem());
|
||||
if (!bioKey)
|
||||
{
|
||||
DEBUG_ERROR("failed to allocate bioKey");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO_write(bioKey, reply.pub_key, SPICE_TICKET_PUBKEY_BYTES);
|
||||
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
|
||||
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
|
||||
|
||||
char enc[RSA_size(rsa)];
|
||||
if (RSA_public_encrypt(
|
||||
strlen(spice.password) + 1,
|
||||
(uint8_t*)spice.password,
|
||||
(uint8_t*)enc,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING
|
||||
) <= 0)
|
||||
{
|
||||
DEBUG_ERROR("rsa public encrypt failed");
|
||||
spice_disconnect_channel(channel);
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t rsaSize = RSA_size(rsa);
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
|
||||
if (!spice_write(channel, enc, rsaSize))
|
||||
{
|
||||
DEBUG_ERROR("failed to write encrypted data");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t linkResult;
|
||||
if (!spice_read(channel, &linkResult, sizeof(linkResult)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkResult");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (linkResult != SPICE_LINK_ERR_OK)
|
||||
{
|
||||
DEBUG_ERROR("connect code error %u", linkResult);
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void spice_disconnect_channel(struct SpiceChannel * channel)
|
||||
{
|
||||
if (channel->connected)
|
||||
close(channel->socket);
|
||||
channel->connected = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
ssize_t spice_write(const struct SpiceChannel * channel, const void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!channel->connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
DEBUG_ERROR("invalid buffer argument supplied");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t len = send(channel->socket, buffer, size, 0);
|
||||
if (len != size)
|
||||
DEBUG_WARN("incomplete write");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size)
|
||||
{
|
||||
SpiceDataHeader header;
|
||||
header.serial = channel->serial++;
|
||||
header.type = type;
|
||||
header.size = size;
|
||||
header.sub_list = 0;
|
||||
|
||||
if (spice_write(channel, &header, sizeof(header)) != sizeof(header))
|
||||
{
|
||||
DEBUG_ERROR("failed to write message header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spice_write(channel, buffer, size) != size)
|
||||
{
|
||||
DEBUG_ERROR("failed to write message body");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_read(const struct SpiceChannel * channel, void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!channel->connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
DEBUG_ERROR("invalid buffer argument supplied");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t len = read(channel->socket, buffer, size);
|
||||
if (len != size)
|
||||
{
|
||||
DEBUG_ERROR("incomplete write");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_discard(const struct SpiceChannel * channel, ssize_t size)
|
||||
{
|
||||
while(size)
|
||||
{
|
||||
char c[8192];
|
||||
size_t len = read(channel->socket, c, size > sizeof(c) ? sizeof(c) : size);
|
||||
if (len <= 0)
|
||||
return false;
|
||||
|
||||
size -= len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_key_down(uint32_t code)
|
||||
{
|
||||
DEBUG_KEYBOARD("%u", code);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code > 0x100)
|
||||
code = 0xe0 | ((code - 0x100) << 8);
|
||||
|
||||
SpiceMsgcKeyDown msg;
|
||||
msg.code = code;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_key_up(uint32_t code)
|
||||
{
|
||||
DEBUG_KEYBOARD("%u", code);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code < 0x100)
|
||||
code |= 0x80;
|
||||
else
|
||||
code = 0x80e0 | ((code - 0x100) << 8);
|
||||
|
||||
SpiceMsgcKeyDown msg;
|
||||
msg.code = code;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_mode(bool server)
|
||||
{
|
||||
DEBUG_MOUSE("%s", server ? "server" : "client");
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcMainMouseModeRequest msg;
|
||||
msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT;
|
||||
|
||||
return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_position(uint32_t x, uint32_t y)
|
||||
{
|
||||
DEBUG_MOUSE("x=%u, y=%u", x, y);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcMousePosition msg;
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
msg.display_id = 0;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_motion(int32_t x, int32_t y)
|
||||
{
|
||||
DEBUG_MOUSE("x=%d, y=%d", x, y);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spice.mouse.sentCount == 4)
|
||||
{
|
||||
if (spice.mouse.queueLen == SPICE_MOUSE_QUEUE_SIZE)
|
||||
{
|
||||
DEBUG_ERROR("mouse motion ringbuffer full!");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseMotion *msg =
|
||||
&spice.mouse.queue[spice.mouse.wpos++];
|
||||
msg->x = x;
|
||||
msg->y = y;
|
||||
msg->button_state = spice.mouse.buttonState;
|
||||
|
||||
if (spice.mouse.wpos == SPICE_MOUSE_QUEUE_SIZE)
|
||||
spice.mouse.wpos = 0;
|
||||
|
||||
++spice.mouse.queueLen;
|
||||
return true;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseMotion msg;
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
++spice.mouse.sentCount;
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_press(uint32_t button)
|
||||
{
|
||||
DEBUG_MOUSE("%u", button);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(button)
|
||||
{
|
||||
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
||||
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
||||
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
||||
}
|
||||
|
||||
SpiceMsgcMousePress msg;
|
||||
msg.button = button;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_release(uint32_t button)
|
||||
{
|
||||
DEBUG_MOUSE("%u", button);
|
||||
if (!spice.scInputs.connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(button)
|
||||
{
|
||||
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
||||
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
||||
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseRelease msg;
|
||||
msg.button = button;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg));
|
||||
}
|
||||
567
client/src/app.c
Normal file
567
client/src/app.c
Normal file
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
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 "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 (!g_state.lgr || !g_params.showAlerts)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
const int length = vsnprintf(NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
char *buffer = malloc(length + 1);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, length + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
g_state.lgr->on_alert(
|
||||
g_state.lgrData,
|
||||
type,
|
||||
buffer,
|
||||
NULL
|
||||
);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
|
||||
{
|
||||
// don't allow duplicate binds
|
||||
if (g_state.bindings[sc])
|
||||
{
|
||||
DEBUG_INFO("Key already bound");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KeybindHandle handle = (KeybindHandle)malloc(sizeof(struct KeybindHandle));
|
||||
handle->sc = sc;
|
||||
handle->callback = callback;
|
||||
handle->opaque = opaque;
|
||||
|
||||
g_state.bindings[sc] = handle;
|
||||
g_state.keyDescription[sc] = description;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void app_releaseKeybind(KeybindHandle * handle)
|
||||
{
|
||||
if (!*handle)
|
||||
return;
|
||||
|
||||
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);
|
||||
736
client/src/config.c
Normal file
736
client/src/config.c
Normal file
@@ -0,0 +1,736 @@
|
||||
/*
|
||||
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 "main.h"
|
||||
#include "config.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "common/option.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
|
||||
#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 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();
|
||||
|
||||
static struct Option options[] =
|
||||
{
|
||||
// app options
|
||||
{
|
||||
.module = "app",
|
||||
.name = "configFile",
|
||||
.description = "A file to read additional configuration from",
|
||||
.shortopt = 'C',
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = NULL,
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "renderer",
|
||||
.description = "Specify the renderer to use",
|
||||
.shortopt = 'g',
|
||||
.type = OPTION_TYPE_CUSTOM,
|
||||
.parser = optRendererParse,
|
||||
.getValues = optRendererValues,
|
||||
.toString = optRendererToString
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "license",
|
||||
.description = "Show the license for this application and then terminate",
|
||||
.shortopt = 'l',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "cursorPollInterval",
|
||||
.description = "How often to check for a cursor update in microseconds",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 1000
|
||||
},
|
||||
{
|
||||
.module = "app",
|
||||
.name = "framePollInterval",
|
||||
.description = "How often to check for a frame update in microseconds",
|
||||
.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
|
||||
{
|
||||
.module = "win",
|
||||
.name = "title",
|
||||
.description = "The window title",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = "Looking Glass (client)"
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "position",
|
||||
.description = "Initial window position at startup",
|
||||
.type = OPTION_TYPE_CUSTOM,
|
||||
.parser = optPosParse,
|
||||
.getValues = optPosValues,
|
||||
.toString = optPosToString
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "size",
|
||||
.description = "Initial window size at startup",
|
||||
.type = OPTION_TYPE_CUSTOM,
|
||||
.parser = optSizeParse,
|
||||
.getValues = optSizeValues,
|
||||
.toString = optSizeToString
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "autoResize",
|
||||
.description = "Auto resize the window to the guest",
|
||||
.shortopt = 'a',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "allowResize",
|
||||
.description = "Allow the window to be manually resized",
|
||||
.shortopt = 'n',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "keepAspect",
|
||||
.description = "Maintain the correct aspect ratio",
|
||||
.shortopt = 'r',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "forceAspect",
|
||||
.description = "Force the window to maintain the aspect ratio",
|
||||
.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",
|
||||
.description = "Borderless mode",
|
||||
.shortopt = 'd',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "fullScreen",
|
||||
.description = "Launch in fullscreen borderless mode",
|
||||
.shortopt = 'F',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "maximize",
|
||||
.description = "Launch window maximized",
|
||||
.shortopt = 'T',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "minimizeOnFocusLoss",
|
||||
.description = "Minimize window on focus loss",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "fpsMin",
|
||||
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
|
||||
.shortopt = 'K',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = -1,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "showFPS",
|
||||
.description = "Enable the FPS & UPS display",
|
||||
.shortopt = 'k',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "ignoreQuit",
|
||||
.description = "Ignore requests to quit (ie: Alt+F4)",
|
||||
.shortopt = 'Q',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "noScreensaver",
|
||||
.description = "Prevent the screensaver from starting",
|
||||
.shortopt = 'S',
|
||||
.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",
|
||||
.description = "Show on screen alert messages",
|
||||
.shortopt = 'q',
|
||||
.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
|
||||
{
|
||||
.module = "input",
|
||||
.name = "grabKeyboard",
|
||||
.description = "Grab the keyboard in capture mode",
|
||||
.shortopt = 'G',
|
||||
.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 <linux/input-event-codes.h> for valid values",
|
||||
.shortopt = 'm',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.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",
|
||||
.name = "hideCursor",
|
||||
.description = "Hide the local mouse cursor",
|
||||
.shortopt = 'M',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "mouseSens",
|
||||
.description = "Initial mouse sensitivity when in capture mode (-9 to 9)",
|
||||
.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
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "enable",
|
||||
.description = "Enable the built in SPICE client for input and/or clipboard support",
|
||||
.shortopt = 's',
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "host",
|
||||
.description = "The SPICE server host or UNIX socket",
|
||||
.shortopt = 'c',
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = "127.0.0.1"
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "port",
|
||||
.description = "The SPICE server port (0 = unix socket)",
|
||||
.shortopt = 'p',
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 5900
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "input",
|
||||
.description = "Use SPICE to send keyboard and mouse input events to the guest",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "clipboard",
|
||||
.description = "Use SPICE to syncronize the clipboard contents with the guest",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "clipboardToVM",
|
||||
.description = "Allow the clipboard to be syncronized TO the VM",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "clipboardToLocal",
|
||||
.description = "Allow the clipboard to be syncronized FROM the VM",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "scaleCursor",
|
||||
.description = "Scale cursor input position to screen size when up/down scaled",
|
||||
.shortopt = 'j',
|
||||
.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)
|
||||
{
|
||||
g_params.center = true;
|
||||
g_params.w = 1024;
|
||||
g_params.h = 768;
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
bool config_load(int argc, char * argv[])
|
||||
{
|
||||
// load any global options first
|
||||
struct stat st;
|
||||
if (stat("/etc/looking-glass-client.ini", &st) >= 0)
|
||||
{
|
||||
DEBUG_INFO("Loading config from: /etc/looking-glass-client.ini");
|
||||
if (!option_load("/etc/looking-glass-client.ini"))
|
||||
return false;
|
||||
}
|
||||
|
||||
// load user's local options
|
||||
struct passwd * pw = getpwuid(getuid());
|
||||
char * localFile;
|
||||
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
|
||||
if (stat(localFile, &st) >= 0)
|
||||
{
|
||||
DEBUG_INFO("Loading config from: %s", localFile);
|
||||
if (!option_load(localFile))
|
||||
{
|
||||
free(localFile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
free(localFile);
|
||||
|
||||
// parse the command line arguments
|
||||
if (!option_parse(argc, argv))
|
||||
return false;
|
||||
|
||||
// if a file was specified to also load, do it
|
||||
const char * configFile = option_get_string("app", "configFile");
|
||||
if (configFile)
|
||||
{
|
||||
DEBUG_INFO("Loading config from: %s", configFile);
|
||||
if (!option_load(configFile))
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate the values are sane
|
||||
if (!option_validate())
|
||||
return false;
|
||||
|
||||
if (option_get_bool("app", "license"))
|
||||
{
|
||||
doLicense();
|
||||
return false;
|
||||
}
|
||||
|
||||
// setup the application params for the basic types
|
||||
g_params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
|
||||
g_params.framePollInterval = option_get_int ("app", "framePollInterval" );
|
||||
g_params.allowDMA = option_get_bool ("app", "allowDMA" );
|
||||
|
||||
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" );
|
||||
|
||||
if (g_params.noScreensaver && g_params.autoScreensaver)
|
||||
{
|
||||
fprintf(stderr, "win:noScreensaver (-S) and win:autoScreensaver "
|
||||
"can't be used simultaneously\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
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"))
|
||||
{
|
||||
g_params.spiceHost = option_get_string("spice", "host");
|
||||
g_params.spicePort = option_get_int ("spice", "port");
|
||||
|
||||
g_params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
|
||||
if (g_params.useSpiceClipboard)
|
||||
{
|
||||
g_params.clipboardToVM = option_get_bool("spice", "clipboardToVM" );
|
||||
g_params.clipboardToLocal = option_get_bool("spice", "clipboardToLocal");
|
||||
|
||||
if (!g_params.clipboardToVM && !g_params.clipboardToLocal)
|
||||
g_params.useSpiceClipboard = false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
option_free();
|
||||
}
|
||||
|
||||
static void doLicense(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\n"
|
||||
"Looking Glass - KVM FrameRelay (KVMFR) Client\n"
|
||||
"Copyright(C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>\n"
|
||||
"https://looking-glass.hostfission.com\n"
|
||||
"\n"
|
||||
"This program is free software; you can redistribute it and / or modify it under\n"
|
||||
"the terms of the GNU General Public License as published by the Free Software\n"
|
||||
"Foundation; either version 2 of the License, or (at your option) any later\n"
|
||||
"version.\n"
|
||||
"\n"
|
||||
"This program is distributed in the hope that it will be useful, but WITHOUT ANY\n"
|
||||
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n"
|
||||
"PARTICULAR PURPOSE.See the GNU General Public License for more details.\n"
|
||||
"\n"
|
||||
"You should have received a copy of the GNU General Public License along with\n"
|
||||
"this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n"
|
||||
"Place, Suite 330, Boston, MA 02111 - 1307 USA\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
static bool optRendererParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (strcasecmp(str, "auto") == 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
g_params.forceRenderer = true;
|
||||
g_params.forceRendererIndex = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static StringList optRendererValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
|
||||
// this typecast is safe as the stringlist doesn't own the values
|
||||
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
||||
stringlist_push(sl, (char *)LG_Renderers[i]->get_name());
|
||||
|
||||
return sl;
|
||||
}
|
||||
|
||||
static char * optRendererToString(struct Option * opt)
|
||||
{
|
||||
if (!g_params.forceRenderer)
|
||||
return strdup("auto");
|
||||
|
||||
if (g_params.forceRendererIndex >= LG_RENDERER_COUNT)
|
||||
return NULL;
|
||||
|
||||
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)
|
||||
{
|
||||
g_params.center = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sscanf(str, "%dx%d", &g_params.x, &g_params.y) == 2)
|
||||
{
|
||||
g_params.center = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static StringList optPosValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
stringlist_push(sl, "center");
|
||||
stringlist_push(sl, "<left>x<top>, ie: 100x100");
|
||||
return sl;
|
||||
}
|
||||
|
||||
static char * optPosToString(struct Option * opt)
|
||||
{
|
||||
if (g_params.center)
|
||||
return strdup("center");
|
||||
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
|
||||
char * str = malloc(len + 1);
|
||||
sprintf(str, "%dx%d", g_params.x, g_params.y);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool optSizeParse(struct Option * opt, const char * str)
|
||||
{
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
if (sscanf(str, "%dx%d", &g_params.w, &g_params.h) == 2)
|
||||
{
|
||||
if (g_params.w < 1 || g_params.h < 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static StringList optSizeValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
stringlist_push(sl, "<left>x<top>, ie: 100x100");
|
||||
return sl;
|
||||
}
|
||||
|
||||
static char * optSizeToString(struct Option * opt)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.w, g_params.h);
|
||||
char * str = malloc(len + 1);
|
||||
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,
|
||||
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;
|
||||
}
|
||||
24
client/src/config.h
Normal file
24
client/src/config.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 <stdbool.h>
|
||||
|
||||
void config_init();
|
||||
bool config_load(int argc, char * argv[]);
|
||||
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");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user