mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-18 00:29:48 +00:00
Compare commits
658 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce5c36705e | ||
|
|
7aee4eed11 | ||
|
|
416c4bbb48 | ||
|
|
76007092d5 | ||
|
|
453b8e6a4d | ||
|
|
968b313993 | ||
|
|
4dde82c646 | ||
|
|
4c424cdbdf | ||
|
|
963e7f8393 | ||
|
|
f93c918aa5 | ||
|
|
d5f409b02e | ||
|
|
75cea21cfc | ||
|
|
df2a3b6151 | ||
|
|
fad4d18973 | ||
|
|
f4ad730cc4 | ||
|
|
67ddb70932 | ||
|
|
27c3a93d15 | ||
|
|
df9798c819 | ||
|
|
1dfa0ed218 | ||
|
|
01f5238a9d | ||
|
|
c382a5acb1 | ||
|
|
5e3a46beb9 | ||
|
|
6ed4e23b80 | ||
|
|
0851ae6f14 | ||
|
|
caebddce4d | ||
|
|
01da541815 | ||
|
|
9d6bb57eff | ||
|
|
438548c427 | ||
|
|
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 | ||
|
|
d9397610f3 | ||
|
|
f26d8fbd3e | ||
|
|
43b096a5e7 | ||
|
|
a08aad8009 | ||
|
|
2b66fa6136 | ||
|
|
e3a426f378 | ||
|
|
e5f86a824a | ||
|
|
c0b2c8e655 | ||
|
|
c5cbb948e2 | ||
|
|
58ed978767 | ||
|
|
c098967293 | ||
|
|
8fbacba82e | ||
|
|
0753e63644 | ||
|
|
5501d22a2d | ||
|
|
2f6b7e08f8 | ||
|
|
2fe800f502 | ||
|
|
ec6c35010b | ||
|
|
695822bd6d | ||
|
|
a70adb2568 | ||
|
|
21a4e15868 | ||
|
|
0cc149dd48 | ||
|
|
af8d6c4d27 | ||
|
|
2a93e2ed55 | ||
|
|
8b25f8a344 | ||
|
|
7d307c0a9c | ||
|
|
133d8ec21f | ||
|
|
f4b95eeda9 | ||
|
|
c38e38d43d | ||
|
|
1f004472cc | ||
|
|
0d1d49b00e | ||
|
|
e298f9169c | ||
|
|
9e75c7ab34 | ||
|
|
8dec59f3ff | ||
|
|
49acc6ec0b | ||
|
|
4e6c1ea648 | ||
|
|
0ac1992019 | ||
|
|
5305ec5dd1 | ||
|
|
313a4122bc | ||
|
|
065b8c1eab | ||
|
|
424dc8bb13 | ||
|
|
06e38d897d | ||
|
|
f3e19b743c | ||
|
|
deee61efa9 | ||
|
|
71c7f30265 | ||
|
|
e6c6c16d56 | ||
|
|
3784e9c06f | ||
|
|
24709ef186 | ||
|
|
db59e67ddb | ||
|
|
1815387bbb | ||
|
|
6595374a2c | ||
|
|
9d8f3a79b0 | ||
|
|
bf85358715 | ||
|
|
b77a34b8b6 | ||
|
|
287b983d27 | ||
|
|
7c5b2b5c1c | ||
|
|
758b7af754 | ||
|
|
b89a8fee04 | ||
|
|
2bb8b0227c | ||
|
|
ae4156d041 | ||
|
|
fe337cf510 | ||
|
|
7bfed41523 | ||
|
|
9bb66b7bd6 | ||
|
|
f7420317f1 | ||
|
|
c1379a45d2 | ||
|
|
9c03327701 | ||
|
|
0d8b2449cf | ||
|
|
d1bd5b3115 | ||
|
|
e03621a622 | ||
|
|
b93aca796c | ||
|
|
606da0ae47 | ||
|
|
d08fba9cf9 | ||
|
|
df13340439 | ||
|
|
b6c8136565 | ||
|
|
0948dda12f | ||
|
|
8ae9f8464b | ||
|
|
110aced7d1 | ||
|
|
3c61814c56 | ||
|
|
133001b545 | ||
|
|
106136afd6 | ||
|
|
c72afff055 | ||
|
|
d052239721 | ||
|
|
fdfa3006b4 | ||
|
|
a01d755ab5 | ||
|
|
9a9d84cd1f | ||
|
|
892a3970ff | ||
|
|
d6fee6bf0b | ||
|
|
7691093121 | ||
|
|
524183661d | ||
|
|
cd54cb179b | ||
|
|
973ad5e2b0 | ||
|
|
67bdf2ba97 | ||
|
|
ead48195db | ||
|
|
c0b67e7cb2 | ||
|
|
2ae9b479f6 | ||
|
|
3fa9f371b4 | ||
|
|
8ec4abc544 | ||
|
|
7280f305e0 | ||
|
|
7b64d35cd1 |
11
.github/issue_template.md
vendored
Normal file
11
.github/issue_template.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
### Required information
|
||||
|
||||
Host CPU:
|
||||
Host GPU:
|
||||
Guest GPU:
|
||||
Host Kernel version:
|
||||
Host QEMU version:
|
||||
|
||||
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.
|
||||
|
||||
**Reports that do no include this information will be ignored and closed**
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
module/*.ko
|
||||
module/*.o
|
||||
module/*.mod.c
|
||||
module/.*
|
||||
module/Module.symvers
|
||||
module/modules.order
|
||||
*.a
|
||||
*.o
|
||||
*.exe
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "vendor/kvm-guest-drivers-windows"]
|
||||
path = vendor/kvm-guest-drivers-windows
|
||||
url = https://github.com/virtio-win/kvm-guest-drivers-windows.git
|
||||
|
||||
@@ -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)
|
||||
|
||||
52
README.md
Normal file
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Looking Glass
|
||||
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
|
||||
|
||||
* Project Website: https://looking-glass.hostfission.com
|
||||
|
||||
## 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.
|
||||
|
||||
* [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
|
||||
|
||||
Please also be sure to see the following files for more information
|
||||
|
||||
* [client/README.md](client/README.md)
|
||||
* [c-host/README.md](c-host/README.md)
|
||||
* [module/README.md](module/README.md)
|
||||
|
||||
## Obtaining and using Looking Glass
|
||||
|
||||
Please see https://looking-glass.hostfission.com/quickstart
|
||||
|
||||
## Latest Version
|
||||
|
||||
If you would like to use the latest bleeding edge version of Looking Glass please be aware there will be no support at this time.
|
||||
Latest bleeding edge builds of the Windows host application can be obtained from:
|
||||
|
||||
https://looking-glass.hostfission.com/downloads
|
||||
|
||||
# Help and support
|
||||
|
||||
## Web
|
||||
|
||||
https://forum.level1techs.com/t/looking-glass-triage/130952
|
||||
|
||||
## Discord
|
||||
|
||||
https://discord.gg/4ahCn4c
|
||||
|
||||
## IRC
|
||||
|
||||
Join us in the #LookingGlass channel on the FreeNode network
|
||||
|
||||
## Trello
|
||||
|
||||
* https://trello.com/b/tI1Xbwsg/looking-glass
|
||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
3
arbiter/.gitignore
vendored
Normal file
3
arbiter/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
bin/
|
||||
build/
|
||||
*.swp
|
||||
62
arbiter/CMakeLists.txt
Normal file
62
arbiter/CMakeLists.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-arbiter 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()
|
||||
|
||||
add_compile_options(
|
||||
"-Wall"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
"-ffast-math"
|
||||
"-fdata-sections"
|
||||
"-ffunction-sections"
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
|
||||
set(EXE_FLAGS "-Wl,--gc-sections")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
src/main.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory("${PROJECT_TOP}/porthole" "${CMAKE_BINARY_DIR}/porthole")
|
||||
|
||||
add_executable(looking-glass-arbiter ${SOURCES})
|
||||
target_link_libraries(looking-glass-arbiter
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
porthole
|
||||
)
|
||||
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-arbiter DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
55
arbiter/src/main.c
Normal file
55
arbiter/src/main.c
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "porthole/client.h"
|
||||
|
||||
static struct Option options[] =
|
||||
{
|
||||
// app options
|
||||
{
|
||||
.module = "host",
|
||||
.name = "socket",
|
||||
.description = "The porthole host socket",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = "/var/tmp/porthole",
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
static void map_event(uint32_t type, PortholeMap * map)
|
||||
{
|
||||
DEBUG_INFO("map_event: %u, %u, %u", type, map->id, map->size);
|
||||
}
|
||||
|
||||
static void unmap_event(uint32_t id)
|
||||
{
|
||||
DEBUG_INFO("unmap_event: %u", id);
|
||||
}
|
||||
|
||||
static void discon_event()
|
||||
{
|
||||
DEBUG_INFO("discon_event");
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
option_register(options);
|
||||
if (!option_parse(argc, argv))
|
||||
return -1;
|
||||
|
||||
if (!option_validate())
|
||||
return -1;
|
||||
|
||||
PortholeClient phc;
|
||||
if (!porthole_client_open(
|
||||
&phc,
|
||||
option_get_string("host", "socket"),
|
||||
map_event,
|
||||
unmap_event,
|
||||
discon_event))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
porthole_client_close(&phc);
|
||||
return 0;
|
||||
}
|
||||
2
c-host/.gitignore
vendored
Normal file
2
c-host/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
*.swp
|
||||
71
c-host/CMakeLists.txt
Normal file
71
c-host/CMakeLists.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-host C)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
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_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
||||
|
||||
add_compile_options(
|
||||
"-Wall"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
"-ffast-math"
|
||||
"-fdata-sections"
|
||||
"-ffunction-sections"
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PROJECT_TOP}/vendor/ivshmem
|
||||
${PKGCONFIG_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
#link_libraries(
|
||||
#)
|
||||
|
||||
set(SOURCES
|
||||
src/app.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory(platform)
|
||||
|
||||
if(WIN32)
|
||||
add_executable(looking-glass-host WIN32 ${SOURCES})
|
||||
else()
|
||||
add_executable(looking-glass-host ${SOURCES})
|
||||
endif()
|
||||
target_link_libraries(looking-glass-host
|
||||
lg_common
|
||||
platform
|
||||
)
|
||||
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections")
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
102
c-host/README.md
Normal file
102
c-host/README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# General Questions
|
||||
|
||||
## What is this?
|
||||
|
||||
This is a rewrite of the host application in pure C using the MinGW toolchain.
|
||||
|
||||
## Why make this?
|
||||
|
||||
Several reasons:
|
||||
|
||||
1. The client is written in C and I would like to unify the project's language
|
||||
2. The host is currently hard to build using MinGW and is very Windows specific
|
||||
3. The host is a jumbled mess of code from all the experimentation going on
|
||||
4. I would eventually like to be able to port this to run on Linux guests
|
||||
|
||||
## When will it be ready?
|
||||
|
||||
Soon :)
|
||||
|
||||
## Will it replace the C++ host?
|
||||
|
||||
Yes, but only when it is feature complete.
|
||||
|
||||
## Why doesn't this use CMake?
|
||||
|
||||
It does now...
|
||||
~~Because win-builds doesn't distribute it, so to make it easy for everyone to compile we do not require it.~~
|
||||
|
||||
## How do I build it?
|
||||
|
||||
#### For Windows on Windows
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "MSYS Makefiles" ..
|
||||
make
|
||||
```
|
||||
|
||||
#### For Linux on Linux
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
#### For Windows cross compiling on Linux
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
## Where is the log?
|
||||
|
||||
It is in your user's temp directory:
|
||||
|
||||
%TEMP%\looking-glass-host.txt
|
||||
|
||||
For example:
|
||||
|
||||
C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
|
||||
|
||||
You can also open it by simply right clicking the tray icon and selecting "Open Log File"
|
||||
|
||||
## Why does this version require Administrator privileges
|
||||
|
||||
This is intentional for several reasons.
|
||||
|
||||
1. NvFBC requires a system wide hook to correctly obtain the cursor position as NVIDIA decided to not provide this as part of the cursor updates.
|
||||
2. NvFBC requires administrator level access to enable the interface in the first place. (WIP)
|
||||
3. DXGI performance can be improved if we have this. (WIP)
|
||||
|
||||
## NvFBC (NVIDIA Frame Buffer Capture)
|
||||
|
||||
### Why isn't there a build with NvFBC support available.
|
||||
|
||||
~~Because NVIDIA have decided to put restrictions on the NvFBC API that simply make it incompatible with the GPL/2 licence. Providing a pre-built binary with NvFBC support would violate the EULA I have agreed to in order to access the NVidia Capture SDK.~~
|
||||
|
||||
Either I miss-read the License Agreement or it has been updated, it is now viable to produce a "derived work" from the capture SDK.
|
||||
|
||||
> 1.1 License Grant. Subject to the terms of this Agreement, NVIDIA hereby grants you a nonexclusive, non-transferable, worldwide,
|
||||
revocable, limited, royalty-free, fully paid-up license during the term of this Agreement to:
|
||||
> (i) install, use and reproduce the Licensed Software delivered by NVIDIA plus make modifications and create derivative
|
||||
works of the source code and header files delivered by NVIDIA, provided that the software is executed only in hardware products as
|
||||
specified by NVIDIA in the accompanying documentation (such as release notes) as supported, to develop, test and service your
|
||||
products (each, a “Customer Product”) that are interoperable with supported hardware products. If the NVIDIA documentation is
|
||||
silent, the supported hardware consists of certain NVIDIA GPUs; and
|
||||
|
||||
To be safe we are still not including the NVIDIA headers in the repository, but I am now providing pre-built binaries with NvFBC support included.
|
||||
|
||||
See: https://looking-glass.hostfission.com/downloads
|
||||
|
||||
### Why can't I compile NvFBC support into the host
|
||||
|
||||
You must download and install the NVidia Capture SDK. Please note that by doing so you will be agreeing to NVIDIA's SDK License agreement.
|
||||
|
||||
_-Geoff_
|
||||
1
c-host/cmake/NVFBC.cmake
Normal file
1
c-host/cmake/NVFBC.cmake
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
15
c-host/cmake/PostCapture.cmake
Normal file
15
c-host/cmake/PostCapture.cmake
Normal file
@@ -0,0 +1,15 @@
|
||||
list(REMOVE_AT CAPTURE 0)
|
||||
list(REMOVE_AT CAPTURE_LINK 0)
|
||||
|
||||
list(LENGTH CAPTURE CAPTURE_COUNT)
|
||||
file(APPEND ${CAPTURE_H} "#define LG_CAPTURE_COUNT ${CAPTURE_COUNT}\n")
|
||||
|
||||
foreach(renderer ${CAPTURE})
|
||||
file(APPEND ${CAPTURE_C} "extern CaptureInterface Capture_${renderer};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${CAPTURE_C} "\nconst CaptureInterface * CaptureInterfaces[] =\n{\n")
|
||||
foreach(renderer ${CAPTURE})
|
||||
file(APPEND ${CAPTURE_C} " &Capture_${renderer},\n")
|
||||
endforeach()
|
||||
file(APPEND ${CAPTURE_C} " NULL\n};")
|
||||
16
c-host/cmake/PreCapture.cmake
Normal file
16
c-host/cmake/PreCapture.cmake
Normal file
@@ -0,0 +1,16 @@
|
||||
set(CAPTURE_H "${CMAKE_BINARY_DIR}/include/dynamic/capture.h")
|
||||
set(CAPTURE_C "${CMAKE_BINARY_DIR}/src/capture.c")
|
||||
|
||||
file(WRITE ${CAPTURE_H} "#include \"interface/capture.h\"\n\n")
|
||||
file(APPEND ${CAPTURE_H} "extern CaptureInterface * CaptureInterfaces[];\n\n")
|
||||
|
||||
file(WRITE ${CAPTURE_C} "#include \"interface/capture.h\"\n\n")
|
||||
file(APPEND ${CAPTURE_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(CAPTURE "_")
|
||||
set(CAPTURE_LINK "_")
|
||||
function(add_capture name)
|
||||
set(CAPTURE "${CAPTURE};${name}" PARENT_SCOPE)
|
||||
set(CAPTURE_LINK "${CAPTURE_LINK};capture_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
91
c-host/include/interface/capture.h
Normal file
91
c-host/include/interface/capture.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 <stdint.h>
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
typedef enum CaptureResult
|
||||
{
|
||||
CAPTURE_RESULT_OK ,
|
||||
CAPTURE_RESULT_REINIT ,
|
||||
CAPTURE_RESULT_TIMEOUT,
|
||||
CAPTURE_RESULT_ERROR
|
||||
}
|
||||
CaptureResult;
|
||||
|
||||
typedef enum CaptureFormat
|
||||
{
|
||||
// frame formats
|
||||
CAPTURE_FMT_BGRA ,
|
||||
CAPTURE_FMT_RGBA ,
|
||||
CAPTURE_FMT_RGBA10,
|
||||
CAPTURE_FMT_YUV420,
|
||||
|
||||
// pointer formats
|
||||
CAPTURE_FMT_COLOR ,
|
||||
CAPTURE_FMT_MONO ,
|
||||
CAPTURE_FMT_MASKED,
|
||||
|
||||
CAPTURE_FMT_MAX
|
||||
}
|
||||
CaptureFormat;
|
||||
|
||||
typedef struct CaptureFrame
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int pitch;
|
||||
unsigned int stride;
|
||||
CaptureFormat format;
|
||||
}
|
||||
CaptureFrame;
|
||||
|
||||
typedef struct CapturePointer
|
||||
{
|
||||
int x, y;
|
||||
bool visible;
|
||||
|
||||
bool shapeUpdate;
|
||||
CaptureFormat format;
|
||||
unsigned int width, height;
|
||||
unsigned int pitch;
|
||||
}
|
||||
CapturePointer;
|
||||
|
||||
typedef struct CaptureInterface
|
||||
{
|
||||
const char * (*getName )();
|
||||
void (*initOptions )();
|
||||
|
||||
bool (*create )();
|
||||
bool (*init )(void * pointerShape, const unsigned int pointerSize);
|
||||
void (*stop )();
|
||||
bool (*deinit )();
|
||||
void (*free )();
|
||||
unsigned int (*getMaxFrameSize)();
|
||||
|
||||
CaptureResult (*capture )();
|
||||
CaptureResult (*waitFrame )(CaptureFrame * frame );
|
||||
CaptureResult (*getFrame )(FrameBuffer frame );
|
||||
CaptureResult (*getPointer)(CapturePointer * pointer);
|
||||
}
|
||||
CaptureInterface;
|
||||
54
c-host/include/interface/platform.h
Normal file
54
c-host/include/interface/platform.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
int app_main(int argc, char * argv[]);
|
||||
bool app_init();
|
||||
void app_quit();
|
||||
|
||||
// these must be implemented for each OS
|
||||
const char * os_getExecutable();
|
||||
|
||||
unsigned int os_shmemSize();
|
||||
bool os_shmemMmap(void **ptr);
|
||||
void os_shmemUnmap();
|
||||
|
||||
// os specific thread functions
|
||||
|
||||
typedef struct osThreadHandle osThreadHandle;
|
||||
typedef int (*osThreadFunction)(void * opaque);
|
||||
|
||||
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle);
|
||||
bool os_joinThread (osThreadHandle * handle, int * resultCode);
|
||||
|
||||
// os specific event functions
|
||||
|
||||
#define TIMEOUT_INFINITE ((unsigned int)~0)
|
||||
|
||||
typedef struct osEventHandle osEventHandle;
|
||||
|
||||
osEventHandle * os_createEvent(bool autoReset);
|
||||
void os_freeEvent (osEventHandle * handle);
|
||||
bool os_waitEvent (osEventHandle * handle, unsigned int timeout);
|
||||
bool os_waitEvents (osEventHandle * handles[], int count, bool waitAll, unsigned int timeout);
|
||||
bool os_signalEvent(osEventHandle * handle);
|
||||
bool os_resetEvent (osEventHandle * handle);
|
||||
13
c-host/platform/CMakeLists.txt
Normal file
13
c-host/platform/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(platform LANGUAGES C)
|
||||
|
||||
if (UNIX)
|
||||
set(PLATFORM "Linux")
|
||||
elseif(WIN32)
|
||||
set(PLATFORM "Windows")
|
||||
endif()
|
||||
|
||||
add_subdirectory(${PLATFORM})
|
||||
|
||||
add_library(platform INTERFACE)
|
||||
target_link_libraries(platform INTERFACE platform_${PLATFORM})
|
||||
22
c-host/platform/Linux/CMakeLists.txt
Normal file
22
c-host/platform/Linux/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(platform_Linux LANGUAGES C)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
add_library(platform_Linux STATIC
|
||||
src/platform.c
|
||||
)
|
||||
|
||||
add_subdirectory("capture")
|
||||
|
||||
target_link_libraries(platform_Linux
|
||||
capture
|
||||
pthread
|
||||
)
|
||||
|
||||
target_include_directories(platform_Linux
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
11
c-host/platform/Linux/capture/CMakeLists.txt
Normal file
11
c-host/platform/Linux/capture/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(capture LANGUAGES C)
|
||||
|
||||
include("PreCapture")
|
||||
|
||||
add_capture("XCB")
|
||||
|
||||
include("PostCapture")
|
||||
|
||||
add_library(capture STATIC ${CAPTURE_C})
|
||||
target_link_libraries(capture ${CAPTURE_LINK})
|
||||
18
c-host/platform/Linux/capture/XCB/CMakeLists.txt
Normal file
18
c-host/platform/Linux/capture/XCB/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(capture_XCB LANGUAGES C)
|
||||
|
||||
add_library(capture_XCB STATIC
|
||||
src/xcb.c
|
||||
)
|
||||
|
||||
target_link_libraries(capture_XCB
|
||||
lg_common
|
||||
xcb
|
||||
xcb-shm
|
||||
Xfixes
|
||||
)
|
||||
|
||||
target_include_directories(capture_XCB
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
242
c-host/platform/Linux/capture/XCB/src/xcb.c
Normal file
242
c-host/platform/Linux/capture/XCB/src/xcb.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
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/capture.h"
|
||||
#include "interface/platform.h"
|
||||
#include "common/debug.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <xcb/shm.h>
|
||||
#include <xcb/xfixes.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
struct xcb
|
||||
{
|
||||
bool initialized;
|
||||
xcb_connection_t * xcb;
|
||||
xcb_screen_t * xcbScreen;
|
||||
uint32_t seg;
|
||||
int shmID;
|
||||
void * data;
|
||||
osEventHandle * frameEvent;
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
|
||||
bool hasFrame;
|
||||
xcb_shm_get_image_cookie_t imgC;
|
||||
xcb_xfixes_get_cursor_image_cookie_t curC;
|
||||
};
|
||||
|
||||
struct xcb * this = NULL;
|
||||
|
||||
// forwards
|
||||
|
||||
static bool xcb_deinit();
|
||||
static unsigned int xcb_getMaxFrameSize();
|
||||
|
||||
// implementation
|
||||
|
||||
static const char * xcb_getName()
|
||||
{
|
||||
return "XCB";
|
||||
}
|
||||
|
||||
static bool xcb_create()
|
||||
{
|
||||
assert(!this);
|
||||
this = (struct xcb *)calloc(sizeof(struct xcb), 1);
|
||||
this->shmID = -1;
|
||||
this->data = (void *)-1;
|
||||
this->frameEvent = os_createEvent(true);
|
||||
|
||||
if (!this->frameEvent)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame event");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xcb_init()
|
||||
{
|
||||
assert(this);
|
||||
assert(!this->initialized);
|
||||
|
||||
os_resetEvent(this->frameEvent);
|
||||
|
||||
this->xcb = xcb_connect(NULL, NULL);
|
||||
if (!this->xcb || xcb_connection_has_error(this->xcb))
|
||||
{
|
||||
DEBUG_ERROR("Unable to open the X display");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!xcb_get_extension_data(this->xcb, &xcb_shm_id)->present)
|
||||
{
|
||||
DEBUG_ERROR("Missing the SHM extension");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
xcb_screen_iterator_t iter;
|
||||
iter = xcb_setup_roots_iterator(xcb_get_setup(this->xcb));
|
||||
this->xcbScreen = iter.data;
|
||||
this->width = iter.data->width_in_pixels;
|
||||
this->height = iter.data->height_in_pixels;
|
||||
DEBUG_INFO("Frame Size : %u x %u", this->width, this->height);
|
||||
|
||||
this->seg = xcb_generate_id(this->xcb);
|
||||
this->shmID = shmget(IPC_PRIVATE, xcb_getMaxFrameSize(), IPC_CREAT | 0777);
|
||||
if (this->shmID == -1)
|
||||
{
|
||||
DEBUG_ERROR("shmget failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
xcb_shm_attach(this->xcb, this->seg ,this->shmID, false);
|
||||
this->data = shmat(this->shmID, NULL, 0);
|
||||
if ((uintptr_t)this->data == -1)
|
||||
{
|
||||
DEBUG_ERROR("shmat failed");
|
||||
goto fail;
|
||||
}
|
||||
DEBUG_INFO("Frame Data : 0x%" PRIXPTR, (uintptr_t)this->data);
|
||||
|
||||
this->initialized = true;
|
||||
return true;
|
||||
fail:
|
||||
xcb_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool xcb_deinit()
|
||||
{
|
||||
assert(this);
|
||||
|
||||
if ((uintptr_t)this->data != -1)
|
||||
{
|
||||
shmdt(this->data);
|
||||
this->data = (void *)-1;
|
||||
}
|
||||
|
||||
if (this->shmID != -1)
|
||||
{
|
||||
shmctl(this->shmID, IPC_RMID, NULL);
|
||||
this->shmID = -1;
|
||||
}
|
||||
|
||||
if (this->xcb)
|
||||
{
|
||||
xcb_disconnect(this->xcb);
|
||||
this->xcb = NULL;
|
||||
}
|
||||
|
||||
this->initialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void xcb_free()
|
||||
{
|
||||
os_freeEvent(this->frameEvent);
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static unsigned int xcb_getMaxFrameSize()
|
||||
{
|
||||
return this->width * this->height * 4;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_capture()
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
if (!this->hasFrame)
|
||||
{
|
||||
this->imgC = xcb_shm_get_image_unchecked(
|
||||
this->xcb,
|
||||
this->xcbScreen->root,
|
||||
0, 0,
|
||||
this->width,
|
||||
this->height,
|
||||
~0,
|
||||
XCB_IMAGE_FORMAT_Z_PIXMAP,
|
||||
this->seg,
|
||||
0);
|
||||
|
||||
this->hasFrame = true;
|
||||
os_signalEvent(this->frameEvent);
|
||||
}
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_getFrame(CaptureFrame * frame)
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
assert(frame);
|
||||
assert(frame->data);
|
||||
|
||||
os_waitEvent(this->frameEvent, TIMEOUT_INFINITE);
|
||||
|
||||
xcb_shm_get_image_reply_t * img;
|
||||
img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL);
|
||||
if (!img)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get image reply");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
frame->width = this->width;
|
||||
frame->height = this->height;
|
||||
frame->pitch = this->width * 4;
|
||||
frame->stride = this->width;
|
||||
frame->format = CAPTURE_FMT_BGRA;
|
||||
memcpy(frame->data, this->data, this->width * this->height * 4);
|
||||
free(img);
|
||||
|
||||
this->hasFrame = false;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_getPointer(CapturePointer * pointer)
|
||||
{
|
||||
memset(pointer, 0, sizeof(CapturePointer));
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
struct CaptureInterface Capture_XCB =
|
||||
{
|
||||
.getName = xcb_getName,
|
||||
.create = xcb_create,
|
||||
.init = xcb_init,
|
||||
.deinit = xcb_deinit,
|
||||
.free = xcb_free,
|
||||
.getMaxFrameSize = xcb_getMaxFrameSize,
|
||||
.capture = xcb_capture,
|
||||
.getFrame = xcb_getFrame,
|
||||
.getPointer = xcb_getPointer
|
||||
};
|
||||
453
c-host/platform/Linux/src/platform.c
Normal file
453
c-host/platform/Linux/src/platform.c
Normal file
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
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/platform.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct app
|
||||
{
|
||||
const char * executable;
|
||||
unsigned int shmSize;
|
||||
int shmFD;
|
||||
void * shmMap;
|
||||
};
|
||||
|
||||
static struct app app;
|
||||
|
||||
struct osThreadHandle
|
||||
{
|
||||
const char * name;
|
||||
osThreadFunction function;
|
||||
void * opaque;
|
||||
pthread_t handle;
|
||||
int resultCode;
|
||||
};
|
||||
|
||||
void sigHandler(int signo)
|
||||
{
|
||||
DEBUG_INFO("SIGINT");
|
||||
app_quit();
|
||||
}
|
||||
|
||||
static int uioOpenFile(const char * shmDevice, const char * file)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "/sys/class/uio/%s/%s", shmDevice, file);
|
||||
char * path = malloc(len + 1);
|
||||
sprintf(path, "/sys/class/uio/%s/%s", shmDevice, file);
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
{
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(path);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static char * uioGetName(const char * shmDevice)
|
||||
{
|
||||
int fd = uioOpenFile(shmDevice, "name");
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
char * name = malloc(32);
|
||||
int len = read(fd, name, 31);
|
||||
if (len <= 0)
|
||||
{
|
||||
free(name);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
name[len] = '\0';
|
||||
close(fd);
|
||||
|
||||
while(len > 0 && name[len-1] == '\n')
|
||||
{
|
||||
--len;
|
||||
name[len] = '\0';
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static int shmOpenDev(const char * shmDevice)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "/dev/%s", shmDevice);
|
||||
char * path = malloc(len + 1);
|
||||
sprintf(path, "/dev/%s", shmDevice);
|
||||
|
||||
int fd = open(path, O_RDWR, (mode_t)0600);
|
||||
if (fd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to open: %s", path);
|
||||
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
|
||||
free(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(path);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool shmDeviceValidator(struct Option * opt, const char ** error)
|
||||
{
|
||||
char * name = uioGetName(opt->value.x_string);
|
||||
if (!name)
|
||||
{
|
||||
*error = "Failed to get the uio device name";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcmp(name, "KVMFR") != 0)
|
||||
{
|
||||
free(name);
|
||||
*error = "Device is not a KVMFR device";
|
||||
return false;
|
||||
}
|
||||
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
static StringList shmDeviceGetValues(struct Option * option)
|
||||
{
|
||||
StringList sl = stringlist_new(true);
|
||||
|
||||
DIR * d = opendir("/sys/class/uio");
|
||||
if (!d)
|
||||
return sl;
|
||||
|
||||
struct dirent * dir;
|
||||
while((dir = readdir(d)) != NULL)
|
||||
{
|
||||
if (dir->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
char * name = uioGetName(dir->d_name);
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
if (strcmp(name, "KVMFR") == 0)
|
||||
stringlist_push(sl, strdup(dir->d_name));
|
||||
|
||||
free(name);
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
return sl;
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
app.executable = argv[0];
|
||||
|
||||
struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "os",
|
||||
.name = "shmDevice",
|
||||
.description = "The IVSHMEM device to use",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = "uio0",
|
||||
.validator = shmDeviceValidator,
|
||||
.getValues = shmDeviceGetValues
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
|
||||
int result = app_main(argc, argv);
|
||||
os_shmemUnmap();
|
||||
close(app.shmFD);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool app_init()
|
||||
{
|
||||
const char * shmDevice = option_get_string("os", "shmDevice");
|
||||
|
||||
// get the device size
|
||||
int fd = uioOpenFile(shmDevice, "maps/map0/size");
|
||||
if (fd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to open %s/size", shmDevice);
|
||||
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
|
||||
return false;
|
||||
}
|
||||
|
||||
char size[32];
|
||||
int len = read(fd, size, sizeof(size) - 1);
|
||||
if (len <= 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read the device size");
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
size[len] = '\0';
|
||||
close(fd);
|
||||
|
||||
app.shmSize = strtoul(size, NULL, 16);
|
||||
|
||||
// open the device
|
||||
app.shmFD = shmOpenDev(shmDevice);
|
||||
app.shmMap = MAP_FAILED;
|
||||
if (app.shmFD < 0)
|
||||
return false;
|
||||
|
||||
DEBUG_INFO("KVMFR Device : %s", shmDevice);
|
||||
|
||||
signal(SIGINT, sigHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char * os_getExecutable()
|
||||
{
|
||||
return app.executable;
|
||||
}
|
||||
|
||||
unsigned int os_shmemSize()
|
||||
{
|
||||
return app.shmSize;
|
||||
}
|
||||
|
||||
bool os_shmemMmap(void **ptr)
|
||||
{
|
||||
if (app.shmMap == MAP_FAILED)
|
||||
{
|
||||
app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0);
|
||||
if (app.shmMap == MAP_FAILED)
|
||||
{
|
||||
const char * shmDevice = option_get_string("os", "shmDevice");
|
||||
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*ptr = app.shmMap;
|
||||
return true;
|
||||
}
|
||||
|
||||
void os_shmemUnmap()
|
||||
{
|
||||
if (app.shmMap == MAP_FAILED)
|
||||
return;
|
||||
|
||||
munmap(app.shmMap, app.shmSize);
|
||||
app.shmMap = MAP_FAILED;
|
||||
}
|
||||
|
||||
static void * threadWrapper(void * opaque)
|
||||
{
|
||||
osThreadHandle * handle = (osThreadHandle *)opaque;
|
||||
handle->resultCode = handle->function(handle->opaque);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
|
||||
{
|
||||
*handle = (osThreadHandle*)malloc(sizeof(osThreadHandle));
|
||||
(*handle)->name = name;
|
||||
(*handle)->function = function;
|
||||
(*handle)->opaque = opaque;
|
||||
|
||||
if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0)
|
||||
{
|
||||
DEBUG_ERROR("pthread_create failed for thread: %s", name);
|
||||
free(*handle);
|
||||
*handle = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool os_joinThread(osThreadHandle * handle, int * resultCode)
|
||||
{
|
||||
if (pthread_join(handle->handle, NULL) != 0)
|
||||
{
|
||||
DEBUG_ERROR("pthread_join failed for thread: %s", handle->name);
|
||||
free(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resultCode)
|
||||
*resultCode = handle->resultCode;
|
||||
|
||||
free(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct osEventHandle
|
||||
{
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool flag;
|
||||
bool autoReset;
|
||||
};
|
||||
|
||||
osEventHandle * os_createEvent(bool autoReset)
|
||||
{
|
||||
osEventHandle * handle = (osEventHandle *)calloc(sizeof(osEventHandle), 1);
|
||||
if (!handle)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the mutex");
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pthread_cond_init(&handle->cond, NULL) != 0)
|
||||
{
|
||||
pthread_mutex_destroy(&handle->mutex);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->autoReset = autoReset;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void os_freeEvent(osEventHandle * handle)
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
pthread_cond_destroy (&handle->cond );
|
||||
pthread_mutex_destroy(&handle->mutex);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
if (pthread_mutex_lock(&handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to lock the mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
while(!handle->flag)
|
||||
{
|
||||
if (timeout == TIMEOUT_INFINITE)
|
||||
{
|
||||
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Wait to wait on the condition");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = timeout / 1000;
|
||||
ts.tv_nsec = (timeout % 1000) * 1000000;
|
||||
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, &ts))
|
||||
{
|
||||
case ETIMEDOUT:
|
||||
return false;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Timed wait failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handle->autoReset)
|
||||
handle->flag = false;
|
||||
|
||||
if (pthread_mutex_unlock(&handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unlock the mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool os_signalEvent(osEventHandle * handle)
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
if (pthread_mutex_lock(&handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to lock the mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->flag = true;
|
||||
|
||||
if (pthread_mutex_unlock(&handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unlock the mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pthread_cond_signal(&handle->cond) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to signal the condition");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool os_resetEvent(osEventHandle * handle)
|
||||
{
|
||||
assert(handle);
|
||||
|
||||
if (pthread_mutex_lock(&handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to lock the mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
handle->flag = false;
|
||||
|
||||
if (pthread_mutex_unlock(&handle->mutex) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unlock the mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
33
c-host/platform/Windows/CMakeLists.txt
Normal file
33
c-host/platform/Windows/CMakeLists.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(platform_Windows LANGUAGES C)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
add_library(platform_Windows STATIC
|
||||
src/platform.c
|
||||
src/windebug.c
|
||||
src/mousehook.c
|
||||
)
|
||||
|
||||
add_subdirectory("capture")
|
||||
|
||||
FIND_PROGRAM(WINDRES_EXECUTABLE NAMES "x86_64-w64-mingw32-windres" "windres.exe" DOC "windres executable")
|
||||
ADD_CUSTOM_COMMAND(TARGET platform_Windows POST_BUILD
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
COMMAND ${WINDRES_EXECUTABLE} -i resource.rc -o "${PROJECT_BINARY_DIR}/resource.o"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
target_link_libraries(platform_Windows
|
||||
"${PROJECT_BINARY_DIR}/resource.o"
|
||||
lg_common
|
||||
capture
|
||||
setupapi
|
||||
)
|
||||
|
||||
target_include_directories(platform_Windows
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
12
c-host/platform/Windows/app.manifest
Normal file
12
c-host/platform/Windows/app.manifest
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="hello" type="win32"/>
|
||||
<description>Hello World</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
31
c-host/platform/Windows/capture/CMakeLists.txt
Normal file
31
c-host/platform/Windows/capture/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(capture LANGUAGES C)
|
||||
|
||||
include(PreCapture)
|
||||
|
||||
option(USE_NVFBC "Enable NVFBC Support" OFF)
|
||||
option(USE_DXGI "Enable DXGI Support" ON)
|
||||
|
||||
if(NOT DEFINED NVFBC_SDK)
|
||||
set(NVFBC_SDK "C:/Program Files (x86)/NVIDIA Corporation/NVIDIA Capture SDK")
|
||||
endif()
|
||||
|
||||
file(TO_CMAKE_PATH "${NVFBC_SDK}" nvfbc_sdk)
|
||||
|
||||
if(NOT EXISTS "${nvfbc_sdk}/inc" OR NOT IS_DIRECTORY "${nvfbc_sdk}/inc")
|
||||
message("Disabling NVFBC support, can't find the SDK headers")
|
||||
set(USE_NVFBC OFF)
|
||||
endif()
|
||||
|
||||
if(USE_NVFBC)
|
||||
add_capture("NVFBC")
|
||||
endif()
|
||||
|
||||
if(USE_DXGI)
|
||||
add_capture("DXGI")
|
||||
endif()
|
||||
|
||||
include("PostCapture")
|
||||
|
||||
add_library(capture STATIC ${CAPTURE_C})
|
||||
target_link_libraries(capture ${CAPTURE_LINK})
|
||||
26
c-host/platform/Windows/capture/DXGI/CMakeLists.txt
Normal file
26
c-host/platform/Windows/capture/DXGI/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(capture_DXGI LANGUAGES C)
|
||||
|
||||
add_library(capture_DXGI STATIC
|
||||
src/dxgi.c
|
||||
)
|
||||
|
||||
add_definitions("-DCOBJMACROS -DINITGUID")
|
||||
|
||||
FIND_PROGRAM(DLLTOOL_EXECUTABLE NAMES "x86_64-w64-mingw32-dlltool" "dlltool" "dlltool.exe" DOC "dlltool executable")
|
||||
ADD_CUSTOM_COMMAND(TARGET capture_DXGI POST_BUILD
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/dll"
|
||||
COMMAND ${DLLTOOL_EXECUTABLE} --def libd3d11.def --output-lib "${PROJECT_BINARY_DIR}/libd3d11.dll"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
target_link_libraries(capture_DXGI
|
||||
lg_common
|
||||
${PROJECT_BINARY_DIR}/libd3d11.dll
|
||||
dxgi
|
||||
)
|
||||
|
||||
target_include_directories(capture_DXGI
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
44
c-host/platform/Windows/capture/DXGI/dll/libd3d11.def
Normal file
44
c-host/platform/Windows/capture/DXGI/dll/libd3d11.def
Normal file
@@ -0,0 +1,44 @@
|
||||
LIBRARY "d3d11.dll"
|
||||
EXPORTS
|
||||
D3DKMTCloseAdapter
|
||||
D3DKMTDestroyAllocation
|
||||
D3DKMTDestroyContext
|
||||
D3DKMTDestroyDevice
|
||||
D3DKMTDestroySynchronizationObject
|
||||
D3DKMTQueryAdapterInfo
|
||||
D3DKMTSetDisplayPrivateDriverFormat
|
||||
D3DKMTSignalSynchronizationObject
|
||||
D3DKMTUnlock
|
||||
D3DKMTWaitForSynchronizationObject
|
||||
OpenAdapter10
|
||||
OpenAdapter10_2
|
||||
D3D11CoreCreateDevice
|
||||
D3D11CoreCreateLayeredDevice
|
||||
D3D11CoreGetLayeredDeviceSize
|
||||
D3D11CoreRegisterLayers
|
||||
D3D11CreateDevice
|
||||
D3D11CreateDeviceAndSwapChain
|
||||
D3DKMTCreateAllocation
|
||||
D3DKMTCreateContext
|
||||
D3DKMTCreateDevice
|
||||
D3DKMTCreateSynchronizationObject
|
||||
D3DKMTEscape
|
||||
D3DKMTGetContextSchedulingPriority
|
||||
D3DKMTGetDeviceState
|
||||
D3DKMTGetDisplayModeList
|
||||
D3DKMTGetMultisampleMethodList
|
||||
D3DKMTGetRuntimeData
|
||||
D3DKMTGetSharedPrimaryHandle
|
||||
D3DKMTLock
|
||||
D3DKMTOpenAdapterFromHdc
|
||||
D3DKMTOpenResource
|
||||
D3DKMTPresent
|
||||
D3DKMTQueryAllocationResidency
|
||||
D3DKMTQueryResourceInfo
|
||||
D3DKMTRender
|
||||
D3DKMTSetAllocationPriority
|
||||
D3DKMTSetContextSchedulingPriority
|
||||
D3DKMTSetDisplayMode
|
||||
D3DKMTSetGammaRamp
|
||||
D3DKMTSetVidPnSourceOwner
|
||||
D3DKMTWaitForVerticalBlankEvent
|
||||
881
c-host/platform/Windows/capture/DXGI/src/dxgi.c
Normal file
881
c-host/platform/Windows/capture/DXGI/src/dxgi.c
Normal file
@@ -0,0 +1,881 @@
|
||||
/*
|
||||
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/capture.h"
|
||||
#include "interface/platform.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "windows/debug.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <dxgi.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcommon.h>
|
||||
|
||||
#include "dxgi_extra.h"
|
||||
|
||||
enum TextureState
|
||||
{
|
||||
TEXTURE_STATE_UNUSED,
|
||||
TEXTURE_STATE_PENDING_MAP,
|
||||
TEXTURE_STATE_MAPPED
|
||||
};
|
||||
|
||||
typedef struct Texture
|
||||
{
|
||||
enum TextureState state;
|
||||
ID3D11Texture2D * tex;
|
||||
D3D11_MAPPED_SUBRESOURCE map;
|
||||
osEventHandle * mapped;
|
||||
osEventHandle * free;
|
||||
}
|
||||
Texture;
|
||||
|
||||
typedef struct Pointer
|
||||
{
|
||||
unsigned int version;
|
||||
|
||||
unsigned int x, y;
|
||||
unsigned int w, h;
|
||||
bool visible;
|
||||
unsigned int pitch;
|
||||
CaptureFormat format;
|
||||
}
|
||||
Pointer;
|
||||
|
||||
// locals
|
||||
struct iface
|
||||
{
|
||||
bool initialized;
|
||||
bool stop;
|
||||
IDXGIFactory1 * factory;
|
||||
IDXGIAdapter1 * adapter;
|
||||
IDXGIOutput * output;
|
||||
ID3D11Device * device;
|
||||
ID3D11DeviceContext * deviceContext;
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
IDXGIOutputDuplication * dup;
|
||||
int maxTextures;
|
||||
Texture * texture;
|
||||
int texRIndex;
|
||||
int texWIndex;
|
||||
bool needsRelease;
|
||||
osEventHandle * pointerEvent;
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int pitch;
|
||||
unsigned int stride;
|
||||
CaptureFormat format;
|
||||
|
||||
// pointer state
|
||||
Pointer lastPointer;
|
||||
Pointer pointer;
|
||||
|
||||
// pointer shape
|
||||
void * pointerShape;
|
||||
unsigned int pointerSize;
|
||||
unsigned int pointerUsed;
|
||||
};
|
||||
|
||||
static bool dpiDone = false;
|
||||
static struct iface * this = NULL;
|
||||
|
||||
// forwards
|
||||
|
||||
static bool dxgi_deinit();
|
||||
static CaptureResult dxgi_releaseFrame();
|
||||
|
||||
// implementation
|
||||
|
||||
static const char * dxgi_getName()
|
||||
{
|
||||
return "DXGI";
|
||||
}
|
||||
|
||||
static void dxgi_initOptions()
|
||||
{
|
||||
struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "dxgi",
|
||||
.name = "adapter",
|
||||
.description = "The name of the adapter to capture",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = NULL
|
||||
},
|
||||
{
|
||||
.module = "dxgi",
|
||||
.name = "output",
|
||||
.description = "The name of the adapter's output to capture",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = NULL
|
||||
},
|
||||
{
|
||||
.module = "dxgi",
|
||||
.name = "maxTextures",
|
||||
.description = "The maximum number of frames to buffer before skipping",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 3
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static bool dxgi_create()
|
||||
{
|
||||
assert(!this);
|
||||
this = calloc(sizeof(struct iface), 1);
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("failed to allocate iface struct");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->pointerEvent = os_createEvent(true);
|
||||
if (!this->pointerEvent)
|
||||
{
|
||||
DEBUG_ERROR("failed to create the pointer event");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->maxTextures = option_get_int("dxgi", "maxTextures");
|
||||
if (this->maxTextures <= 0)
|
||||
this->maxTextures = 1;
|
||||
|
||||
this->texture = calloc(sizeof(struct Texture), this->maxTextures);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
|
||||
{
|
||||
assert(this);
|
||||
|
||||
// this is required for DXGI 1.5 support to function
|
||||
if (!dpiDone)
|
||||
{
|
||||
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
|
||||
typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value);
|
||||
|
||||
HMODULE user32 = LoadLibraryA("user32.dll");
|
||||
User32_SetProcessDpiAwarenessContext fn;
|
||||
fn = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext");
|
||||
if (fn)
|
||||
fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
FreeLibrary(user32);
|
||||
dpiDone = true;
|
||||
}
|
||||
|
||||
HRESULT status;
|
||||
DXGI_OUTPUT_DESC outputDesc;
|
||||
|
||||
this->pointerShape = pointerShape;
|
||||
this->pointerSize = pointerSize;
|
||||
this->pointerUsed = 0;
|
||||
|
||||
this->stop = false;
|
||||
this->texRIndex = 0;
|
||||
this->texWIndex = 0;
|
||||
|
||||
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create DXGIFactory1", status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
const char * optAdapter = option_get_string("dxgi", "adapter");
|
||||
const char * optOutput = option_get_string("dxgi", "output" );
|
||||
|
||||
for(int i = 0; IDXGIFactory1_EnumAdapters1(this->factory, i, &this->adapter) != DXGI_ERROR_NOT_FOUND; ++i)
|
||||
{
|
||||
if (optAdapter)
|
||||
{
|
||||
DXGI_ADAPTER_DESC1 adapterDesc;
|
||||
IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
|
||||
|
||||
const size_t s = (wcslen(adapterDesc.Description)+1) * 2;
|
||||
char * desc = malloc(s);
|
||||
wcstombs(desc, adapterDesc.Description, s);
|
||||
|
||||
if (strstr(desc, optAdapter) == NULL)
|
||||
{
|
||||
DEBUG_INFO("Not using adapter: %ls", adapterDesc.Description);
|
||||
free(desc);
|
||||
IDXGIAdapter1_Release(this->adapter);
|
||||
this->adapter = NULL;
|
||||
continue;
|
||||
}
|
||||
free(desc);
|
||||
|
||||
DEBUG_INFO("Adapter matched, trying: %ls", adapterDesc.Description);
|
||||
}
|
||||
|
||||
for(int n = 0; IDXGIAdapter1_EnumOutputs(this->adapter, n, &this->output) != DXGI_ERROR_NOT_FOUND; ++n)
|
||||
{
|
||||
IDXGIOutput_GetDesc(this->output, &outputDesc);
|
||||
if (optOutput)
|
||||
{
|
||||
const size_t s = (wcslen(outputDesc.DeviceName)+1) * 2;
|
||||
char * desc = malloc(s);
|
||||
wcstombs(desc, outputDesc.DeviceName, s);
|
||||
|
||||
if (strstr(desc, optOutput) == NULL)
|
||||
{
|
||||
DEBUG_INFO("Not using adapter output: %ls", outputDesc.DeviceName);
|
||||
free(desc);
|
||||
IDXGIOutput_Release(this->output);
|
||||
this->output = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(desc);
|
||||
|
||||
DEBUG_INFO("Adapter output matched, trying: %ls", outputDesc.DeviceName);
|
||||
}
|
||||
|
||||
if (outputDesc.AttachedToDesktop)
|
||||
break;
|
||||
|
||||
IDXGIOutput_Release(this->output);
|
||||
this->output = NULL;
|
||||
}
|
||||
|
||||
if (this->output)
|
||||
break;
|
||||
|
||||
IDXGIAdapter1_Release(this->adapter);
|
||||
this->adapter = NULL;
|
||||
}
|
||||
|
||||
if (!this->output)
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate a valid output device");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
static const D3D_FEATURE_LEVEL featureLevels[] =
|
||||
{
|
||||
D3D_FEATURE_LEVEL_12_1,
|
||||
D3D_FEATURE_LEVEL_12_0,
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_2,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
IDXGIAdapter * tmp;
|
||||
status = IDXGIAdapter1_QueryInterface(this->adapter, &IID_IDXGIAdapter, (void **)&tmp);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_ERROR("Failed to query IDXGIAdapter interface");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
status = D3D11CreateDevice(
|
||||
tmp,
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
NULL,
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&this->device,
|
||||
&this->featureLevel,
|
||||
&this->deviceContext);
|
||||
|
||||
IDXGIAdapter_Release(tmp);
|
||||
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create D3D11 device", status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
DXGI_ADAPTER_DESC1 adapterDesc;
|
||||
IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
|
||||
this->width = outputDesc.DesktopCoordinates.right - outputDesc.DesktopCoordinates.left;
|
||||
this->height = outputDesc.DesktopCoordinates.bottom - outputDesc.DesktopCoordinates.top;
|
||||
|
||||
DEBUG_INFO("Device Descripion: %ls" , adapterDesc.Description);
|
||||
DEBUG_INFO("Device Vendor ID : 0x%x" , adapterDesc.VendorId);
|
||||
DEBUG_INFO("Device Device ID : 0x%x" , adapterDesc.DeviceId);
|
||||
DEBUG_INFO("Device Video Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedVideoMemory / 1048576));
|
||||
DEBUG_INFO("Device Sys Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedSystemMemory / 1048576));
|
||||
DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576));
|
||||
DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel);
|
||||
DEBUG_INFO("Capture Size : %u x %u", this->width, this->height);
|
||||
|
||||
// bump up our priority
|
||||
{
|
||||
IDXGIDevice * dxgi;
|
||||
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("failed to query DXGI interface from device", status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
IDXGIDevice_SetGPUThreadPriority(dxgi, 7);
|
||||
IDXGIDevice_Release(dxgi);
|
||||
}
|
||||
|
||||
IDXGIOutput5 * output5 = NULL;
|
||||
status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput5, (void **)&output5);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WARN("IDXGIOutput5 is not available, please update windows for improved performance!");
|
||||
DEBUG_WARN("Falling back to IDXIGOutput1");
|
||||
|
||||
IDXGIOutput1 * output1 = NULL;
|
||||
status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput1, (void **)&output1);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_ERROR("Failed to query IDXGIOutput1 from the output");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// we try this twice in case we still get an error on re-initialization
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
status = IDXGIOutput1_DuplicateOutput(output1, (IUnknown *)this->device, &this->dup);
|
||||
if (SUCCEEDED(status))
|
||||
break;
|
||||
Sleep(200);
|
||||
}
|
||||
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("DuplicateOutput Failed", status);
|
||||
IDXGIOutput1_Release(output1);
|
||||
goto fail;
|
||||
}
|
||||
IDXGIOutput1_Release(output1);
|
||||
}
|
||||
else
|
||||
{
|
||||
const DXGI_FORMAT supportedFormats[] =
|
||||
{
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
DXGI_FORMAT_R10G10B10A2_UNORM
|
||||
};
|
||||
|
||||
// we try this twice in case we still get an error on re-initialization
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
status = IDXGIOutput5_DuplicateOutput1(
|
||||
output5,
|
||||
(IUnknown *)this->device,
|
||||
0,
|
||||
sizeof(supportedFormats) / sizeof(DXGI_FORMAT),
|
||||
supportedFormats,
|
||||
&this->dup);
|
||||
|
||||
if (SUCCEEDED(status))
|
||||
break;
|
||||
|
||||
// if access is denied we just keep trying until it isn't
|
||||
if (status == E_ACCESSDENIED)
|
||||
--i;
|
||||
|
||||
Sleep(200);
|
||||
}
|
||||
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("DuplicateOutput1 Failed", status);
|
||||
IDXGIOutput5_Release(output5);
|
||||
goto fail;
|
||||
}
|
||||
IDXGIOutput5_Release(output5);
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_DESC dupDesc;
|
||||
IDXGIOutputDuplication_GetDesc(this->dup, &dupDesc);
|
||||
DEBUG_INFO("Source Format : %s", GetDXGIFormatStr(dupDesc.ModeDesc.Format));
|
||||
|
||||
switch(dupDesc.ModeDesc.Format)
|
||||
{
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM : this->format = CAPTURE_FMT_BGRA ; break;
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM : this->format = CAPTURE_FMT_RGBA ; break;
|
||||
case DXGI_FORMAT_R10G10B10A2_UNORM: this->format = CAPTURE_FMT_RGBA10; break;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported source format");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC texDesc;
|
||||
memset(&texDesc, 0, sizeof(texDesc));
|
||||
texDesc.Width = this->width;
|
||||
texDesc.Height = this->height;
|
||||
texDesc.MipLevels = 1;
|
||||
texDesc.ArraySize = 1;
|
||||
texDesc.SampleDesc.Count = 1;
|
||||
texDesc.SampleDesc.Quality = 0;
|
||||
texDesc.Usage = D3D11_USAGE_STAGING;
|
||||
texDesc.Format = dupDesc.ModeDesc.Format;
|
||||
texDesc.BindFlags = 0;
|
||||
texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
texDesc.MiscFlags = 0;
|
||||
|
||||
for(int i = 0; i < this->maxTextures; ++i)
|
||||
{
|
||||
status = ID3D11Device_CreateTexture2D(this->device, &texDesc, NULL, &this->texture[i].tex);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create texture", status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
this->texture[i].free = os_createEvent(true);
|
||||
if (!this->texture[i].free)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the texture free event");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// pre-signal the free events to flag as unused
|
||||
os_signalEvent(this->texture[i].free);
|
||||
|
||||
this->texture[i].mapped = os_createEvent(false);
|
||||
if (!this->texture[i].mapped)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the texture mapped event");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
// map the texture simply to get the pitch and stride
|
||||
D3D11_MAPPED_SUBRESOURCE mapping;
|
||||
status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0, D3D11_MAP_READ, 0, &mapping);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to map the texture", status);
|
||||
goto fail;
|
||||
}
|
||||
this->pitch = mapping.RowPitch;
|
||||
this->stride = mapping.RowPitch / 4;
|
||||
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0);
|
||||
|
||||
this->initialized = true;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
dxgi_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dxgi_stop()
|
||||
{
|
||||
this->stop = true;
|
||||
|
||||
os_signalEvent(this->texture[this->texRIndex].mapped);
|
||||
os_signalEvent(this->pointerEvent);
|
||||
}
|
||||
|
||||
static bool dxgi_deinit()
|
||||
{
|
||||
assert(this);
|
||||
|
||||
for(int i = 0; i < this->maxTextures; ++i)
|
||||
{
|
||||
this->texture[i].state = TEXTURE_STATE_UNUSED;
|
||||
|
||||
if (this->texture[i].map.pData)
|
||||
{
|
||||
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)this->texture[i].tex, 0);
|
||||
this->texture[i].map.pData = NULL;
|
||||
}
|
||||
|
||||
if (this->texture[i].tex)
|
||||
{
|
||||
ID3D11Texture2D_Release(this->texture[i].tex);
|
||||
this->texture[i].tex = NULL;
|
||||
}
|
||||
|
||||
if (this->texture[i].free)
|
||||
{
|
||||
os_signalEvent(this->texture[i].free);
|
||||
os_freeEvent(this->texture[i].free);
|
||||
this->texture[i].free = NULL;
|
||||
}
|
||||
|
||||
if (this->texture[i].mapped)
|
||||
{
|
||||
os_signalEvent(this->texture[i].mapped);
|
||||
os_freeEvent(this->texture[i].mapped);
|
||||
this->texture[i].mapped = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->dup)
|
||||
{
|
||||
dxgi_releaseFrame();
|
||||
IDXGIOutputDuplication_Release(this->dup);
|
||||
this->dup = NULL;
|
||||
}
|
||||
|
||||
if (this->deviceContext)
|
||||
{
|
||||
ID3D11DeviceContext_Release(this->deviceContext);
|
||||
this->deviceContext = NULL;
|
||||
}
|
||||
|
||||
if (this->output)
|
||||
{
|
||||
IDXGIOutput_Release(this->output);
|
||||
this->output = NULL;
|
||||
}
|
||||
|
||||
if (this->device)
|
||||
{
|
||||
ID3D11Device_Release(this->device);
|
||||
this->device = NULL;
|
||||
}
|
||||
|
||||
if (this->adapter)
|
||||
{
|
||||
IDXGIAdapter1_Release(this->adapter);
|
||||
this->adapter = NULL;
|
||||
}
|
||||
|
||||
if (this->factory)
|
||||
{
|
||||
// if this doesn't free we have a memory leak
|
||||
DWORD count = IDXGIFactory1_Release(this->factory);
|
||||
this->factory = NULL;
|
||||
if (count != 0)
|
||||
{
|
||||
DEBUG_ERROR("Factory release is %lu, there is a memory leak!", count);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->initialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dxgi_free()
|
||||
{
|
||||
assert(this);
|
||||
|
||||
if (this->initialized)
|
||||
dxgi_deinit();
|
||||
|
||||
os_freeEvent(this->pointerEvent);
|
||||
free(this->texture);
|
||||
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static unsigned int dxgi_getMaxFrameSize()
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
return this->height * this->pitch;
|
||||
}
|
||||
|
||||
static CaptureResult dxgi_capture()
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
CaptureResult result;
|
||||
HRESULT status;
|
||||
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
||||
IDXGIResource * res;
|
||||
|
||||
// if the read texture is pending a mapping
|
||||
for(int i = 0; i < this->maxTextures; ++i)
|
||||
{
|
||||
if (this->texture[i].state != TEXTURE_STATE_PENDING_MAP)
|
||||
continue;
|
||||
|
||||
Texture * tex = &this->texture[i];
|
||||
|
||||
// try to map the resource, but don't wait for it
|
||||
status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);
|
||||
|
||||
if (status != DXGI_ERROR_WAS_STILL_DRAWING)
|
||||
{
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to map the texture", status);
|
||||
IDXGIResource_Release(res);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// successful map, set the state and signal that there is a frame available
|
||||
tex->state = TEXTURE_STATE_MAPPED;
|
||||
os_signalEvent(tex->mapped);
|
||||
}
|
||||
}
|
||||
|
||||
// release the prior frame
|
||||
result = dxgi_releaseFrame();
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
return result;
|
||||
|
||||
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
|
||||
switch(status)
|
||||
{
|
||||
case S_OK:
|
||||
this->needsRelease = true;
|
||||
break;
|
||||
|
||||
case DXGI_ERROR_WAIT_TIMEOUT:
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
case DXGI_ERROR_ACCESS_LOST:
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
default:
|
||||
DEBUG_WINERROR("AcquireNextFrame failed", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (frameInfo.LastPresentTime.QuadPart != 0)
|
||||
{
|
||||
Texture * tex = &this->texture[this->texWIndex];
|
||||
|
||||
// check if the texture is free, if not skip the frame to keep up
|
||||
if (!os_waitEvent(tex->free, 0))
|
||||
{
|
||||
/*
|
||||
NOTE: This is only informational for when debugging, skipping frames is
|
||||
OK as we are likely getting frames faster then the client can render
|
||||
them (ie, vsync off in a title)
|
||||
*/
|
||||
//DEBUG_WARN("Frame skipped");
|
||||
}
|
||||
else
|
||||
{
|
||||
ID3D11Texture2D * src;
|
||||
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to get the texture from the dxgi resource", status);
|
||||
IDXGIResource_Release(res);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// if the texture was mapped, unmap it
|
||||
if (tex->state == TEXTURE_STATE_MAPPED)
|
||||
{
|
||||
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);
|
||||
tex->map.pData = NULL;
|
||||
}
|
||||
|
||||
// issue the copy from GPU to CPU RAM and release the src
|
||||
ID3D11DeviceContext_CopyResource(this->deviceContext,
|
||||
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
|
||||
ID3D11Texture2D_Release(src);
|
||||
|
||||
// pending map
|
||||
tex->state = TEXTURE_STATE_PENDING_MAP;
|
||||
|
||||
// advance our write pointer
|
||||
if (++this->texWIndex == this->maxTextures)
|
||||
this->texWIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
IDXGIResource_Release(res);
|
||||
|
||||
// if the pointer has moved or changed state
|
||||
bool signalPointer = false;
|
||||
if (frameInfo.LastMouseUpdateTime.QuadPart)
|
||||
{
|
||||
if (
|
||||
frameInfo.PointerPosition.Position.x != this->lastPointer.x ||
|
||||
frameInfo.PointerPosition.Position.y != this->lastPointer.y ||
|
||||
frameInfo.PointerPosition.Visible != this->lastPointer.visible
|
||||
)
|
||||
{
|
||||
this->pointer.x = frameInfo.PointerPosition.Position.x;
|
||||
this->pointer.y = frameInfo.PointerPosition.Position.y;
|
||||
this->pointer.visible = frameInfo.PointerPosition.Visible;
|
||||
signalPointer = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if the pointer shape has changed
|
||||
if (frameInfo.PointerShapeBufferSize > 0)
|
||||
{
|
||||
// update the buffer
|
||||
if (frameInfo.PointerShapeBufferSize > this->pointerSize)
|
||||
DEBUG_WARN("The pointer shape is too large to fit in the buffer, ignoring the shape");
|
||||
else
|
||||
{
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
|
||||
status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, this->pointerSize, this->pointerShape, &this->pointerUsed, &shapeInfo);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to get the new pointer shape", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
switch(shapeInfo.Type)
|
||||
{
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : this->pointer.format = CAPTURE_FMT_COLOR ; break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: this->pointer.format = CAPTURE_FMT_MASKED; break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : this->pointer.format = CAPTURE_FMT_MONO ; break;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported cursor format");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
this->pointer.w = shapeInfo.Width;
|
||||
this->pointer.h = shapeInfo.Height;
|
||||
this->pointer.pitch = shapeInfo.Pitch;
|
||||
++this->pointer.version;
|
||||
signalPointer = true;
|
||||
}
|
||||
}
|
||||
|
||||
// signal about the pointer update
|
||||
if (signalPointer)
|
||||
os_signalEvent(this->pointerEvent);
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
Texture * tex = &this->texture[this->texRIndex];
|
||||
if (!os_waitEvent(tex->mapped, 1000))
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (this->stop)
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
os_resetEvent(tex->mapped);
|
||||
|
||||
frame->width = this->width;
|
||||
frame->height = this->height;
|
||||
frame->pitch = this->pitch;
|
||||
frame->stride = this->stride;
|
||||
frame->format = this->format;
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult dxgi_getFrame(FrameBuffer frame)
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
Texture * tex = &this->texture[this->texRIndex];
|
||||
framebuffer_write(frame, tex->map.pData, this->pitch * this->height);
|
||||
os_signalEvent(tex->free);
|
||||
|
||||
if (++this->texRIndex == this->maxTextures)
|
||||
this->texRIndex = 0;
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult dxgi_getPointer(CapturePointer * pointer)
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
|
||||
if (!os_waitEvent(this->pointerEvent, 1000))
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (this->stop)
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
Pointer p;
|
||||
memcpy(&p, &this->pointer, sizeof(Pointer));
|
||||
|
||||
pointer->x = p.x;
|
||||
pointer->y = p.y;
|
||||
pointer->width = p.w;
|
||||
pointer->height = p.h;
|
||||
pointer->pitch = p.pitch;
|
||||
pointer->visible = p.visible;
|
||||
pointer->format = p.format;
|
||||
pointer->shapeUpdate = p.version > this->lastPointer.version;
|
||||
|
||||
memcpy(&this->lastPointer, &p, sizeof(Pointer));
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult dxgi_releaseFrame()
|
||||
{
|
||||
assert(this);
|
||||
if (!this->needsRelease)
|
||||
return CAPTURE_RESULT_OK;
|
||||
|
||||
HRESULT status = IDXGIOutputDuplication_ReleaseFrame(this->dup);
|
||||
switch(status)
|
||||
{
|
||||
case S_OK:
|
||||
break;
|
||||
|
||||
case DXGI_ERROR_INVALID_CALL:
|
||||
DEBUG_WINERROR("Frame was already released", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
case DXGI_ERROR_ACCESS_LOST:
|
||||
{
|
||||
this->needsRelease = false;
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
}
|
||||
|
||||
default:
|
||||
DEBUG_WINERROR("ReleaseFrame failed", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
this->needsRelease = false;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
struct CaptureInterface Capture_DXGI =
|
||||
{
|
||||
.getName = dxgi_getName,
|
||||
.initOptions = dxgi_initOptions,
|
||||
.create = dxgi_create,
|
||||
.init = dxgi_init,
|
||||
.stop = dxgi_stop,
|
||||
.deinit = dxgi_deinit,
|
||||
.free = dxgi_free,
|
||||
.getMaxFrameSize = dxgi_getMaxFrameSize,
|
||||
.capture = dxgi_capture,
|
||||
.waitFrame = dxgi_waitFrame,
|
||||
.getFrame = dxgi_getFrame,
|
||||
.getPointer = dxgi_getPointer
|
||||
};
|
||||
648
c-host/platform/Windows/capture/DXGI/src/dxgi_extra.h
Normal file
648
c-host/platform/Windows/capture/DXGI/src/dxgi_extra.h
Normal file
@@ -0,0 +1,648 @@
|
||||
/*
|
||||
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 <dxgi.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcommon.h>
|
||||
|
||||
// missing declarations in dxgi.h
|
||||
HRESULT __stdcall CreateDXGIFactory1(REFIID riid, void **factory);
|
||||
#define D3D_FEATURE_LEVEL_12_0 0xc000
|
||||
#define D3D_FEATURE_LEVEL_12_1 0xc100
|
||||
|
||||
#ifndef DXGI_ERROR_ACCESS_LOST
|
||||
#define DXGI_ERROR_ACCESS_LOST _HRESULT_TYPEDEF_(0x887A0026L)
|
||||
#endif
|
||||
|
||||
#ifndef DXGI_ERROR_WAIT_TIMEOUT
|
||||
#define DXGI_ERROR_WAIT_TIMEOUT _HRESULT_TYPEDEF_(0x887A0027L)
|
||||
#endif
|
||||
|
||||
enum DXGI_OUTDUPL_POINTER_SHAPE_TYPE {
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME = 0x1,
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR = 0x2,
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR = 0x4
|
||||
};
|
||||
|
||||
typedef struct DXGI_OUTDUPL_DESC {
|
||||
DXGI_MODE_DESC ModeDesc;
|
||||
DXGI_MODE_ROTATION Rotation;
|
||||
BOOL DesktopImageInSystemMemory;
|
||||
}
|
||||
DXGI_OUTDUPL_DESC;
|
||||
|
||||
typedef struct DXGI_OUTDUPL_POINTER_POSITION {
|
||||
POINT Position;
|
||||
BOOL Visible;
|
||||
}
|
||||
DXGI_OUTDUPL_POINTER_POSITION;
|
||||
|
||||
typedef struct DXGI_OUTDUPL_FRAME_INFO {
|
||||
LARGE_INTEGER LastPresentTime;
|
||||
LARGE_INTEGER LastMouseUpdateTime;
|
||||
UINT AccumulatedFrames;
|
||||
BOOL RectsCoalesced;
|
||||
BOOL ProtectedContentMaskedOut;
|
||||
DXGI_OUTDUPL_POINTER_POSITION PointerPosition;
|
||||
UINT TotalMetadataBufferSize;
|
||||
UINT PointerShapeBufferSize;
|
||||
}
|
||||
DXGI_OUTDUPL_FRAME_INFO;
|
||||
|
||||
typedef struct DXGI_OUTDUPL_MOVE_RECT {
|
||||
POINT SourcePoint;
|
||||
RECT DestinationRect;
|
||||
}
|
||||
DXGI_OUTDUPL_MOVE_RECT;
|
||||
|
||||
typedef struct DXGI_OUTDUPL_POINTER_SHAPE_INFO {
|
||||
UINT Type;
|
||||
UINT Width;
|
||||
UINT Height;
|
||||
UINT Pitch;
|
||||
POINT HotSpot;
|
||||
}
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO;
|
||||
|
||||
DEFINE_GUID(IID_IDXGIOutputDuplication, 0x191cfac3, 0xa341, 0x470d, 0xb2,0x6e,0xa8,0x64,0xf4,0x28,0x31,0x9c);
|
||||
|
||||
typedef interface IDXGIOutputDuplication IDXGIOutputDuplication;
|
||||
|
||||
typedef struct IDXGIOutputDuplicationVtbl {
|
||||
BEGIN_INTERFACE
|
||||
|
||||
/*** IUnknown methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *QueryInterface)(
|
||||
IDXGIOutputDuplication* This,
|
||||
REFIID riid,
|
||||
void **ppvObject);
|
||||
|
||||
ULONG (STDMETHODCALLTYPE *AddRef)(
|
||||
IDXGIOutputDuplication* This);
|
||||
|
||||
ULONG (STDMETHODCALLTYPE *Release)(
|
||||
IDXGIOutputDuplication* This);
|
||||
|
||||
/*** IDXGIObject methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *SetPrivateData)(
|
||||
IDXGIOutputDuplication* This,
|
||||
REFGUID guid,
|
||||
UINT data_size,
|
||||
const void *data);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetPrivateDataInterface)(
|
||||
IDXGIOutputDuplication* This,
|
||||
REFGUID guid,
|
||||
const IUnknown *object);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetPrivateData)(
|
||||
IDXGIOutputDuplication* This,
|
||||
REFGUID guid,
|
||||
UINT *data_size,
|
||||
void *data);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetParent)(
|
||||
IDXGIOutputDuplication* This,
|
||||
REFIID riid,
|
||||
void **parent);
|
||||
|
||||
/*** IDXGIOutputDuplication methods ***/
|
||||
|
||||
void (STDMETHODCALLTYPE *GetDesc)(
|
||||
IDXGIOutputDuplication* This,
|
||||
DXGI_OUTDUPL_DESC *pDesc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *AcquireNextFrame)(
|
||||
IDXGIOutputDuplication* This,
|
||||
UINT TimeoutInMilliseconds,
|
||||
DXGI_OUTDUPL_FRAME_INFO *pFrameInfo,
|
||||
IDXGIResource **ppDesktopResource);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetFrameDirtyRects)(
|
||||
IDXGIOutputDuplication* This,
|
||||
UINT DirtyRectsBufferSize,
|
||||
RECT *pDirtyRectsBuffer,
|
||||
UINT *pDirtyRectsBufferSizeRequired);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetFrameMoveRects)(
|
||||
IDXGIOutputDuplication* This,
|
||||
UINT MoveRectsBufferSize,
|
||||
DXGI_OUTDUPL_MOVE_RECT *pMoveRectBuffer,
|
||||
UINT *pMoveRectsBufferSizeRequired);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetFramePointerShape)(
|
||||
IDXGIOutputDuplication* This,
|
||||
UINT PointerShapeBufferSize,
|
||||
void *pPointerShapeBuffer,
|
||||
UINT *pPointerShapeBufferSizeRequired,
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO *pPointerShapeInfo);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *MapDesktopSurface)(
|
||||
IDXGIOutputDuplication* This,
|
||||
DXGI_MAPPED_RECT *pLockedRect);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *UnMapDesktopSurface)(
|
||||
IDXGIOutputDuplication* This);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *ReleaseFrame)(
|
||||
IDXGIOutputDuplication* This);
|
||||
|
||||
END_INTERFACE
|
||||
}
|
||||
IDXGIOutputDuplicationVtbl;
|
||||
|
||||
interface IDXGIOutputDuplication {
|
||||
CONST_VTBL IDXGIOutputDuplicationVtbl* lpVtbl;
|
||||
};
|
||||
|
||||
#define IDXGIOutputDuplication_Release(This) (This)->lpVtbl->Release(This)
|
||||
#define IDXGIOutputDuplication_GetDesc(This, pDesc) (This)->lpVtbl->GetDesc(This, pDesc)
|
||||
#define IDXGIOutputDuplication_AcquireNextFrame(This, TimeoutInMilliseconds, pFrameInfo, ppDesktopResource) (This)->lpVtbl->AcquireNextFrame(This, TimeoutInMilliseconds, pFrameInfo, ppDesktopResource)
|
||||
#define IDXGIOutputDuplication_GetFrameDirtyRects(This, DirtyRectsBufferSize, pDirectyRectsBuffer, pDirtyRectsBufferSizeRequired) (This)->lpVtbl->GetFrameDirtyRects(This, DirtyRectsBufferSize, pDirectyRectsBuffer, pDirtyRectsBufferSizeRequired)
|
||||
#define IDXGIOutputDuplication_GetFrameMoveRects(This, MoveRectsBufferSize, pDirtyRectsBuffer, pDirtyRectsBufferSizeRequired) (This)->lpVtbl->GetFrameMoveRects(This, MoveRectsBufferSize, pDirtyRectsBuffer, pDirtyRectsBufferSizeRequired)
|
||||
#define IDXGIOutputDuplication_GetFramePointerShape(This, PointerShapeBufferSize, pPointerShapeBuffer, pPointerShapeBufferSizeRequired, pPointerShapeInfo) (This)->lpVtbl->GetFramePointerShape(This, PointerShapeBufferSize, pPointerShapeBuffer, pPointerShapeBufferSizeRequired, pPointerShapeInfo)
|
||||
#define IDXGIOutputDuplication_MapDesktopSurface(This, pLockedRect) (This)->lpVtbl->MapDesktopSurface(This, pLockedRect)
|
||||
#define IDXGIOutputDuplication_UnMapDesktopSurface(This) (This)->lpVtbl->UnMapDesktopSurface(This)
|
||||
#define IDXGIOutputDuplication_ReleaseFrame(This) (This)->lpVtbl->ReleaseFrame(This)
|
||||
|
||||
typedef struct DXGI_MODE_DESC1
|
||||
{
|
||||
UINT Width;
|
||||
UINT Height;
|
||||
DXGI_RATIONAL RefreshRate;
|
||||
DXGI_FORMAT Format;
|
||||
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
|
||||
DXGI_MODE_SCALING Scaling;
|
||||
BOOL Stereo;
|
||||
}
|
||||
DXGI_MODE_DESC1;
|
||||
|
||||
#ifndef __dxgicommon_h__
|
||||
typedef enum DXGI_COLOR_SPACE_TYPE {
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 = 0,
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 = 1,
|
||||
DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709 = 2,
|
||||
DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020 = 3,
|
||||
DXGI_COLOR_SPACE_RESERVED = 4,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601 = 5,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601 = 6,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601 = 7,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709 = 8,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709 = 9,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020 = 10,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020 = 11,
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 = 12,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020 = 13,
|
||||
DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020 = 14,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020 = 15,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020 = 16,
|
||||
DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 = 17,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020 = 18,
|
||||
DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020 = 19,
|
||||
DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P709 = 20,
|
||||
DXGI_COLOR_SPACE_RGB_STUDIO_G24_NONE_P2020 = 21,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P709 = 22,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P2020 = 23,
|
||||
DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020 = 24,
|
||||
DXGI_COLOR_SPACE_CUSTOM = 0xFFFFFFFF
|
||||
} DXGI_COLOR_SPACE_TYPE;
|
||||
#endif
|
||||
|
||||
DEFINE_GUID(IID_IDXGIOutput1, 0x00cddea8, 0x939b, 0x4b83, 0xa3,0x40,0xa6,0x85,0x22,0x66,0x66,0xcc);
|
||||
|
||||
typedef struct IDXGIOutput1 IDXGIOutput1;
|
||||
|
||||
typedef struct IDXGIOutput1Vtbl {
|
||||
BEGIN_INTERFACE
|
||||
|
||||
/*** IUnknown methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *QueryInterface)(
|
||||
IDXGIOutput1* This,
|
||||
REFIID riid,
|
||||
void **ppvObject);
|
||||
|
||||
ULONG (STDMETHODCALLTYPE *AddRef)(
|
||||
IDXGIOutput1* This);
|
||||
|
||||
ULONG (STDMETHODCALLTYPE *Release)(
|
||||
IDXGIOutput1* This);
|
||||
|
||||
/*** IDXGIObject methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *SetPrivateData)(
|
||||
IDXGIOutput1* This,
|
||||
REFGUID guid,
|
||||
UINT data_size,
|
||||
const void *data);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetPrivateDataInterface)(
|
||||
IDXGIOutput1* This,
|
||||
REFGUID guid,
|
||||
const IUnknown *object);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetPrivateData)(
|
||||
IDXGIOutput1* This,
|
||||
REFGUID guid,
|
||||
UINT *data_size,
|
||||
void *data);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetParent)(
|
||||
IDXGIOutput1* This,
|
||||
REFIID riid,
|
||||
void **parent);
|
||||
|
||||
/*** IDXGIOutput methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *GetDesc)(
|
||||
IDXGIOutput1* This,
|
||||
DXGI_OUTPUT_DESC *desc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplayModeList)(
|
||||
IDXGIOutput1* This,
|
||||
DXGI_FORMAT format,
|
||||
UINT flags,
|
||||
UINT *mode_count,
|
||||
DXGI_MODE_DESC *desc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode)(
|
||||
IDXGIOutput1* This,
|
||||
const DXGI_MODE_DESC *mode,
|
||||
DXGI_MODE_DESC *closest_match,
|
||||
IUnknown *device);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *WaitForVBlank)(
|
||||
IDXGIOutput1* This);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *TakeOwnership)(
|
||||
IDXGIOutput1* This,
|
||||
IUnknown *device,
|
||||
WINBOOL exclusive);
|
||||
|
||||
void (STDMETHODCALLTYPE *ReleaseOwnership)(
|
||||
IDXGIOutput1* This);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetGammaControlCapabilities)(
|
||||
IDXGIOutput1* This,
|
||||
DXGI_GAMMA_CONTROL_CAPABILITIES *gamma_caps);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetGammaControl)(
|
||||
IDXGIOutput1* This,
|
||||
const DXGI_GAMMA_CONTROL *gamma_control);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetGammaControl)(
|
||||
IDXGIOutput1* This,
|
||||
DXGI_GAMMA_CONTROL *gamma_control);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetDisplaySurface)(
|
||||
IDXGIOutput1* This,
|
||||
IDXGISurface *surface);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData)(
|
||||
IDXGIOutput1* This,
|
||||
IDXGISurface *surface);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetFrameStatistics)(
|
||||
IDXGIOutput1* This,
|
||||
DXGI_FRAME_STATISTICS *stats);
|
||||
|
||||
/*** IDXGIOutput1 methods ***/
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplayModeList1)(
|
||||
IDXGIOutput1* This,
|
||||
DXGI_FORMAT EnumFormat,
|
||||
UINT Flags,
|
||||
UINT *pNumModes,
|
||||
DXGI_MODE_DESC1 *pDesc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode1)(
|
||||
IDXGIOutput1* This,
|
||||
const DXGI_MODE_DESC1 *pModeToMatch,
|
||||
DXGI_MODE_DESC1 *pClosestMatch,
|
||||
IUnknown *pConcernedDevice);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData1)(
|
||||
IDXGIOutput1* This,
|
||||
IDXGIResource *pDestination);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *DuplicateOutput)(
|
||||
IDXGIOutput1* This,
|
||||
IUnknown *pDevice,
|
||||
IDXGIOutputDuplication **ppOutputDuplication);
|
||||
|
||||
END_INTERFACE
|
||||
}
|
||||
IDXGIOutput1Vtbl;
|
||||
interface IDXGIOutput1 {
|
||||
CONST_VTBL IDXGIOutput1Vtbl* lpVtbl;
|
||||
};
|
||||
|
||||
#define IDXGIOutput1_DuplicateOutput(This,pDevice,ppOutputDuplication) (This)->lpVtbl->DuplicateOutput(This,pDevice,ppOutputDuplication)
|
||||
#define IDXGIOutput1_Release(This) (This)->lpVtbl->Release(This);
|
||||
|
||||
DEFINE_GUID(IID_IDXGIOutput5, 0x80a07424, 0xab52, 0x42eb, 0x83,0x3c,0x0c,0x42,0xfd,0x28,0x2d,0x98);
|
||||
|
||||
typedef struct IDXGIOutput5 IDXGIOutput5;
|
||||
|
||||
typedef struct IDXGIOutput5Vtbl {
|
||||
BEGIN_INTERFACE
|
||||
|
||||
/*** IUnknown methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *QueryInterface)(
|
||||
IDXGIOutput5* This,
|
||||
REFIID riid,
|
||||
void **ppvObject);
|
||||
|
||||
ULONG (STDMETHODCALLTYPE *AddRef)(
|
||||
IDXGIOutput5* This);
|
||||
|
||||
ULONG (STDMETHODCALLTYPE *Release)(
|
||||
IDXGIOutput5* This);
|
||||
|
||||
/*** IDXGIObject methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *SetPrivateData)(
|
||||
IDXGIOutput5* This,
|
||||
REFGUID guid,
|
||||
UINT data_size,
|
||||
const void *data);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetPrivateDataInterface)(
|
||||
IDXGIOutput5* This,
|
||||
REFGUID guid,
|
||||
const IUnknown *object);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetPrivateData)(
|
||||
IDXGIOutput5* This,
|
||||
REFGUID guid,
|
||||
UINT *data_size,
|
||||
void *data);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetParent)(
|
||||
IDXGIOutput5* This,
|
||||
REFIID riid,
|
||||
void **parent);
|
||||
|
||||
/*** IDXGIOutput methods ***/
|
||||
HRESULT (STDMETHODCALLTYPE *GetDesc)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_OUTPUT_DESC *desc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplayModeList)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_FORMAT format,
|
||||
UINT flags,
|
||||
UINT *mode_count,
|
||||
DXGI_MODE_DESC *desc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode)(
|
||||
IDXGIOutput5* This,
|
||||
const DXGI_MODE_DESC *mode,
|
||||
DXGI_MODE_DESC *closest_match,
|
||||
IUnknown *device);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *WaitForVBlank)(
|
||||
IDXGIOutput5* This);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *TakeOwnership)(
|
||||
IDXGIOutput5* This,
|
||||
IUnknown *device,
|
||||
WINBOOL exclusive);
|
||||
|
||||
void (STDMETHODCALLTYPE *ReleaseOwnership)(
|
||||
IDXGIOutput5* This);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetGammaControlCapabilities)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_GAMMA_CONTROL_CAPABILITIES *gamma_caps);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetGammaControl)(
|
||||
IDXGIOutput5* This,
|
||||
const DXGI_GAMMA_CONTROL *gamma_control);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetGammaControl)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_GAMMA_CONTROL *gamma_control);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *SetDisplaySurface)(
|
||||
IDXGIOutput5* This,
|
||||
IDXGISurface *surface);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData)(
|
||||
IDXGIOutput5* This,
|
||||
IDXGISurface *surface);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetFrameStatistics)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_FRAME_STATISTICS *stats);
|
||||
|
||||
/*** IDXGIOutput1 methods ***/
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplayModeList1)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_FORMAT EnumFormat,
|
||||
UINT Flags,
|
||||
UINT *pNumModes,
|
||||
DXGI_MODE_DESC1 *pDesc);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *FindClosestMatchingMode1)(
|
||||
IDXGIOutput5* This,
|
||||
const DXGI_MODE_DESC1 *pModeToMatch,
|
||||
DXGI_MODE_DESC1 *pClosestMatch,
|
||||
IUnknown *pConcernedDevice);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *GetDisplaySurfaceData1)(
|
||||
IDXGIOutput5* This,
|
||||
IDXGIResource *pDestination);
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *DuplicateOutput)(
|
||||
IDXGIOutput5* This,
|
||||
IUnknown *pDevice,
|
||||
IDXGIOutputDuplication **ppOutputDuplication);
|
||||
|
||||
/*** IDXGIOutput2 methods ***/
|
||||
|
||||
BOOL (STDMETHODCALLTYPE *SupportsOverlays)(
|
||||
IDXGIOutput5* This);
|
||||
|
||||
/*** IDXGIOutput3 methods ***/
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *CheckOverlaySupport)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_FORMAT EnumFormat,
|
||||
IUnknown *pConcernedDevice,
|
||||
UINT *pFlags);
|
||||
|
||||
/*** IDXGIOutput4 methods ***/
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *CheckOverlayColorSpaceSupport)(
|
||||
IDXGIOutput5* This,
|
||||
DXGI_FORMAT Format,
|
||||
DXGI_COLOR_SPACE_TYPE ColorSpace,
|
||||
IUnknown *pConcernedDevice,
|
||||
UINT *pFlags);
|
||||
|
||||
/*** IDXGIOutput5 methods ***/
|
||||
|
||||
HRESULT (STDMETHODCALLTYPE *DuplicateOutput1)(
|
||||
IDXGIOutput5* This,
|
||||
IUnknown *pDevice,
|
||||
UINT Flags,
|
||||
UINT SupportedFormatsCount,
|
||||
const DXGI_FORMAT *pSupportedFormats,
|
||||
IDXGIOutputDuplication **ppOutputDuplication);
|
||||
|
||||
END_INTERFACE
|
||||
}
|
||||
IDXGIOutput5Vtbl;
|
||||
interface IDXGIOutput5 {
|
||||
CONST_VTBL IDXGIOutput5Vtbl* lpVtbl;
|
||||
};
|
||||
|
||||
#define IDXGIOutput5_DuplicateOutput1(This,pDevice,Flags,SupportedForamtsCount,pSupportedFormats,ppOutputDuplication) (This)->lpVtbl->DuplicateOutput1(This,pDevice,Flags,SupportedForamtsCount,pSupportedFormats,ppOutputDuplication)
|
||||
#define IDXGIOutput5_Release(This) (This)->lpVtbl->Release(This);
|
||||
|
||||
|
||||
static const char * DXGI_FORMAT_STR[] = {
|
||||
"DXGI_FORMAT_UNKNOWN",
|
||||
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
|
||||
"DXGI_FORMAT_R32G32B32A32_FLOAT",
|
||||
"DXGI_FORMAT_R32G32B32A32_UINT",
|
||||
"DXGI_FORMAT_R32G32B32A32_SINT",
|
||||
"DXGI_FORMAT_R32G32B32_TYPELESS",
|
||||
"DXGI_FORMAT_R32G32B32_FLOAT",
|
||||
"DXGI_FORMAT_R32G32B32_UINT",
|
||||
"DXGI_FORMAT_R32G32B32_SINT",
|
||||
"DXGI_FORMAT_R16G16B16A16_TYPELESS",
|
||||
"DXGI_FORMAT_R16G16B16A16_FLOAT",
|
||||
"DXGI_FORMAT_R16G16B16A16_UNORM",
|
||||
"DXGI_FORMAT_R16G16B16A16_UINT",
|
||||
"DXGI_FORMAT_R16G16B16A16_SNORM",
|
||||
"DXGI_FORMAT_R16G16B16A16_SINT",
|
||||
"DXGI_FORMAT_R32G32_TYPELESS",
|
||||
"DXGI_FORMAT_R32G32_FLOAT",
|
||||
"DXGI_FORMAT_R32G32_UINT",
|
||||
"DXGI_FORMAT_R32G32_SINT",
|
||||
"DXGI_FORMAT_R32G8X24_TYPELESS",
|
||||
"DXGI_FORMAT_D32_FLOAT_S8X24_UINT",
|
||||
"DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS",
|
||||
"DXGI_FORMAT_X32_TYPELESS_G8X24_UINT",
|
||||
"DXGI_FORMAT_R10G10B10A2_TYPELESS",
|
||||
"DXGI_FORMAT_R10G10B10A2_UNORM",
|
||||
"DXGI_FORMAT_R10G10B10A2_UINT",
|
||||
"DXGI_FORMAT_R11G11B10_FLOAT",
|
||||
"DXGI_FORMAT_R8G8B8A8_TYPELESS",
|
||||
"DXGI_FORMAT_R8G8B8A8_UNORM",
|
||||
"DXGI_FORMAT_R8G8B8A8_UNORM_SRGB",
|
||||
"DXGI_FORMAT_R8G8B8A8_UINT",
|
||||
"DXGI_FORMAT_R8G8B8A8_SNORM",
|
||||
"DXGI_FORMAT_R8G8B8A8_SINT",
|
||||
"DXGI_FORMAT_R16G16_TYPELESS",
|
||||
"DXGI_FORMAT_R16G16_FLOAT",
|
||||
"DXGI_FORMAT_R16G16_UNORM",
|
||||
"DXGI_FORMAT_R16G16_UINT",
|
||||
"DXGI_FORMAT_R16G16_SNORM",
|
||||
"DXGI_FORMAT_R16G16_SINT",
|
||||
"DXGI_FORMAT_R32_TYPELESS",
|
||||
"DXGI_FORMAT_D32_FLOAT",
|
||||
"DXGI_FORMAT_R32_FLOAT",
|
||||
"DXGI_FORMAT_R32_UINT",
|
||||
"DXGI_FORMAT_R32_SINT",
|
||||
"DXGI_FORMAT_R24G8_TYPELESS",
|
||||
"DXGI_FORMAT_D24_UNORM_S8_UINT",
|
||||
"DXGI_FORMAT_R24_UNORM_X8_TYPELESS",
|
||||
"DXGI_FORMAT_X24_TYPELESS_G8_UINT",
|
||||
"DXGI_FORMAT_R8G8_TYPELESS",
|
||||
"DXGI_FORMAT_R8G8_UNORM",
|
||||
"DXGI_FORMAT_R8G8_UINT",
|
||||
"DXGI_FORMAT_R8G8_SNORM",
|
||||
"DXGI_FORMAT_R8G8_SINT",
|
||||
"DXGI_FORMAT_R16_TYPELESS",
|
||||
"DXGI_FORMAT_R16_FLOAT",
|
||||
"DXGI_FORMAT_D16_UNORM",
|
||||
"DXGI_FORMAT_R16_UNORM",
|
||||
"DXGI_FORMAT_R16_UINT",
|
||||
"DXGI_FORMAT_R16_SNORM",
|
||||
"DXGI_FORMAT_R16_SINT",
|
||||
"DXGI_FORMAT_R8_TYPELESS",
|
||||
"DXGI_FORMAT_R8_UNORM",
|
||||
"DXGI_FORMAT_R8_UINT",
|
||||
"DXGI_FORMAT_R8_SNORM",
|
||||
"DXGI_FORMAT_R8_SINT",
|
||||
"DXGI_FORMAT_A8_UNORM",
|
||||
"DXGI_FORMAT_R1_UNORM",
|
||||
"DXGI_FORMAT_R9G9B9E5_SHAREDEXP",
|
||||
"DXGI_FORMAT_R8G8_B8G8_UNORM",
|
||||
"DXGI_FORMAT_G8R8_G8B8_UNORM",
|
||||
"DXGI_FORMAT_BC1_TYPELESS",
|
||||
"DXGI_FORMAT_BC1_UNORM",
|
||||
"DXGI_FORMAT_BC1_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC2_TYPELESS",
|
||||
"DXGI_FORMAT_BC2_UNORM",
|
||||
"DXGI_FORMAT_BC2_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC3_TYPELESS",
|
||||
"DXGI_FORMAT_BC3_UNORM",
|
||||
"DXGI_FORMAT_BC3_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC4_TYPELESS",
|
||||
"DXGI_FORMAT_BC4_UNORM",
|
||||
"DXGI_FORMAT_BC4_SNORM",
|
||||
"DXGI_FORMAT_BC5_TYPELESS",
|
||||
"DXGI_FORMAT_BC5_UNORM",
|
||||
"DXGI_FORMAT_BC5_SNORM",
|
||||
"DXGI_FORMAT_B5G6R5_UNORM",
|
||||
"DXGI_FORMAT_B5G5R5A1_UNORM",
|
||||
"DXGI_FORMAT_B8G8R8A8_UNORM",
|
||||
"DXGI_FORMAT_B8G8R8X8_UNORM",
|
||||
"DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM",
|
||||
"DXGI_FORMAT_B8G8R8A8_TYPELESS",
|
||||
"DXGI_FORMAT_B8G8R8A8_UNORM_SRGB",
|
||||
"DXGI_FORMAT_B8G8R8X8_TYPELESS",
|
||||
"DXGI_FORMAT_B8G8R8X8_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC6H_TYPELESS",
|
||||
"DXGI_FORMAT_BC6H_UF16",
|
||||
"DXGI_FORMAT_BC6H_SF16",
|
||||
"DXGI_FORMAT_BC7_TYPELESS",
|
||||
"DXGI_FORMAT_BC7_UNORM",
|
||||
"DXGI_FORMAT_BC7_UNORM_SRGB",
|
||||
"DXGI_FORMAT_AYUV",
|
||||
"DXGI_FORMAT_Y410",
|
||||
"DXGI_FORMAT_Y416",
|
||||
"DXGI_FORMAT_NV12",
|
||||
"DXGI_FORMAT_P010",
|
||||
"DXGI_FORMAT_P016",
|
||||
"DXGI_FORMAT_420_OPAQUE",
|
||||
"DXGI_FORMAT_YUY2",
|
||||
"DXGI_FORMAT_Y210",
|
||||
"DXGI_FORMAT_Y216",
|
||||
"DXGI_FORMAT_NV11",
|
||||
"DXGI_FORMAT_AI44",
|
||||
"DXGI_FORMAT_IA44",
|
||||
"DXGI_FORMAT_P8",
|
||||
"DXGI_FORMAT_A8P8",
|
||||
"DXGI_FORMAT_B4G4R4A4_UNORM",
|
||||
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
|
||||
"DXGI_FORMAT_P208",
|
||||
"DXGI_FORMAT_V208",
|
||||
"DXGI_FORMAT_V408"
|
||||
};
|
||||
|
||||
static const char * GetDXGIFormatStr(DXGI_FORMAT format)
|
||||
{
|
||||
if (format > sizeof(DXGI_FORMAT_STR) / sizeof(const char *))
|
||||
return DXGI_FORMAT_STR[0];
|
||||
return DXGI_FORMAT_STR[format];
|
||||
}
|
||||
19
c-host/platform/Windows/capture/NVFBC/CMakeLists.txt
Normal file
19
c-host/platform/Windows/capture/NVFBC/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(capture_NVFBC LANGUAGES C CXX)
|
||||
|
||||
add_library(capture_NVFBC STATIC
|
||||
src/nvfbc.c
|
||||
src/wrapper.cpp
|
||||
)
|
||||
|
||||
file(TO_CMAKE_PATH "${NVFBC_SDK}" nvfbc_sdk)
|
||||
include_directories(file, "${nvfbc_sdk}/inc")
|
||||
|
||||
target_include_directories(capture_NVFBC
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
|
||||
target_link_libraries(capture_NVFBC
|
||||
platform_Windows
|
||||
)
|
||||
326
c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
Normal file
326
c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
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/capture.h"
|
||||
#include "interface/platform.h"
|
||||
#include "windows/platform.h"
|
||||
#include "windows/debug.h"
|
||||
#include "windows/mousehook.h"
|
||||
#include "common/option.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <NvFBC/nvFBC.h>
|
||||
#include "wrapper.h"
|
||||
|
||||
struct iface
|
||||
{
|
||||
bool stop;
|
||||
NvFBCHandle nvfbc;
|
||||
|
||||
bool seperateCursor;
|
||||
void * pointerShape;
|
||||
unsigned int pointerSize;
|
||||
unsigned int maxWidth, maxHeight;
|
||||
unsigned int width , height;
|
||||
|
||||
uint8_t * frameBuffer;
|
||||
|
||||
NvFBCFrameGrabInfo grabInfo;
|
||||
|
||||
osEventHandle * frameEvent;
|
||||
osEventHandle * cursorEvents[2];
|
||||
|
||||
int mouseX, mouseY, mouseHotX, mouseHotY;
|
||||
bool mouseVisible;
|
||||
};
|
||||
|
||||
static struct iface * this = NULL;
|
||||
|
||||
static void nvfbc_free();
|
||||
|
||||
static void getDesktopSize(unsigned int * width, unsigned int * height)
|
||||
{
|
||||
HMONITOR monitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO monitorInfo = {
|
||||
.cbSize = sizeof(MONITORINFO)
|
||||
};
|
||||
|
||||
GetMonitorInfo(monitor, &monitorInfo);
|
||||
CloseHandle(monitor);
|
||||
|
||||
*width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
|
||||
*height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
|
||||
}
|
||||
|
||||
static void on_mouseMove(int x, int y)
|
||||
{
|
||||
this->mouseX = x;
|
||||
this->mouseY = y;
|
||||
os_signalEvent(this->cursorEvents[0]);
|
||||
}
|
||||
|
||||
static const char * nvfbc_getName()
|
||||
{
|
||||
return "NVFBC (NVidia Frame Buffer Capture)";
|
||||
};
|
||||
|
||||
static void nvfbc_initOptions()
|
||||
{
|
||||
struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "nvfbc",
|
||||
.name = "decoupleCursor",
|
||||
.description = "Capture the cursor seperately",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static bool nvfbc_create()
|
||||
{
|
||||
if (!NvFBCInit())
|
||||
return false;
|
||||
|
||||
int bufferLen = GetEnvironmentVariable("NVFBC_PRIV_DATA", NULL, 0);
|
||||
uint8_t * privData = NULL;
|
||||
int privDataLen = 0;
|
||||
|
||||
if(bufferLen)
|
||||
{
|
||||
char * buffer = malloc(bufferLen);
|
||||
GetEnvironmentVariable("NVFBC_PRIV_DATA", buffer, bufferLen);
|
||||
|
||||
privDataLen = (bufferLen - 1) / 2;
|
||||
privData = (uint8_t *)malloc(privDataLen);
|
||||
char hex[3] = {0};
|
||||
for(int i = 0; i < privDataLen; ++i)
|
||||
{
|
||||
memcpy(hex, &buffer[i*2], 2);
|
||||
privData[i] = (uint8_t)strtoul(hex, NULL, 16);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
this = (struct iface *)calloc(sizeof(struct iface), 1);
|
||||
if (!NvFBCToSysCreate(privData, privDataLen, &this->nvfbc, &this->maxWidth, &this->maxHeight))
|
||||
{
|
||||
free(privData);
|
||||
nvfbc_free();
|
||||
return false;
|
||||
}
|
||||
free(privData);
|
||||
|
||||
this->frameEvent = os_createEvent(true);
|
||||
if (!this->frameEvent)
|
||||
{
|
||||
DEBUG_ERROR("failed to create the frame event");
|
||||
nvfbc_free();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
|
||||
{
|
||||
this->stop = false;
|
||||
this->pointerShape = pointerShape;
|
||||
this->pointerSize = pointerSize;
|
||||
|
||||
getDesktopSize(&this->width, &this->height);
|
||||
os_resetEvent(this->frameEvent);
|
||||
|
||||
|
||||
HANDLE event;
|
||||
if (!NvFBCToSysSetup(
|
||||
this->nvfbc,
|
||||
BUFFER_FMT_ARGB,
|
||||
!this->seperateCursor,
|
||||
this->seperateCursor,
|
||||
false,
|
||||
0,
|
||||
(void **)&this->frameBuffer,
|
||||
NULL,
|
||||
&event
|
||||
))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->cursorEvents[0] = os_createEvent(true);
|
||||
mouseHook_install(on_mouseMove);
|
||||
|
||||
if (this->seperateCursor)
|
||||
this->cursorEvents[1] = os_wrapEvent(event);
|
||||
|
||||
DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated");
|
||||
|
||||
Sleep(100);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nvfbc_stop()
|
||||
{
|
||||
this->stop = true;
|
||||
os_signalEvent(this->cursorEvents[0]);
|
||||
os_signalEvent(this->frameEvent);
|
||||
}
|
||||
|
||||
static bool nvfbc_deinit()
|
||||
{
|
||||
mouseHook_remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nvfbc_free()
|
||||
{
|
||||
NvFBCToSysRelease(&this->nvfbc);
|
||||
|
||||
if (this->frameEvent)
|
||||
os_freeEvent(this->frameEvent);
|
||||
|
||||
free(this);
|
||||
this = NULL;
|
||||
NvFBCFree();
|
||||
}
|
||||
|
||||
static unsigned int nvfbc_getMaxFrameSize()
|
||||
{
|
||||
return this->maxWidth * this->maxHeight * 4;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_capture()
|
||||
{
|
||||
getDesktopSize(&this->width, &this->height);
|
||||
NvFBCFrameGrabInfo grabInfo;
|
||||
CaptureResult result = NvFBCToSysCapture(
|
||||
this->nvfbc,
|
||||
1000,
|
||||
0, 0,
|
||||
this->width,
|
||||
this->height,
|
||||
&grabInfo
|
||||
);
|
||||
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
return result;
|
||||
|
||||
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
|
||||
os_signalEvent(this->frameEvent);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
|
||||
{
|
||||
if (!os_waitEvent(this->frameEvent, 1000))
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (this->stop)
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
frame->width = this->grabInfo.dwWidth;
|
||||
frame->height = this->grabInfo.dwHeight;
|
||||
frame->pitch = this->grabInfo.dwBufferWidth * 4;
|
||||
frame->stride = this->grabInfo.dwBufferWidth;
|
||||
|
||||
#if 0
|
||||
//NvFBC never sets bIsHDR so instead we check for any data in the alpha channel
|
||||
//If there is data, it's HDR. This is clearly suboptimal
|
||||
if (!this->grabInfo.bIsHDR)
|
||||
for(int y = 0; y < frame->height; ++y)
|
||||
for(int x = 0; x < frame->width; ++x)
|
||||
{
|
||||
int offset = (y * frame->pitch) + (x * 4);
|
||||
if (this->frameBuffer[offset + 3])
|
||||
{
|
||||
this->grabInfo.bIsHDR = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_getFrame(FrameBuffer frame)
|
||||
{
|
||||
framebuffer_write(
|
||||
frame,
|
||||
this->frameBuffer,
|
||||
this->grabInfo.dwHeight * this->grabInfo.dwBufferWidth * 4
|
||||
);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_getPointer(CapturePointer * pointer)
|
||||
{
|
||||
osEventHandle * events[2];
|
||||
memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2);
|
||||
if (!os_waitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (this->stop)
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
CaptureResult result;
|
||||
pointer->shapeUpdate = false;
|
||||
if (this->seperateCursor && events[1])
|
||||
{
|
||||
result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize);
|
||||
this->mouseVisible = pointer->visible;
|
||||
this->mouseHotX = pointer->x;
|
||||
this->mouseHotY = pointer->y;
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
pointer->visible = this->mouseVisible;
|
||||
pointer->x = this->mouseX - this->mouseHotX;
|
||||
pointer->y = this->mouseY - this->mouseHotY;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
struct CaptureInterface Capture_NVFBC =
|
||||
{
|
||||
.getName = nvfbc_getName,
|
||||
.initOptions = nvfbc_initOptions,
|
||||
|
||||
.create = nvfbc_create,
|
||||
.init = nvfbc_init,
|
||||
.stop = nvfbc_stop,
|
||||
.deinit = nvfbc_deinit,
|
||||
.free = nvfbc_free,
|
||||
.getMaxFrameSize = nvfbc_getMaxFrameSize,
|
||||
.capture = nvfbc_capture,
|
||||
.waitFrame = nvfbc_waitFrame,
|
||||
.getFrame = nvfbc_getFrame,
|
||||
.getPointer = nvfbc_getPointer
|
||||
};
|
||||
330
c-host/platform/Windows/capture/NVFBC/src/wrapper.cpp
Normal file
330
c-host/platform/Windows/capture/NVFBC/src/wrapper.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
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 "wrapper.h"
|
||||
#include "windows/debug.h"
|
||||
#include <windows.h>
|
||||
#include <NvFBC/nvFBCToSys.h>
|
||||
|
||||
#ifdef _WIN64
|
||||
#define NVFBC_DLL "NvFBC64.dll"
|
||||
#else
|
||||
#define NVFBC_DLL "NvFBC.dll"
|
||||
#endif
|
||||
|
||||
struct NVAPI
|
||||
{
|
||||
bool initialized;
|
||||
HMODULE dll;
|
||||
|
||||
NvFBC_CreateFunctionExType createEx;
|
||||
NvFBC_SetGlobalFlagsType setGlobalFlags;
|
||||
NvFBC_GetStatusExFunctionType getStatusEx;
|
||||
NvFBC_EnableFunctionType enable;
|
||||
NvFBC_GetSDKVersionFunctionType getVersion;
|
||||
};
|
||||
|
||||
struct stNvFBCHandle
|
||||
{
|
||||
NvFBCToSys * nvfbc;
|
||||
HANDLE cursorEvent;
|
||||
int retry;
|
||||
};
|
||||
|
||||
static NVAPI nvapi;
|
||||
|
||||
bool NvFBCInit()
|
||||
{
|
||||
if (nvapi.initialized)
|
||||
return true;
|
||||
|
||||
nvapi.dll = LoadLibraryA(NVFBC_DLL);
|
||||
if (!nvapi.dll)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to load " NVFBC_DLL, GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
nvapi.createEx = (NvFBC_CreateFunctionExType )GetProcAddress(nvapi.dll, "NvFBC_CreateEx" );
|
||||
nvapi.setGlobalFlags = (NvFBC_SetGlobalFlagsType )GetProcAddress(nvapi.dll, "NvFBC_SetGlobalFlags");
|
||||
nvapi.getStatusEx = (NvFBC_GetStatusExFunctionType )GetProcAddress(nvapi.dll, "NvFBC_GetStatusEx" );
|
||||
nvapi.enable = (NvFBC_EnableFunctionType )GetProcAddress(nvapi.dll, "NvFBC_Enable" );
|
||||
nvapi.getVersion = (NvFBC_GetSDKVersionFunctionType)GetProcAddress(nvapi.dll, "NvFBC_GetSDKVersion" );
|
||||
|
||||
if (
|
||||
!nvapi.createEx ||
|
||||
!nvapi.setGlobalFlags ||
|
||||
!nvapi.getStatusEx ||
|
||||
!nvapi.enable ||
|
||||
!nvapi.getVersion)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the required proc addresses");
|
||||
return false;
|
||||
}
|
||||
|
||||
NvU32 version;
|
||||
if (nvapi.getVersion(&version) != NVFBC_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the NvFBC SDK version");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("NvFBC SDK Version: %lu", version);
|
||||
|
||||
if (nvapi.enable(NVFBC_STATE_ENABLE) != NVFBC_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to enable the NvFBC interface");
|
||||
return false;
|
||||
}
|
||||
|
||||
nvapi.initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NvFBCFree()
|
||||
{
|
||||
if (!nvapi.initialized)
|
||||
return;
|
||||
|
||||
FreeLibrary(nvapi.dll);
|
||||
nvapi.initialized = false;
|
||||
}
|
||||
|
||||
bool NvFBCToSysCreate(
|
||||
void * privData,
|
||||
unsigned int privDataSize,
|
||||
NvFBCHandle * handle,
|
||||
unsigned int * maxWidth,
|
||||
unsigned int * maxHeight
|
||||
)
|
||||
{
|
||||
NvFBCCreateParams params = {0};
|
||||
|
||||
params.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
||||
params.dwInterfaceType = NVFBC_TO_SYS;
|
||||
params.pDevice = NULL;
|
||||
params.dwAdapterIdx = 0;
|
||||
params.dwPrivateDataSize = privDataSize;
|
||||
params.pPrivateData = privData;
|
||||
|
||||
if (nvapi.createEx(¶ms) != NVFBC_SUCCESS)
|
||||
{
|
||||
*handle = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
*handle = (NvFBCHandle)calloc(sizeof(struct stNvFBCHandle), 1);
|
||||
(*handle)->nvfbc = static_cast<NvFBCToSys *>(params.pNvFBC);
|
||||
|
||||
if (maxWidth)
|
||||
*maxWidth = params.dwMaxDisplayWidth;
|
||||
|
||||
if (maxHeight)
|
||||
*maxHeight = params.dwMaxDisplayHeight;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NvFBCToSysRelease(NvFBCHandle * handle)
|
||||
{
|
||||
if (!*handle)
|
||||
return;
|
||||
|
||||
(*handle)->nvfbc->NvFBCToSysRelease();
|
||||
free(*handle);
|
||||
*handle = NULL;
|
||||
}
|
||||
|
||||
bool NvFBCToSysSetup(
|
||||
NvFBCHandle handle,
|
||||
enum BufferFormat format,
|
||||
bool hwCursor,
|
||||
bool seperateCursorCapture,
|
||||
bool useDiffMap,
|
||||
enum DiffMapBlockSize diffMapBlockSize,
|
||||
void ** frameBuffer,
|
||||
void ** diffMap,
|
||||
HANDLE * cursorEvent
|
||||
)
|
||||
{
|
||||
NVFBC_TOSYS_SETUP_PARAMS params = {0};
|
||||
params.dwVersion = NVFBC_TOSYS_SETUP_PARAMS_VER;
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case BUFFER_FMT_ARGB : params.eMode = NVFBC_TOSYS_ARGB ; break;
|
||||
case BUFFER_FMT_RGB : params.eMode = NVFBC_TOSYS_RGB ; break;
|
||||
case BUFFER_FMT_YYYYUV420p: params.eMode = NVFBC_TOSYS_YYYYUV420p; break;
|
||||
case BUFFER_FMT_RGB_PLANAR: params.eMode = NVFBC_TOSYS_RGB_PLANAR; break;
|
||||
case BUFFER_FMT_XOR : params.eMode = NVFBC_TOSYS_XOR ; break;
|
||||
case BUFFER_FMT_YUV444p : params.eMode = NVFBC_TOSYS_YUV444p ; break;
|
||||
case BUFFER_FMT_ARGB10 :
|
||||
params.eMode = NVFBC_TOSYS_ARGB10;
|
||||
params.bHDRRequest = TRUE;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_INFO("Invalid format");
|
||||
return false;
|
||||
}
|
||||
|
||||
params.bWithHWCursor = hwCursor ? TRUE : FALSE;
|
||||
params.bEnableSeparateCursorCapture = seperateCursorCapture ? TRUE : FALSE;
|
||||
params.bDiffMap = useDiffMap ? TRUE : FALSE;
|
||||
|
||||
switch(diffMapBlockSize)
|
||||
{
|
||||
case DIFFMAP_BLOCKSIZE_128X128: params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_128X128; break;
|
||||
case DIFFMAP_BLOCKSIZE_16X16 : params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_16X16 ; break;
|
||||
case DIFFMAP_BLOCKSIZE_32X32 : params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_32X32 ; break;
|
||||
case DIFFMAP_BLOCKSIZE_64X64 : params.eDiffMapBlockSize = NVFBC_TOSYS_DIFFMAP_BLOCKSIZE_64X64 ; break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Invalid diffMapBlockSize");
|
||||
return false;
|
||||
}
|
||||
|
||||
params.ppBuffer = frameBuffer;
|
||||
params.ppDiffMap = diffMap;
|
||||
|
||||
NVFBCRESULT status = handle->nvfbc->NvFBCToSysSetUp(¶ms);
|
||||
if (status != NVFBC_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup NVFBCToSys");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cursorEvent)
|
||||
*cursorEvent = params.hCursorCaptureEvent;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CaptureResult NvFBCToSysCapture(
|
||||
NvFBCHandle handle,
|
||||
const unsigned int waitTime,
|
||||
const unsigned int x,
|
||||
const unsigned int y,
|
||||
const unsigned int width,
|
||||
const unsigned int height,
|
||||
NvFBCFrameGrabInfo * grabInfo
|
||||
)
|
||||
{
|
||||
NVFBC_TOSYS_GRAB_FRAME_PARAMS params = {0};
|
||||
|
||||
params.dwVersion = NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER;
|
||||
params.dwFlags = NVFBC_TOSYS_WAIT_WITH_TIMEOUT;
|
||||
params.dwWaitTime = waitTime;
|
||||
params.eGMode = NVFBC_TOSYS_SOURCEMODE_CROP;
|
||||
params.dwStartX = x;
|
||||
params.dwStartY = y;
|
||||
params.dwTargetWidth = width;
|
||||
params.dwTargetHeight = height;
|
||||
params.pNvFBCFrameGrabInfo = grabInfo;
|
||||
|
||||
grabInfo->bMustRecreate = FALSE;
|
||||
NVFBCRESULT status = handle->nvfbc->NvFBCToSysGrabFrame(¶ms);
|
||||
if (grabInfo->bMustRecreate)
|
||||
{
|
||||
DEBUG_INFO("NvFBC reported recreation is required");
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
}
|
||||
|
||||
switch(status)
|
||||
{
|
||||
case NVFBC_SUCCESS:
|
||||
handle->retry = 0;
|
||||
break;
|
||||
|
||||
case NVFBC_ERROR_INVALID_PARAM:
|
||||
if (handle->retry < 2)
|
||||
{
|
||||
Sleep(100);
|
||||
++handle->retry;
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
}
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
|
||||
case NVFBC_ERROR_DYNAMIC_DISABLE:
|
||||
DEBUG_ERROR("NvFBC was disabled by someone else");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
|
||||
case NVFBC_ERROR_INVALIDATED_SESSION:
|
||||
DEBUG_WARN("Session was invalidated, attempting to restart");
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown NVFBCRESULT failure 0x%x", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer, void * buffer, unsigned int size)
|
||||
{
|
||||
NVFBC_CURSOR_CAPTURE_PARAMS params;
|
||||
params.dwVersion = NVFBC_CURSOR_CAPTURE_PARAMS_VER;
|
||||
|
||||
if (handle->nvfbc->NvFBCToSysCursorCapture(¶ms) != NVFBC_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the cursor");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
pointer->x = params.dwXHotSpot;
|
||||
pointer->y = params.dwYHotSpot;
|
||||
pointer->width = params.dwWidth;
|
||||
pointer->height = params.dwHeight;
|
||||
pointer->pitch = params.dwPitch;
|
||||
pointer->visible = params.bIsHwCursor;
|
||||
pointer->shapeUpdate = params.bIsHwCursor;
|
||||
|
||||
if (!params.bIsHwCursor)
|
||||
return CAPTURE_RESULT_OK;
|
||||
|
||||
switch(params.dwPointerFlags & 0x7)
|
||||
{
|
||||
case 0x1:
|
||||
pointer->format = CAPTURE_FMT_MONO;
|
||||
pointer->height *= 2;
|
||||
break;
|
||||
|
||||
case 0x2:
|
||||
pointer->format = CAPTURE_FMT_COLOR;
|
||||
break;
|
||||
|
||||
case 0x4:
|
||||
pointer->format = CAPTURE_FMT_MASKED;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Invalid/unknown pointer data format");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (params.dwBufferSize > size)
|
||||
{
|
||||
DEBUG_WARN("Cursor data larger then provided buffer");
|
||||
params.dwBufferSize = size;
|
||||
}
|
||||
|
||||
memcpy(buffer, params.pBits, params.dwBufferSize);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
88
c-host/platform/Windows/capture/NVFBC/src/wrapper.h
Normal file
88
c-host/platform/Windows/capture/NVFBC/src/wrapper.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
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>
|
||||
#include <NvFBC/nvFBC.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "interface/capture.h"
|
||||
|
||||
typedef struct stNvFBCHandle * NvFBCHandle;
|
||||
|
||||
enum BufferFormat
|
||||
{
|
||||
BUFFER_FMT_ARGB,
|
||||
BUFFER_FMT_RGB,
|
||||
BUFFER_FMT_YYYYUV420p,
|
||||
BUFFER_FMT_RGB_PLANAR,
|
||||
BUFFER_FMT_XOR,
|
||||
BUFFER_FMT_YUV444p,
|
||||
BUFFER_FMT_ARGB10
|
||||
};
|
||||
|
||||
enum DiffMapBlockSize
|
||||
{
|
||||
DIFFMAP_BLOCKSIZE_128X128 = 0,
|
||||
DIFFMAP_BLOCKSIZE_16X16,
|
||||
DIFFMAP_BLOCKSIZE_32X32,
|
||||
DIFFMAP_BLOCKSIZE_64X64
|
||||
};
|
||||
|
||||
bool NvFBCInit();
|
||||
void NvFBCFree();
|
||||
|
||||
bool NvFBCToSysCreate(
|
||||
void * privData,
|
||||
unsigned int privDataSize,
|
||||
NvFBCHandle * handle,
|
||||
unsigned int * maxWidth,
|
||||
unsigned int * maxHeight
|
||||
);
|
||||
void NvFBCToSysRelease(NvFBCHandle * handle);
|
||||
|
||||
bool NvFBCToSysSetup(
|
||||
NvFBCHandle handle,
|
||||
enum BufferFormat format,
|
||||
bool hwCursor,
|
||||
bool seperateCursorCapture,
|
||||
bool useDiffMap,
|
||||
enum DiffMapBlockSize diffMapBlockSize,
|
||||
void ** frameBuffer,
|
||||
void ** diffMap,
|
||||
HANDLE * cursorEvent
|
||||
);
|
||||
|
||||
CaptureResult NvFBCToSysCapture(
|
||||
NvFBCHandle handle,
|
||||
const unsigned int waitTime,
|
||||
const unsigned int x,
|
||||
const unsigned int y,
|
||||
const unsigned int width,
|
||||
const unsigned int height,
|
||||
NvFBCFrameGrabInfo * grabInfo
|
||||
);
|
||||
|
||||
CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer, void * buffer, unsigned int size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
35
c-host/platform/Windows/include/windows/debug.h
Normal file
35
c-host/platform/Windows/include/windows/debug.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 "common/debug.h"
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status);
|
||||
|
||||
#define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
23
c-host/platform/Windows/include/windows/mousehook.h
Normal file
23
c-host/platform/Windows/include/windows/mousehook.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
typedef void (*MouseHookFn)(int x, int y);
|
||||
|
||||
void mouseHook_install(MouseHookFn callback);
|
||||
void mouseHook_remove();
|
||||
23
c-host/platform/Windows/include/windows/platform.h
Normal file
23
c-host/platform/Windows/include/windows/platform.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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/platform.h"
|
||||
#include <windows.h>
|
||||
|
||||
osEventHandle * os_wrapEvent(HANDLE event);
|
||||
4
c-host/platform/Windows/resource.rc
Normal file
4
c-host/platform/Windows/resource.rc
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "winuser.h"
|
||||
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "app.manifest"
|
||||
IDI_APPLICATION ICON "../../../resources/icon.ico"
|
||||
98
c-host/platform/Windows/src/mousehook.c
Normal file
98
c-host/platform/Windows/src/mousehook.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "windows/mousehook.h"
|
||||
#include "windows/debug.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mouseHook
|
||||
{
|
||||
bool installed;
|
||||
HHOOK hook;
|
||||
MouseHookFn callback;
|
||||
};
|
||||
|
||||
static struct mouseHook mouseHook = { 0 };
|
||||
|
||||
// forwards
|
||||
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
void mouseHook_install(MouseHookFn callback)
|
||||
{
|
||||
struct MSG_CALL_FUNCTION cf;
|
||||
cf.fn = msg_callback;
|
||||
cf.wParam = 1;
|
||||
cf.lParam = (LPARAM)callback;
|
||||
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
|
||||
}
|
||||
|
||||
void mouseHook_remove()
|
||||
{
|
||||
struct MSG_CALL_FUNCTION cf;
|
||||
cf.fn = msg_callback;
|
||||
cf.wParam = 0;
|
||||
cf.lParam = 0;
|
||||
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
|
||||
}
|
||||
|
||||
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (wParam)
|
||||
{
|
||||
if (mouseHook.installed)
|
||||
{
|
||||
DEBUG_WARN("Mouse hook already installed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mouseHook.hook = SetWindowsHookEx(WH_MOUSE_LL, mouseHook_hook, NULL, 0);
|
||||
if (!mouseHook.hook)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to install the mouse hook", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
mouseHook.installed = true;
|
||||
mouseHook.callback = (MouseHookFn)lParam;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mouseHook.installed)
|
||||
return 0;
|
||||
|
||||
UnhookWindowsHookEx(mouseHook.hook);
|
||||
mouseHook.installed = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE)
|
||||
{
|
||||
MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam;
|
||||
mouseHook.callback(msg->pt.x, msg->pt.y);
|
||||
}
|
||||
return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam);
|
||||
}
|
||||
556
c-host/platform/Windows/src/platform.c
Normal file
556
c-host/platform/Windows/src/platform.c
Normal file
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
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 "platform.h"
|
||||
#include "windows/platform.h"
|
||||
#include "windows/mousehook.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <setupapi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include "interface/platform.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "windows/debug.h"
|
||||
#include "ivshmem.h"
|
||||
|
||||
#define ID_MENU_OPEN_LOG 3000
|
||||
#define ID_MENU_EXIT 3001
|
||||
|
||||
struct AppState
|
||||
{
|
||||
HINSTANCE hInst;
|
||||
|
||||
int argc;
|
||||
char ** argv;
|
||||
|
||||
char executable[MAX_PATH + 1];
|
||||
HANDLE shmemHandle;
|
||||
bool shmemOwned;
|
||||
IVSHMEM_MMAP shmemMap;
|
||||
HWND messageWnd;
|
||||
HMENU trayMenu;
|
||||
};
|
||||
|
||||
static struct AppState app =
|
||||
{
|
||||
.shmemHandle = INVALID_HANDLE_VALUE,
|
||||
.shmemOwned = false,
|
||||
.shmemMap = {0}
|
||||
};
|
||||
|
||||
struct osThreadHandle
|
||||
{
|
||||
const char * name;
|
||||
osThreadFunction function;
|
||||
void * opaque;
|
||||
HANDLE handle;
|
||||
DWORD threadID;
|
||||
|
||||
int resultCode;
|
||||
};
|
||||
|
||||
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch(msg)
|
||||
{
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
|
||||
case WM_CALL_FUNCTION:
|
||||
{
|
||||
struct MSG_CALL_FUNCTION * cf = (struct MSG_CALL_FUNCTION *)lParam;
|
||||
return cf->fn(cf->wParam, cf->lParam);
|
||||
}
|
||||
|
||||
case WM_TRAYICON:
|
||||
{
|
||||
if (lParam == WM_RBUTTONDOWN)
|
||||
{
|
||||
POINT curPoint;
|
||||
GetCursorPos(&curPoint);
|
||||
SetForegroundWindow(hwnd);
|
||||
UINT clicked = TrackPopupMenu(
|
||||
app.trayMenu,
|
||||
TPM_RETURNCMD | TPM_NONOTIFY,
|
||||
curPoint.x,
|
||||
curPoint.y,
|
||||
0,
|
||||
hwnd,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (clicked == ID_MENU_EXIT ) app_quit();
|
||||
else if (clicked == ID_MENU_OPEN_LOG)
|
||||
{
|
||||
const char * logFile = option_get_string("os", "logFile");
|
||||
if (strcmp(logFile, "stderr") == 0)
|
||||
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
|
||||
else
|
||||
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int appThread(void * opaque)
|
||||
{
|
||||
// register our TrayIcon
|
||||
NOTIFYICONDATA iconData =
|
||||
{
|
||||
.cbSize = sizeof(NOTIFYICONDATA),
|
||||
.hWnd = app.messageWnd,
|
||||
.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP,
|
||||
.uCallbackMessage = WM_TRAYICON,
|
||||
.szTip = "Looking Glass (host)"
|
||||
};
|
||||
iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
|
||||
Shell_NotifyIcon(NIM_ADD, &iconData);
|
||||
|
||||
int result = app_main(app.argc, app.argv);
|
||||
|
||||
Shell_NotifyIcon(NIM_DELETE, &iconData);
|
||||
mouseHook_remove();
|
||||
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
return SendMessage(app.messageWnd, Msg, wParam, lParam);
|
||||
}
|
||||
|
||||
static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
|
||||
{
|
||||
if (dwCtrlType == CTRL_C_EVENT)
|
||||
{
|
||||
SendMessage(app.messageWnd, WM_CLOSE, 0, 0);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||
{
|
||||
int result = 0;
|
||||
app.hInst = hInstance;
|
||||
|
||||
char tempPath[MAX_PATH+1];
|
||||
GetTempPathA(sizeof(tempPath), tempPath);
|
||||
int len = snprintf(NULL, 0, "%slooking-glass-host.txt", tempPath);
|
||||
char * logFilePath = malloc(len + 1);
|
||||
sprintf(logFilePath, "%slooking-glass-host.txt", tempPath);
|
||||
|
||||
struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "os",
|
||||
.name = "shmDevice",
|
||||
.description = "The IVSHMEM device to use",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0
|
||||
},
|
||||
{
|
||||
.module = "os",
|
||||
.name = "logFile",
|
||||
.description = "The log file to write to",
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = logFilePath
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
free(logFilePath);
|
||||
|
||||
// convert the command line to the standard argc and argv
|
||||
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
|
||||
app.argv = malloc(sizeof(char *) * app.argc);
|
||||
for(int i = 0; i < app.argc; ++i)
|
||||
{
|
||||
const size_t s = (wcslen(wargv[i])+1) * 2;
|
||||
app.argv[i] = malloc(s);
|
||||
wcstombs(app.argv[i], wargv[i], s);
|
||||
}
|
||||
LocalFree(wargv);
|
||||
|
||||
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
|
||||
|
||||
// setup a handler for ctrl+c
|
||||
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
||||
|
||||
// create a message window so that our message pump works
|
||||
WNDCLASSEX wx = {};
|
||||
wx.cbSize = sizeof(WNDCLASSEX);
|
||||
wx.lpfnWndProc = DummyWndProc;
|
||||
wx.hInstance = hInstance;
|
||||
wx.lpszClassName = "DUMMY_CLASS";
|
||||
wx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
|
||||
if (!RegisterClassEx(&wx))
|
||||
{
|
||||
DEBUG_ERROR("Failed to register message window class");
|
||||
result = -1;
|
||||
goto finish;
|
||||
}
|
||||
app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
|
||||
|
||||
app.trayMenu = CreatePopupMenu();
|
||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
|
||||
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
||||
|
||||
// create the application thread
|
||||
osThreadHandle * thread;
|
||||
if (!os_createThread("appThread", appThread, NULL, &thread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the main application thread");
|
||||
result = -1;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
MSG msg;
|
||||
BOOL bRet = GetMessage(&msg, NULL, 0, 0);
|
||||
if (bRet > 0)
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
continue;
|
||||
}
|
||||
else if (bRet < 0)
|
||||
{
|
||||
DEBUG_ERROR("Unknown error from GetMessage");
|
||||
result = -1;
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
shutdown:
|
||||
DestroyMenu(app.trayMenu);
|
||||
app_quit();
|
||||
if (!os_joinThread(thread, &result))
|
||||
{
|
||||
DEBUG_ERROR("Failed to join the main application thread");
|
||||
result = -1;
|
||||
}
|
||||
|
||||
finish:
|
||||
os_shmemUnmap();
|
||||
|
||||
if (app.shmemHandle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(app.shmemHandle);
|
||||
|
||||
for(int i = 0; i < app.argc; ++i)
|
||||
free(app.argv[i]);
|
||||
free(app.argv);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool app_init()
|
||||
{
|
||||
const int shmDevice = option_get_int ("os", "shmDevice");
|
||||
const char * logFile = option_get_string("os", "logFile" );
|
||||
|
||||
// redirect stderr to a file
|
||||
if (logFile && strcmp(logFile, "stderr") != 0)
|
||||
freopen(logFile, "a", stderr);
|
||||
|
||||
// always flush stderr
|
||||
setbuf(stderr, NULL);
|
||||
|
||||
HDEVINFO deviceInfoSet;
|
||||
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
|
||||
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
|
||||
|
||||
deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
|
||||
memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
|
||||
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &deviceInterfaceData) == FALSE)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_NO_MORE_ITEMS)
|
||||
{
|
||||
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD reqSize = 0;
|
||||
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL);
|
||||
if (!reqSize)
|
||||
{
|
||||
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
|
||||
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL))
|
||||
{
|
||||
free(infData);
|
||||
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
|
||||
if (app.shmemHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
free(infData);
|
||||
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
free(infData);
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char * os_getExecutable()
|
||||
{
|
||||
return app.executable;
|
||||
}
|
||||
|
||||
unsigned int os_shmemSize()
|
||||
{
|
||||
IVSHMEM_SIZE size;
|
||||
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
|
||||
{
|
||||
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (unsigned int)size;
|
||||
}
|
||||
|
||||
bool os_shmemMmap(void **ptr)
|
||||
{
|
||||
if (app.shmemOwned)
|
||||
{
|
||||
*ptr = app.shmemMap.ptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
IVSHMEM_MMAP_CONFIG config =
|
||||
{
|
||||
.cacheMode = IVSHMEM_CACHE_WRITECOMBINED
|
||||
};
|
||||
|
||||
memset(&app.shmemMap, 0, sizeof(IVSHMEM_MMAP));
|
||||
if (!DeviceIoControl(
|
||||
app.shmemHandle,
|
||||
IOCTL_IVSHMEM_REQUEST_MMAP,
|
||||
&config, sizeof(IVSHMEM_MMAP_CONFIG),
|
||||
&app.shmemMap, sizeof(IVSHMEM_MMAP),
|
||||
NULL, NULL))
|
||||
{
|
||||
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
*ptr = app.shmemMap.ptr;
|
||||
app.shmemOwned = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void os_shmemUnmap()
|
||||
{
|
||||
if (!app.shmemOwned)
|
||||
return;
|
||||
|
||||
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
|
||||
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
|
||||
else
|
||||
app.shmemOwned = false;
|
||||
}
|
||||
|
||||
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
|
||||
{
|
||||
osThreadHandle * handle = (osThreadHandle *)lpParameter;
|
||||
handle->resultCode = handle->function(handle->opaque);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
|
||||
{
|
||||
*handle = (osThreadHandle *)malloc(sizeof(osThreadHandle));
|
||||
(*handle)->name = name;
|
||||
(*handle)->function = function;
|
||||
(*handle)->opaque = opaque;
|
||||
(*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID);
|
||||
|
||||
if (!(*handle)->handle)
|
||||
{
|
||||
free(*handle);
|
||||
*handle = NULL;
|
||||
DEBUG_WINERROR("CreateThread failed", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool os_joinThread(osThreadHandle * handle, int * resultCode)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
switch(WaitForSingleObject(handle->handle, INFINITE))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
if (resultCode)
|
||||
*resultCode = handle->resultCode;
|
||||
CloseHandle(handle->handle);
|
||||
free(handle);
|
||||
return true;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
case WAIT_TIMEOUT:
|
||||
continue;
|
||||
|
||||
case WAIT_FAILED:
|
||||
DEBUG_WINERROR("Wait for thread failed", GetLastError());
|
||||
CloseHandle(handle->handle);
|
||||
free(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
osEventHandle * os_createEvent(bool autoReset)
|
||||
{
|
||||
HANDLE event = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
|
||||
if (!event)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to create the event", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (osEventHandle*)event;
|
||||
}
|
||||
|
||||
osEventHandle * os_wrapEvent(HANDLE event)
|
||||
{
|
||||
return (osEventHandle*)event;
|
||||
}
|
||||
|
||||
void os_freeEvent(osEventHandle * handle)
|
||||
{
|
||||
CloseHandle((HANDLE)handle);
|
||||
}
|
||||
|
||||
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
|
||||
{
|
||||
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
|
||||
while(true)
|
||||
{
|
||||
switch(WaitForSingleObject((HANDLE)handle, to))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
continue;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
if (timeout == TIMEOUT_INFINITE)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
|
||||
case WAIT_FAILED:
|
||||
DEBUG_WINERROR("Wait for event failed", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_ERROR("Unknown wait event return code");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool os_waitEvents(osEventHandle * handles[], int count, bool waitAll, unsigned int timeout)
|
||||
{
|
||||
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
|
||||
while(true)
|
||||
{
|
||||
DWORD result = WaitForMultipleObjects(count, (HANDLE*)handles, waitAll, to);
|
||||
if (result >= WAIT_OBJECT_0 && result < count)
|
||||
{
|
||||
// null non signalled events from the handle list
|
||||
for(int i = 0; i < count; ++i)
|
||||
if (i != result && !os_waitEvent(handles[i], 0))
|
||||
handles[i] = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
|
||||
continue;
|
||||
|
||||
switch(result)
|
||||
{
|
||||
case WAIT_TIMEOUT:
|
||||
if (timeout == TIMEOUT_INFINITE)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
|
||||
case WAIT_FAILED:
|
||||
DEBUG_WINERROR("Wait for event failed", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_ERROR("Unknown wait event return code");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool os_signalEvent(osEventHandle * handle)
|
||||
{
|
||||
return SetEvent((HANDLE)handle);
|
||||
}
|
||||
|
||||
bool os_resetEvent(osEventHandle * handle)
|
||||
{
|
||||
return ResetEvent((HANDLE)handle);
|
||||
}
|
||||
33
c-host/platform/Windows/src/platform.h
Normal file
33
c-host/platform/Windows/src/platform.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
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define WM_CALL_FUNCTION (WM_USER+1)
|
||||
#define WM_TRAYICON (WM_USER+2)
|
||||
|
||||
typedef LRESULT (*CallFunction)(WPARAM wParam, LPARAM lParam);
|
||||
struct MSG_CALL_FUNCTION
|
||||
{
|
||||
CallFunction fn;
|
||||
WPARAM wParam;
|
||||
LPARAM lParam;
|
||||
};
|
||||
|
||||
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
|
||||
42
c-host/platform/Windows/src/windebug.c
Normal file
42
c-host/platform/Windows/src/windebug.c
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "windows/debug.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status)
|
||||
{
|
||||
char *buffer;
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
||||
NULL,
|
||||
status,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char*)&buffer,
|
||||
1024,
|
||||
NULL
|
||||
);
|
||||
|
||||
for(size_t i = strlen(buffer) - 1; i > 0; --i)
|
||||
if (buffer[i] == '\n' || buffer[i] == '\r')
|
||||
buffer[i] = 0;
|
||||
|
||||
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
|
||||
LocalFree(buffer);
|
||||
}
|
||||
475
c-host/src/app.c
Normal file
475
c-host/src/app.c
Normal file
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
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/platform.h"
|
||||
#include "interface/capture.h"
|
||||
#include "dynamic/capture.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/crash.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
|
||||
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
|
||||
#define MAX_FRAMES 2
|
||||
|
||||
struct app
|
||||
{
|
||||
unsigned int clientInstance;
|
||||
|
||||
KVMFRHeader * shmHeader;
|
||||
uint8_t * pointerData;
|
||||
unsigned int pointerDataSize;
|
||||
unsigned int pointerOffset;
|
||||
|
||||
CaptureInterface * iface;
|
||||
|
||||
uint8_t * frames;
|
||||
unsigned int frameSize;
|
||||
FrameBuffer frame[MAX_FRAMES];
|
||||
unsigned int frameOffset[MAX_FRAMES];
|
||||
|
||||
bool running;
|
||||
bool reinit;
|
||||
osThreadHandle * pointerThread;
|
||||
osThreadHandle * frameThread;
|
||||
};
|
||||
|
||||
static struct app app;
|
||||
|
||||
static int pointerThread(void * opaque)
|
||||
{
|
||||
DEBUG_INFO("Pointer thread started");
|
||||
|
||||
volatile KVMFRCursor * ci = &(app.shmHeader->cursor);
|
||||
|
||||
uint8_t flags;
|
||||
bool pointerValid = false;
|
||||
bool shapeValid = false;
|
||||
unsigned int clientInstance = 0;
|
||||
CapturePointer pointer = { 0 };
|
||||
|
||||
while(app.running)
|
||||
{
|
||||
bool resend = false;
|
||||
|
||||
pointer.shapeUpdate = false;
|
||||
|
||||
switch(app.iface->getPointer(&pointer))
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
{
|
||||
pointerValid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
{
|
||||
app.reinit = true;
|
||||
DEBUG_INFO("Pointer thread reinit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the pointer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
{
|
||||
// if the pointer is valid and the client has restarted, send it
|
||||
if (pointerValid && clientInstance != app.clientInstance)
|
||||
{
|
||||
resend = true;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
clientInstance = app.clientInstance;
|
||||
|
||||
// wait for the client to finish with the previous update
|
||||
while((ci->flags & ~KVMFR_CURSOR_FLAG_UPDATE) != 0 && app.running)
|
||||
usleep(1000);
|
||||
|
||||
flags = KVMFR_CURSOR_FLAG_UPDATE;
|
||||
ci->x = pointer.x;
|
||||
ci->y = pointer.y;
|
||||
flags |= KVMFR_CURSOR_FLAG_POS;
|
||||
if (pointer.visible)
|
||||
flags |= KVMFR_CURSOR_FLAG_VISIBLE;
|
||||
|
||||
// if we have shape data
|
||||
if (pointer.shapeUpdate || (shapeValid && resend))
|
||||
{
|
||||
switch(pointer.format)
|
||||
{
|
||||
case CAPTURE_FMT_COLOR : ci->type = CURSOR_TYPE_COLOR ; break;
|
||||
case CAPTURE_FMT_MONO : ci->type = CURSOR_TYPE_MONOCHROME ; break;
|
||||
case CAPTURE_FMT_MASKED: ci->type = CURSOR_TYPE_MASKED_COLOR; break;
|
||||
default:
|
||||
DEBUG_ERROR("Invalid pointer format: %d", pointer.format);
|
||||
continue;
|
||||
}
|
||||
|
||||
ci->width = pointer.width;
|
||||
ci->height = pointer.height;
|
||||
ci->pitch = pointer.pitch;
|
||||
ci->dataPos = app.pointerOffset;
|
||||
++ci->version;
|
||||
shapeValid = true;
|
||||
flags |= KVMFR_CURSOR_FLAG_SHAPE;
|
||||
}
|
||||
|
||||
// update the flags for the client
|
||||
ci->flags = flags;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Pointer thread stopped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int frameThread(void * opaque)
|
||||
{
|
||||
DEBUG_INFO("Frame thread started");
|
||||
|
||||
volatile KVMFRFrame * fi = &(app.shmHeader->frame);
|
||||
|
||||
bool frameValid = false;
|
||||
int frameIndex = 0;
|
||||
unsigned int clientInstance = 0;
|
||||
CaptureFrame frame = { 0 };
|
||||
|
||||
while(app.running)
|
||||
{
|
||||
switch(app.iface->waitFrame(&frame))
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
break;
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
{
|
||||
app.reinit = true;
|
||||
DEBUG_INFO("Frame thread reinit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
{
|
||||
DEBUG_ERROR("Failed to get the frame");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
{
|
||||
if (frameValid && clientInstance != app.clientInstance)
|
||||
{
|
||||
// resend the last frame
|
||||
if (--frameIndex < 0)
|
||||
frameIndex = MAX_FRAMES - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
clientInstance = app.clientInstance;
|
||||
|
||||
// wait for the client to finish with the previous frame
|
||||
while(fi->flags & KVMFR_FRAME_FLAG_UPDATE && app.running)
|
||||
usleep(1000);
|
||||
|
||||
switch(frame.format)
|
||||
{
|
||||
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
|
||||
case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break;
|
||||
case CAPTURE_FMT_RGBA10: fi->type = FRAME_TYPE_RGBA10; break;
|
||||
case CAPTURE_FMT_YUV420: fi->type = FRAME_TYPE_YUV420; break;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format);
|
||||
continue;
|
||||
}
|
||||
|
||||
fi->width = frame.width;
|
||||
fi->height = frame.height;
|
||||
fi->stride = frame.stride;
|
||||
fi->pitch = frame.pitch;
|
||||
fi->dataPos = app.frameOffset[frameIndex];
|
||||
frameValid = true;
|
||||
|
||||
framebuffer_prepare(app.frame[frameIndex]);
|
||||
INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
|
||||
app.iface->getFrame(app.frame[frameIndex]);
|
||||
|
||||
if (++frameIndex == MAX_FRAMES)
|
||||
frameIndex = 0;
|
||||
}
|
||||
DEBUG_INFO("Frame thread stopped");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool startThreads()
|
||||
{
|
||||
app.running = true;
|
||||
if (!os_createThread("CursorThread", pointerThread, NULL, &app.pointerThread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the pointer thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!os_createThread("FrameThread", frameThread, NULL, &app.frameThread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool stopThreads()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
app.running = false;
|
||||
app.iface->stop();
|
||||
|
||||
if (app.frameThread && !os_joinThread(app.frameThread, NULL))
|
||||
{
|
||||
DEBUG_WARN("Failed to join the frame thread");
|
||||
ok = false;
|
||||
}
|
||||
app.frameThread = NULL;
|
||||
|
||||
if (app.pointerThread && !os_joinThread(app.pointerThread, NULL))
|
||||
{
|
||||
DEBUG_WARN("Failed to join the pointer thread");
|
||||
ok = false;
|
||||
}
|
||||
app.pointerThread = NULL;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool captureStart()
|
||||
{
|
||||
DEBUG_INFO("Using : %s", app.iface->getName());
|
||||
|
||||
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
|
||||
if (maxFrameSize > app.frameSize)
|
||||
{
|
||||
DEBUG_ERROR("Maximum frame size of %d bytes excceds maximum space available", maxFrameSize);
|
||||
return false;
|
||||
}
|
||||
DEBUG_INFO("Capture Size : %u MiB (%u)", maxFrameSize / 1048576, maxFrameSize);
|
||||
|
||||
DEBUG_INFO("==== [ Capture Start ] ====");
|
||||
return startThreads();
|
||||
}
|
||||
|
||||
static bool captureRestart()
|
||||
{
|
||||
DEBUG_INFO("==== [ Capture Restart ] ====");
|
||||
if (!stopThreads())
|
||||
return false;
|
||||
|
||||
if (!app.iface->deinit() || !app.iface->init(app.pointerData, app.pointerDataSize))
|
||||
{
|
||||
DEBUG_ERROR("Failed to reinitialize the capture device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!captureStart())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// this is called from the platform specific startup routine
|
||||
int app_main(int argc, char * argv[])
|
||||
{
|
||||
if (!installCrashHandler(os_getExecutable()))
|
||||
DEBUG_WARN("Failed to install the crash handler");
|
||||
|
||||
// register capture interface options
|
||||
for(int i = 0; CaptureInterfaces[i]; ++i)
|
||||
if (CaptureInterfaces[i]->initOptions)
|
||||
CaptureInterfaces[i]->initOptions();
|
||||
|
||||
// try load values from a config file
|
||||
option_load("looking-glass-host.ini");
|
||||
|
||||
// parse the command line arguments
|
||||
if (!option_parse(argc, argv))
|
||||
{
|
||||
option_free();
|
||||
DEBUG_ERROR("Failure to parse the command line");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!option_validate())
|
||||
{
|
||||
option_free();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// perform platform specific initialization
|
||||
if (!app_init())
|
||||
return -1;
|
||||
|
||||
unsigned int shmemSize = os_shmemSize();
|
||||
uint8_t * shmemMap = NULL;
|
||||
int exitcode = 0;
|
||||
|
||||
DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")");
|
||||
DEBUG_INFO("IVSHMEM Size : %u MiB", shmemSize / 1048576);
|
||||
if (!os_shmemMmap((void **)&shmemMap) || !shmemMap)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map the shared memory");
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmemMap);
|
||||
|
||||
app.shmHeader = (KVMFRHeader *)shmemMap;
|
||||
app.pointerData = (uint8_t *)ALIGN_UP(shmemMap + sizeof(KVMFRHeader));
|
||||
app.pointerDataSize = 1048576; // 1MB fixed for pointer size, should be more then enough
|
||||
app.pointerOffset = app.pointerData - shmemMap;
|
||||
app.frames = (uint8_t *)ALIGN_UP(app.pointerData + app.pointerDataSize);
|
||||
app.frameSize = ALIGN_DN((shmemSize - (app.frames - shmemMap)) / MAX_FRAMES);
|
||||
|
||||
DEBUG_INFO("Max Cursor Size : %u MiB", app.pointerDataSize / 1048576);
|
||||
DEBUG_INFO("Max Frame Size : %u MiB", app.frameSize / 1048576);
|
||||
DEBUG_INFO("Cursor : 0x%" PRIXPTR " (0x%08x)", (uintptr_t)app.pointerData, app.pointerOffset);
|
||||
|
||||
for (int i = 0; i < MAX_FRAMES; ++i)
|
||||
{
|
||||
app.frame [i] = (FrameBuffer)(app.frames + i * app.frameSize);
|
||||
app.frameOffset[i] = (uint8_t *)app.frame[i] - shmemMap;
|
||||
DEBUG_INFO("Frame %d : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]);
|
||||
}
|
||||
|
||||
CaptureInterface * iface = NULL;
|
||||
for(int i = 0; CaptureInterfaces[i]; ++i)
|
||||
{
|
||||
iface = CaptureInterfaces[i];
|
||||
DEBUG_INFO("Trying : %s", iface->getName());
|
||||
|
||||
if (!iface->create())
|
||||
{
|
||||
iface = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iface->init(app.pointerData, app.pointerDataSize))
|
||||
break;
|
||||
|
||||
iface->free();
|
||||
iface = NULL;
|
||||
}
|
||||
|
||||
if (!iface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to find a supported capture interface");
|
||||
exitcode = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
app.iface = iface;
|
||||
|
||||
// initialize the shared memory headers
|
||||
memcpy(app.shmHeader->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC));
|
||||
app.shmHeader->version = KVMFR_HEADER_VERSION;
|
||||
|
||||
// zero and notify the client we are starting
|
||||
memset(&(app.shmHeader->frame ), 0, sizeof(KVMFRFrame ));
|
||||
memset(&(app.shmHeader->cursor), 0, sizeof(KVMFRCursor));
|
||||
app.shmHeader->flags &= ~KVMFR_HEADER_FLAG_RESTART;
|
||||
|
||||
if (!captureStart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
volatile char * flags = (volatile char *)&(app.shmHeader->flags);
|
||||
|
||||
while(app.running)
|
||||
{
|
||||
if (INTERLOCKED_AND8(flags, ~(KVMFR_HEADER_FLAG_RESTART)) & KVMFR_HEADER_FLAG_RESTART)
|
||||
{
|
||||
DEBUG_INFO("Client restarted");
|
||||
++app.clientInstance;
|
||||
}
|
||||
|
||||
if (app.reinit && !captureRestart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
app.reinit = false;
|
||||
|
||||
switch(iface->capture())
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
break;
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
if (!captureRestart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
app.reinit = false;
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
DEBUG_ERROR("Capture interface reported a fatal error");
|
||||
exitcode = -1;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
stopThreads();
|
||||
exit:
|
||||
|
||||
iface->deinit();
|
||||
iface->free();
|
||||
fail:
|
||||
os_shmemUnmap();
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
void app_quit()
|
||||
{
|
||||
app.running = false;
|
||||
}
|
||||
17
c-host/toolchain-mingw64.cmake
Normal file
17
c-host/toolchain-mingw64.cmake
Normal file
@@ -0,0 +1,17 @@
|
||||
# the name of the target operating system
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
# which compilers to use for C and C++
|
||||
SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||
SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
|
||||
SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||
|
||||
# here is the target environment located
|
||||
SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||
|
||||
# adjust the default behaviour of the FIND_XXX() commands:
|
||||
# search headers and libraries in the target environment, search
|
||||
# programs in the host environment
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
2
client/.gitignore
vendored
2
client/.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
bin/
|
||||
.build/
|
||||
build/
|
||||
*.swp
|
||||
|
||||
10
client/.vimrc
Normal file
10
client/.vimrc
Normal file
@@ -0,0 +1,10 @@
|
||||
packadd termdebug
|
||||
|
||||
function Debug()
|
||||
!cd build && make
|
||||
if v:shell_error == 0
|
||||
TermdebugCommand build/looking-glass-client
|
||||
endif
|
||||
endfunction
|
||||
|
||||
command Debug call Debug()
|
||||
106
client/CMakeLists.txt
Normal file
106
client/CMakeLists.txt
Normal file
@@ -0,0 +1,106 @@
|
||||
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_CB_X11 "Enable X11 clipboard integration" ON)
|
||||
add_feature_info(ENABLE_CB_X11 ENABLE_CB_X11 "X11 Clipboard Integration.")
|
||||
|
||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
||||
|
||||
add_compile_options(
|
||||
"-Wall"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
"-ffast-math"
|
||||
"-fdata-sections"
|
||||
"-ffunction-sections"
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
|
||||
set(EXE_FLAGS "-Wl,--gc-sections")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PKGCONFIG REQUIRED
|
||||
sdl2
|
||||
x11
|
||||
)
|
||||
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
find_package(GMP)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
add_definitions(-D ATOMIC_LOCKING)
|
||||
add_definitions(-D GL_GLEXT_PROTOTYPES)
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PKGCONFIG_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
${PKGCONFIG_LIBRARIES}
|
||||
${GMP_LIBRARIES}
|
||||
rt
|
||||
m
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
src/main.c
|
||||
src/app.c
|
||||
src/config.c
|
||||
src/lg-renderer.c
|
||||
src/ll.c
|
||||
src/utils.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory(spice)
|
||||
add_subdirectory(renderers)
|
||||
add_subdirectory(clipboards)
|
||||
add_subdirectory(fonts)
|
||||
add_subdirectory(decoders)
|
||||
|
||||
add_executable(looking-glass-client ${SOURCES})
|
||||
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
|
||||
target_link_libraries(looking-glass-client
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
spice
|
||||
renderers
|
||||
clipboards
|
||||
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,37 +0,0 @@
|
||||
BINARY = looking-glass-client
|
||||
CFLAGS = -g -O3 -std=gnu99 -march=native -Wall -Werror -I./ -I../common -DDEBUG
|
||||
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 \
|
||||
spice/spice.o \
|
||||
ivshmem/ivshmem.o \
|
||||
renderers/opengl.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
|
||||
162
client/README.md
Normal file
162
client/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# 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
|
||||
|
||||
#### 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
|
||||
|
||||
### Building
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
|
||||
Should this all go well you should be left with the file `looking-glass-client`
|
||||
|
||||
---
|
||||
|
||||
## 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>I</kbd> | Spice keyboard & mouse enable toggle |
|
||||
| <kbd>ScrLk</kbd>+<kbd>N</kbd> | Toggle night vision mode (EGL renderer only!) |
|
||||
| <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 |
|
||||
|
||||
|
||||
|
||||
### 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:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file |
|
||||
| app:shmSize | -L | 0 | Specify the size in MB of the shared memory file (0 = detect) |
|
||||
| app:renderer | -g | auto | Specify the renderer to use |
|
||||
| app:license | -l | no | Show the license for this application and then terminate |
|
||||
| app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds |
|
||||
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| win:title | | Looking Glass (client) | The window title |
|
||||
| win:position | | center | Initial window position at startup |
|
||||
| win:size | | 1024x768 | Initial window size at startup |
|
||||
| win:autoResize | -a | no | Auto resize the window to the guest |
|
||||
| win:allowResize | -n | yes | Aallow the window to be manually resized |
|
||||
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
|
||||
| win:borderless | -d | no | Borderless mode |
|
||||
| win:fullScreen | -F | no | Launch in fullscreen borderless mode |
|
||||
| win:maximize | -T | no | Launch window maximized |
|
||||
| win:minimizeOnFocusLoss | | yes | Minimize window on focus loss |
|
||||
| win:fpsLimit | -K | 200 | Frame rate limit (0 = disable - not recommended) |
|
||||
| win:showFPS | -k | no | Enable the FPS & UPS display |
|
||||
| win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) |
|
||||
| win:noScreensaver | -S | no | Prevent the screensaver from starting |
|
||||
| win:alerts | -q | yes | Show on screen alert messages |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | yes | Grab the keyboard in capture mode |
|
||||
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| 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 |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|--------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|--------------------------------------------------------------------------|
|
||||
| egl:vsync | | no | Enable vsync |
|
||||
| egl:nvGainMax | | 1 | The maximum night vision gain |
|
||||
| egl:nvGain | | 0 | The initial night vision gain at startup |
|
||||
|--------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------|
|
||||
| opengl:mipmap | | yes | Enable mipmapping |
|
||||
| opengl:vsync | | yes | Enable vsync |
|
||||
| opengl:preventBuffer | | yes | Prevent the driver from buffering frames |
|
||||
| opengl:amdPinnedMem | | yes | Use GL_AMD_pinned_memory if it is available |
|
||||
|------------------------------------------------------------------------------------|
|
||||
```
|
||||
43
client/clipboards/CMakeLists.txt
Normal file
43
client/clipboards/CMakeLists.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboards LANGUAGES C)
|
||||
|
||||
set(CLIPBOARD_H "${CMAKE_BINARY_DIR}/include/dynamic/clipboards.h")
|
||||
set(CLIPBOARD_C "${CMAKE_BINARY_DIR}/src/clipboards.c")
|
||||
|
||||
file(WRITE ${CLIPBOARD_H} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_H} "extern LG_Clipboard * LG_Clipboards[];\n\n")
|
||||
|
||||
file(WRITE ${CLIPBOARD_C} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(CLIPBOARDS "_")
|
||||
set(CLIPBOARDS_LINK "_")
|
||||
function(add_clipboard name)
|
||||
set(CLIPBOARDS "${CLIPBOARDS};${name}" PARENT_SCOPE)
|
||||
set(CLIPBOARDS_LINK "${CLIPBOARDS_LINK};clipboard_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove clipboards here!
|
||||
if (ENABLE_CB_X11)
|
||||
add_clipboard(X11)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT CLIPBOARDS 0)
|
||||
list(REMOVE_AT CLIPBOARDS_LINK 0)
|
||||
|
||||
list(LENGTH CLIPBOARDS CLIPBOARD_COUNT)
|
||||
file(APPEND ${CLIPBOARD_H} "#define LG_CLIPBOARD_COUNT ${CLIPBOARD_COUNT}\n")
|
||||
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} "extern LG_Clipboard LGC_${clipboard};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${CLIPBOARD_C} "\nconst LG_Clipboard * LG_Clipboards[] =\n{\n")
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} " &LGC_${clipboard},\n")
|
||||
endforeach()
|
||||
file(APPEND ${CLIPBOARD_C} " NULL\n};\n\n")
|
||||
|
||||
add_library(clipboards STATIC ${CLIPBOARD_C})
|
||||
target_link_libraries(clipboards ${CLIPBOARDS_LINK})
|
||||
26
client/clipboards/X11/CMakeLists.txt
Normal file
26
client/clipboards/X11/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboard_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xfixes
|
||||
)
|
||||
|
||||
add_library(clipboard_X11 STATIC
|
||||
src/x11.c
|
||||
)
|
||||
|
||||
target_link_libraries(clipboard_X11
|
||||
${CLIPBOARD_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(clipboard_X11
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
359
client/clipboards/X11/src/x11.c
Normal file
359
client/clipboards/X11/src/x11.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/clipboard.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
struct state
|
||||
{
|
||||
Display * display;
|
||||
Window window;
|
||||
Atom aSelection;
|
||||
Atom aCurSelection;
|
||||
Atom aTargets;
|
||||
Atom aSelData;
|
||||
Atom aIncr;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_ClipboardReleaseFn releaseFn;
|
||||
LG_ClipboardRequestFn requestFn;
|
||||
LG_ClipboardNotifyFn notifyFn;
|
||||
LG_ClipboardDataFn dataFn;
|
||||
LG_ClipboardData type;
|
||||
|
||||
// XFixes vars
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
};
|
||||
|
||||
static struct state * this = NULL;
|
||||
|
||||
static const char * atomTypes[] =
|
||||
{
|
||||
"UTF8_STRING",
|
||||
"image/png",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/jpeg"
|
||||
};
|
||||
|
||||
static const char * x11_cb_getName()
|
||||
{
|
||||
return "X11";
|
||||
}
|
||||
|
||||
static bool x11_cb_init(
|
||||
SDL_SysWMinfo * wminfo,
|
||||
LG_ClipboardReleaseFn releaseFn,
|
||||
LG_ClipboardNotifyFn notifyFn,
|
||||
LG_ClipboardDataFn dataFn)
|
||||
{
|
||||
// final sanity check
|
||||
if (wminfo->subsystem != SDL_SYSWM_X11)
|
||||
{
|
||||
DEBUG_ERROR("wrong subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this = (struct state *)malloc(sizeof(struct state));
|
||||
memset(this, 0, sizeof(struct state));
|
||||
|
||||
this->display = wminfo->info.x11.display;
|
||||
this->window = wminfo->info.x11.window;
|
||||
this->aSelection = XInternAtom(this->display, "CLIPBOARD", False);
|
||||
this->aTargets = XInternAtom(this->display, "TARGETS" , False);
|
||||
this->aSelData = XInternAtom(this->display, "SEL_DATA" , False);
|
||||
this->aIncr = XInternAtom(this->display, "INCR" , False);
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn = releaseFn;
|
||||
this->notifyFn = notifyFn;
|
||||
this->dataFn = dataFn;
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
{
|
||||
this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False);
|
||||
if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue)
|
||||
{
|
||||
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we need the raw X events
|
||||
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
||||
|
||||
// use xfixes to get clipboard change notifications
|
||||
if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase))
|
||||
{
|
||||
DEBUG_ERROR("failed to initialize xfixes");
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(this->display, this->window, XA_PRIMARY , XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(this->display, this->window, this->aSelection, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_cb_free()
|
||||
{
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size)
|
||||
{
|
||||
XEvent *s = (XEvent *)opaque;
|
||||
|
||||
XChangeProperty(
|
||||
this->display ,
|
||||
s->xselection.requestor,
|
||||
s->xselection.property ,
|
||||
s->xselection.target ,
|
||||
8,
|
||||
PropModeReplace,
|
||||
data,
|
||||
size);
|
||||
|
||||
XSendEvent(this->display, s->xselection.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void x11_cb_wmevent(SDL_SysWMmsg * msg)
|
||||
{
|
||||
XEvent e = msg->msg.x11.event;
|
||||
|
||||
if (e.type == SelectionRequest)
|
||||
{
|
||||
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.xselectionrequest.requestor;
|
||||
s->xselection.selection = e.xselectionrequest.selection;
|
||||
s->xselection.target = e.xselectionrequest.target;
|
||||
s->xselection.property = e.xselectionrequest.property;
|
||||
s->xselection.time = e.xselectionrequest.time;
|
||||
|
||||
if (!this->requestFn)
|
||||
{
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// target list requested
|
||||
if (e.xselectionrequest.target == this->aTargets)
|
||||
{
|
||||
Atom targets[2];
|
||||
targets[0] = this->aTargets;
|
||||
targets[1] = this->aTypes[this->type];
|
||||
|
||||
XChangeProperty(
|
||||
e.xselectionrequest.display,
|
||||
e.xselectionrequest.requestor,
|
||||
e.xselectionrequest.property,
|
||||
XA_ATOM,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)targets,
|
||||
sizeof(targets) / sizeof(Atom));
|
||||
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == e.xselectionrequest.target && this->type == i)
|
||||
{
|
||||
// request the data
|
||||
this->requestFn(x11_cb_reply_fn, s);
|
||||
return;
|
||||
}
|
||||
|
||||
// report no data
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
if (e.type == SelectionClear && (
|
||||
e.xselectionclear.selection == XA_PRIMARY ||
|
||||
e.xselectionclear.selection == this->aSelection)
|
||||
)
|
||||
{
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// if someone selected data
|
||||
if (e.type == this->eventBase + XFixesSelectionNotify)
|
||||
{
|
||||
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e;
|
||||
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if (
|
||||
(sne->selection != XA_PRIMARY && sne->selection != this->aSelection) ||
|
||||
sne->owner == this->window ||
|
||||
sne->owner == 0
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remember which selection we are working with
|
||||
this->aCurSelection = sne->selection;
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
sne->selection,
|
||||
this->aTargets,
|
||||
this->aTargets,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type == SelectionNotify)
|
||||
{
|
||||
if (e.xselection.property == None)
|
||||
return;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
|
||||
XGetWindowProperty(
|
||||
this->display,
|
||||
this->window,
|
||||
e.xselection.property,
|
||||
0, ~0L, // start and length
|
||||
True , // delete the property
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data);
|
||||
|
||||
// the target list
|
||||
if (e.xselection.property == this->aTargets)
|
||||
{
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
{
|
||||
if (data)
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const uint64_t * targets = (const uint64_t *)data;
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
{
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
if (this->aTypes[n] == targets[i])
|
||||
{
|
||||
// we have a match, so send the notification
|
||||
this->notifyFn(n);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no matches
|
||||
this->notifyFn(LG_CLIPBOARD_DATA_NONE);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format == this->aIncr)
|
||||
{
|
||||
DEBUG_WARN("fixme: large paste buffers are not yet supported");
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == type)
|
||||
{
|
||||
this->dataFn(i, data, itemCount);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type));
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type)
|
||||
{
|
||||
this->requestFn = requestFn;
|
||||
this->type = type;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_release()
|
||||
{
|
||||
this->requestFn = NULL;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_request(LG_ClipboardData type)
|
||||
{
|
||||
if (this->aCurSelection == BadValue)
|
||||
return;
|
||||
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
this->aCurSelection,
|
||||
this->aTypes[type],
|
||||
this->aSelData,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
const LG_Clipboard LGC_X11 =
|
||||
{
|
||||
.getName = x11_cb_getName,
|
||||
.init = x11_cb_init,
|
||||
.free = x11_cb_free,
|
||||
.wmevent = x11_cb_wmevent,
|
||||
.notice = x11_cb_notice,
|
||||
.release = x11_cb_release,
|
||||
.request = x11_cb_request
|
||||
};
|
||||
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()
|
||||
25
client/decoders/CMakeLists.txt
Normal file
25
client/decoders/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(decoders LANGUAGES C)
|
||||
|
||||
#find_package(PkgConfig)
|
||||
#pkg_check_modules(DECODERS_PKGCONFIG REQUIRED
|
||||
#)
|
||||
|
||||
add_library(decoders STATIC
|
||||
src/null.c
|
||||
src/yuv420.c
|
||||
)
|
||||
|
||||
target_link_libraries(decoders
|
||||
lg_common
|
||||
${DECODERS_PKGCONFIG_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(decoders
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${DECODERS_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
981
client/decoders/src/h264.c
Normal file
981
client/decoders/src/h264.c
Normal file
@@ -0,0 +1,981 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "lg-decoder.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "parsers/nal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <va/va_glx.h>
|
||||
|
||||
#define SURFACE_NUM 3
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
SDL_Window * window;
|
||||
VADisplay vaDisplay;
|
||||
int vaMajorVer, vaMinorVer;
|
||||
VASurfaceID vaSurfaceID[SURFACE_NUM];
|
||||
VAConfigID vaConfigID;
|
||||
VAContextID vaContextID;
|
||||
int lastSID;
|
||||
int currentSID;
|
||||
VAPictureH264 curPic;
|
||||
VAPictureH264 oldPic;
|
||||
int frameNum;
|
||||
int fieldCount;
|
||||
VABufferID picBufferID[SURFACE_NUM];
|
||||
VABufferID matBufferID[SURFACE_NUM];
|
||||
VABufferID sliBufferID[SURFACE_NUM];
|
||||
VABufferID datBufferID[SURFACE_NUM];
|
||||
bool t2First;
|
||||
int sliceType;
|
||||
|
||||
NAL nal;
|
||||
};
|
||||
|
||||
static const unsigned char MatrixBufferH264[] = {
|
||||
//ScalingList4x4[6][16]
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
//ScalingList8x8[2][64]
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
};
|
||||
|
||||
static bool lgd_h264_create (void ** opaque);
|
||||
static void lgd_h264_destroy (void * opaque);
|
||||
static bool lgd_h264_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_h264_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_h264_get_out_format (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque);
|
||||
static bool lgd_h264_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static bool lgd_h264_get_buffer (void * opaque, uint8_t * dst, size_t dstSize);
|
||||
|
||||
static bool lgd_h264_init_gl_texture (void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
static void lgd_h264_free_gl_texture (void * opaque, void * ref);
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref);
|
||||
|
||||
#define check_surface(x, y, z) _check_surface(__LINE__, x, y, z)
|
||||
static bool _check_surface(const unsigned int line, struct Inst * this, unsigned int sid, VASurfaceStatus *out)
|
||||
{
|
||||
VASurfaceStatus surfStatus;
|
||||
VAStatus status = vaQuerySurfaceStatus(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[sid],
|
||||
&surfStatus
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQuerySurfaceStatus: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
DEBUG_INFO("L%d: surface %u status: %d", line, sid, surfStatus);
|
||||
#endif
|
||||
if (out)
|
||||
*out = surfStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
|
||||
if (!nal_initialize(&this->nal))
|
||||
{
|
||||
DEBUG_INFO("Failed to initialize NAL parser");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
lgd_h264_deinitialize(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_destroy(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
nal_deinitialize(this->nal);
|
||||
lgd_h264_deinitialize(this);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool lgd_h264_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
lgd_h264_deinitialize(this);
|
||||
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->window = window;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("Failed to get SDL window WM Info");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
this->vaDisplay = vaGetDisplayGLX(wminfo.info.x11.display);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported window subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAStatus status;
|
||||
status = vaInitialize(this->vaDisplay, &this->vaMajorVer, &this->vaMinorVer);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaInitialize Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Vendor: %s", vaQueryVendorString(this->vaDisplay));
|
||||
|
||||
VAEntrypoint entryPoints[5];
|
||||
int entryPointCount;
|
||||
|
||||
status = vaQueryConfigEntrypoints(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
entryPoints,
|
||||
&entryPointCount
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQueryConfigEntrypoints Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ep;
|
||||
for(ep = 0; ep < entryPointCount; ++ep)
|
||||
if (entryPoints[ep] == VAEntrypointVLD)
|
||||
break;
|
||||
|
||||
if (ep == entryPointCount)
|
||||
{
|
||||
DEBUG_ERROR("Failed to find VAEntrypointVLD index");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAConfigAttrib attrib;
|
||||
attrib.type = VAConfigAttribRTFormat;
|
||||
vaGetConfigAttributes(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1);
|
||||
|
||||
if (!(attrib.value & VA_RT_FORMAT_YUV420))
|
||||
{
|
||||
DEBUG_ERROR("Failed to find desired YUV420 RT format");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateConfig(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1,
|
||||
&this->vaConfigID);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateConfig");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateSurfaces(
|
||||
this->vaDisplay,
|
||||
VA_RT_FORMAT_YUV420,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateSurfaces");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
if (!check_surface(this, i, NULL))
|
||||
return false;
|
||||
|
||||
status = vaCreateContext(
|
||||
this->vaDisplay,
|
||||
this->vaConfigID,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
VA_PROGRESSIVE,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
&this->vaContextID
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateContext");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->currentSID = 0;
|
||||
this->sliceType = 2;
|
||||
this->t2First = true;
|
||||
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[0]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
{
|
||||
if (this->picBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->picBufferID[i]);
|
||||
|
||||
if (this->matBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->matBufferID[i]);
|
||||
|
||||
if (this->sliBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->sliBufferID[i]);
|
||||
|
||||
if (this->datBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->datBufferID[i]);
|
||||
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
}
|
||||
|
||||
if (this->vaSurfaceID[0] != VA_INVALID_ID)
|
||||
vaDestroySurfaces(this->vaDisplay, this->vaSurfaceID, SURFACE_NUM);
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
|
||||
if (this->vaContextID != VA_INVALID_ID)
|
||||
vaDestroyContext(this->vaDisplay, this->vaContextID);
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaConfigID != VA_INVALID_ID)
|
||||
vaDestroyConfig(this->vaDisplay, this->vaConfigID);
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaDisplay)
|
||||
vaTerminate(this->vaDisplay);
|
||||
this->vaDisplay = NULL;
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_h264_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_YUV420;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool get_buffer(struct Inst * this, const VABufferType type, const unsigned int size, VABufferID * buf_id)
|
||||
{
|
||||
if (*buf_id != VA_INVALID_ID)
|
||||
return true;
|
||||
|
||||
VAStatus status = vaCreateBuffer(this->vaDisplay, this->vaContextID, type, size, 1, NULL, buf_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create buffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_surface(this, this->currentSID, NULL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_pic_buffer(struct Inst * this, const NAL_SLICE * slice)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * picBufferID = &this->picBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), picBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get picBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAPictureParameterBufferH264 *p;
|
||||
status = vaMapBuffer(this->vaDisplay, *picBufferID, (void **)&p);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_SPS * sps;
|
||||
if (!nal_get_sps(this->nal, &sps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_sps");
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_PPS * pps;
|
||||
if (!nal_get_pps(this->nal, &pps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_pps");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(p, 0, sizeof(VAPictureParameterBufferH264));
|
||||
p->picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1;
|
||||
p->picture_height_in_mbs_minus1 = sps->pic_height_in_map_units_minus1;
|
||||
p->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8;
|
||||
p->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8;
|
||||
p->num_ref_frames = sps->num_ref_frames;
|
||||
|
||||
p->seq_fields.value = 0;
|
||||
p->seq_fields.bits.chroma_format_idc = sps->chroma_format_idc;
|
||||
p->seq_fields.bits.residual_colour_transform_flag = sps->gaps_in_frame_num_value_allowed_flag;
|
||||
p->seq_fields.bits.frame_mbs_only_flag = sps->frame_mbs_only_flag;
|
||||
p->seq_fields.bits.mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag;
|
||||
p->seq_fields.bits.direct_8x8_inference_flag = sps->direct_8x8_inference_flag;
|
||||
p->seq_fields.bits.MinLumaBiPredSize8x8 = sps->level_idc >= 31;
|
||||
p->seq_fields.bits.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4;
|
||||
p->seq_fields.bits.pic_order_cnt_type = sps->pic_order_cnt_type;
|
||||
p->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4;
|
||||
p->seq_fields.bits.delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag;
|
||||
|
||||
#if 0
|
||||
// these are deprecated, FMO is not supported
|
||||
p->num_slice_groups_minus1 = pps->num_slice_groups_minus1;
|
||||
p->slice_group_map_type = pps->slice_group_map_type;
|
||||
p->slice_group_change_rate_minus1 = pps->slice_group_change_rate_minus1;
|
||||
#endif
|
||||
|
||||
p->pic_init_qp_minus26 = pps->pic_init_qp_minus26;
|
||||
p->pic_init_qs_minus26 = pps->pic_init_qs_minus26;
|
||||
p->chroma_qp_index_offset = pps->chroma_qp_index_offset;
|
||||
p->second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset;
|
||||
|
||||
p->pic_fields.value = 0;
|
||||
p->pic_fields.bits.entropy_coding_mode_flag = pps->entropy_coding_mode_flag;
|
||||
p->pic_fields.bits.weighted_pred_flag = pps->weighted_pred_flag;
|
||||
p->pic_fields.bits.weighted_bipred_idc = pps->weighted_bipred_idc;
|
||||
p->pic_fields.bits.transform_8x8_mode_flag = pps->transform_8x8_mode_flag;
|
||||
p->pic_fields.bits.field_pic_flag = slice->field_pic_flag;
|
||||
p->pic_fields.bits.constrained_intra_pred_flag = pps->constrained_intra_pred_flag;
|
||||
p->pic_fields.bits.pic_order_present_flag = pps->pic_order_present_flag;
|
||||
p->pic_fields.bits.deblocking_filter_control_present_flag = pps->deblocking_filter_control_present_flag;
|
||||
p->pic_fields.bits.redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag;
|
||||
p->pic_fields.bits.reference_pic_flag = slice->nal_ref_idc != 0;
|
||||
|
||||
p->frame_num = slice->frame_num;
|
||||
|
||||
for(int i = 0; i < 16; ++i)
|
||||
{
|
||||
p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
|
||||
p->ReferenceFrames[i].picture_id = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
this->curPic.picture_id = this->vaSurfaceID[this->currentSID];
|
||||
this->curPic.frame_idx = p->frame_num;
|
||||
this->curPic.flags = 0;
|
||||
this->curPic.BottomFieldOrderCnt = this->fieldCount;
|
||||
this->curPic.TopFieldOrderCnt = this->fieldCount;
|
||||
memcpy(&p->CurrPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
if (this->sliceType != 2)
|
||||
{
|
||||
memcpy(&p->ReferenceFrames[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
p->ReferenceFrames[0].flags = 0;
|
||||
}
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *picBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_mat_buffer(struct Inst * this)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * matBufferID = &this->matBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), matBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get matBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAIQMatrixBufferH264 * m;
|
||||
status = vaMapBuffer(this->vaDisplay, *matBufferID, (void **)&m);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(m, MatrixBufferH264, sizeof(MatrixBufferH264));
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *matBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fill_pred_weight_table(
|
||||
NAL_PW_TABLE_L * list,
|
||||
uint32_t active,
|
||||
uint32_t luma_log2_weight_denom,
|
||||
uint8_t luma_weight_flag,
|
||||
short luma_weight[32],
|
||||
short luma_offset[32],
|
||||
uint32_t chroma_log2_weight_denom,
|
||||
uint8_t chroma_weight_flag,
|
||||
short chroma_weight[32][2],
|
||||
short chroma_offset[32][2]
|
||||
)
|
||||
{
|
||||
assert(active < 32);
|
||||
|
||||
for(uint32_t i = 0; i <= active; ++i)
|
||||
{
|
||||
NAL_PW_TABLE_L * l = &list[i];
|
||||
if (luma_weight_flag)
|
||||
{
|
||||
luma_weight[i] = l->luma_weight;
|
||||
luma_offset[i] = l->luma_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
luma_weight[i] = 1 << luma_log2_weight_denom;
|
||||
luma_weight[i] = 0;
|
||||
}
|
||||
|
||||
if (chroma_weight_flag)
|
||||
{
|
||||
chroma_weight[i][0] = l->chroma_weight[0];
|
||||
chroma_offset[i][0] = l->chroma_offset[0];
|
||||
chroma_weight[i][1] = l->chroma_weight[1];
|
||||
chroma_offset[i][1] = l->chroma_offset[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
chroma_weight[i][0] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][0] = 0;
|
||||
chroma_weight[i][1] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool setup_sli_buffer(struct Inst * this, size_t srcSize, const NAL_SLICE * slice, const size_t seek)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * sliBufferID = &this->sliBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), sliBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get sliBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VASliceParameterBufferH264 * s;
|
||||
status = vaMapBuffer(this->vaDisplay, *sliBufferID, (void **)&s);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(s, 0, sizeof(VASliceParameterBufferH264));
|
||||
|
||||
s->slice_data_size = srcSize;
|
||||
s->slice_data_bit_offset = seek << 3;
|
||||
s->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
|
||||
s->first_mb_in_slice = slice->first_mb_in_slice;
|
||||
s->slice_type = slice->slice_type;
|
||||
s->direct_spatial_mv_pred_flag = slice->direct_spatial_mv_pred_flag;
|
||||
s->num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1;
|
||||
s->num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1;
|
||||
s->cabac_init_idc = slice->cabac_init_idc;
|
||||
s->slice_qp_delta = slice->slice_qp_delta;
|
||||
s->disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc;
|
||||
s->slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2;
|
||||
s->slice_beta_offset_div2 = slice->slice_beta_offset_div2;
|
||||
s->luma_log2_weight_denom = slice->pred_weight_table.luma_log2_weight_denom;
|
||||
s->chroma_log2_weight_denom = slice->pred_weight_table.chroma_log2_weight_denom;
|
||||
s->luma_weight_l0_flag = slice->pred_weight_table.luma_weight_flag [0];
|
||||
s->chroma_weight_l0_flag = slice->pred_weight_table.chroma_weight_flag[0];
|
||||
s->luma_weight_l1_flag = slice->pred_weight_table.luma_weight_flag [1];
|
||||
s->chroma_weight_l1_flag = slice->pred_weight_table.chroma_weight_flag[1];
|
||||
|
||||
//RefPicList0/1
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l0,
|
||||
s->num_ref_idx_l0_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l0_flag,
|
||||
s->luma_weight_l0,
|
||||
s->luma_offset_l0,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l0_flag,
|
||||
s->chroma_weight_l0,
|
||||
s->chroma_weight_l0
|
||||
);
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l1,
|
||||
s->num_ref_idx_l1_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l1_flag,
|
||||
s->luma_weight_l1,
|
||||
s->luma_offset_l1,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l1_flag,
|
||||
s->chroma_weight_l1,
|
||||
s->chroma_weight_l1
|
||||
);
|
||||
|
||||
#if 0
|
||||
if (this->sliceType == 2)
|
||||
{
|
||||
set_slice_parameter_buffer_t2(s, this->t2First);
|
||||
this->t2First = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_slice_parameter_buffer(s);
|
||||
memcpy(&s->RefPicList0[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
s->RefPicList0[0].flags = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *sliBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_dat_buffer(struct Inst * this, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * datBufferID = &this->datBufferID[this->currentSID];
|
||||
|
||||
if (!get_buffer(this, VASliceDataBufferType, srcSize, datBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get datBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, *datBufferID, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(d, src, srcSize);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *datBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
size_t seek;
|
||||
if (!nal_parse(this->nal, src, srcSize, &seek))
|
||||
{
|
||||
DEBUG_WARN("nal_parse, perhaps mid stream");
|
||||
return true;
|
||||
}
|
||||
|
||||
const NAL_SLICE * slice;
|
||||
if (!nal_get_slice(this->nal, &slice))
|
||||
{
|
||||
DEBUG_WARN("nal_get_slice failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(seek < srcSize);
|
||||
this->sliceType = slice->slice_type;
|
||||
|
||||
// don't start until we have an I-FRAME
|
||||
if (this->frameNum == 0 && this->sliceType != NAL_SLICE_TYPE_I)
|
||||
return true;
|
||||
|
||||
{
|
||||
if (!setup_pic_buffer(this, slice)) return false;
|
||||
if (!setup_mat_buffer(this)) return false;
|
||||
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->picBufferID[this->currentSID],
|
||||
this->matBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->picBufferID[this->currentSID] =
|
||||
this->matBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!setup_sli_buffer(this, srcSize, slice, seek)) return false;
|
||||
if (!setup_dat_buffer(this, src, srcSize )) return false;
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->sliBufferID[this->currentSID],
|
||||
this->datBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->sliBufferID[this->currentSID] =
|
||||
this->datBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
status = vaEndPicture(this->vaDisplay, this->vaContextID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaEndPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// advance to the next surface and save the old picture info
|
||||
this->lastSID = this->currentSID;
|
||||
if (++this->currentSID == SURFACE_NUM)
|
||||
this->currentSID = 0;
|
||||
this->frameNum += 1;
|
||||
this->fieldCount += 2;
|
||||
memcpy(&this->oldPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
// prepare the next surface
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[this->currentSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_get_buffer(void * opaque, uint8_t * dst, size_t dstSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
// ensure the surface is ready
|
||||
status = vaSyncSurface(this->vaDisplay, this->vaSurfaceID[this->lastSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// this doesn't work on my system, seems the vdpau va driver is bugged
|
||||
VASurfaceStatus surfStatus;
|
||||
if (!check_surface(this, this->lastSID, &surfStatus))
|
||||
return false;
|
||||
|
||||
if (surfStatus != VASurfaceReady)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface didn't block, the surface is not ready!");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// get the decoded data
|
||||
VAImage decoded =
|
||||
{
|
||||
.image_id = VA_INVALID_ID,
|
||||
.buf = VA_INVALID_ID
|
||||
};
|
||||
|
||||
status = vaDeriveImage(this->vaDisplay, this->vaSurfaceID[this->lastSID], &decoded);
|
||||
if (status == VA_STATUS_ERROR_OPERATION_FAILED)
|
||||
{
|
||||
VAImageFormat format =
|
||||
{
|
||||
.fourcc = VA_FOURCC_NV12,
|
||||
.byte_order = VA_LSB_FIRST,
|
||||
.bits_per_pixel = 12
|
||||
};
|
||||
|
||||
status = vaCreateImage(
|
||||
this->vaDisplay,
|
||||
&format,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
&decoded
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaPutImage(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
decoded.image_id,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaPutImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDeriveImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, decoded.buf, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(dst, d, decoded.data_size);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, decoded.buf);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDestroyImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaCreateSurfaceGLX(this->vaDisplay, target, texture, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
*ref = NULL;
|
||||
DEBUG_ERROR("vaCreateSurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaDestroySurfaceGLX(this->vaDisplay, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
DEBUG_ERROR("vaDestroySurfaceGLX: %s", vaErrorStr(status));
|
||||
}
|
||||
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
status = vaCopySurfaceGLX(
|
||||
this->vaDisplay,
|
||||
ref,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
0
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCopySurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_H264 =
|
||||
{
|
||||
.name = "H.264",
|
||||
.create = lgd_h264_create,
|
||||
.destroy = lgd_h264_destroy,
|
||||
.initialize = lgd_h264_initialize,
|
||||
.deinitialize = lgd_h264_deinitialize,
|
||||
.get_out_format = lgd_h264_get_out_format,
|
||||
.get_frame_pitch = lgd_h264_get_frame_pitch,
|
||||
.get_frame_stride = lgd_h264_get_frame_stride,
|
||||
.decode = lgd_h264_decode,
|
||||
.get_buffer = lgd_h264_get_buffer,
|
||||
|
||||
.has_gl = true,
|
||||
.init_gl_texture = lgd_h264_init_gl_texture,
|
||||
.free_gl_texture = lgd_h264_free_gl_texture,
|
||||
.update_gl_texture = lgd_h264_update_gl_texture
|
||||
};
|
||||
130
client/decoders/src/null.c
Normal file
130
client/decoders/src/null.c
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
const uint8_t * src;
|
||||
};
|
||||
|
||||
static bool lgd_null_create (void ** opaque);
|
||||
static void lgd_null_destroy (void * opaque);
|
||||
static bool lgd_null_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_null_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_null_get_out_format (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque);
|
||||
static bool lgd_null_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_null_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_null_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_null_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memset(this, 0, sizeof(struct Inst));
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_null_get_out_format(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
switch(this->format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA : return LG_OUTPUT_BGRA;
|
||||
case FRAME_TYPE_RGBA : return LG_OUTPUT_RGBA;
|
||||
case FRAME_TYPE_RGBA10: return LG_OUTPUT_RGBA10;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown frame type");
|
||||
return LG_OUTPUT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.pitch;
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.stride;
|
||||
}
|
||||
|
||||
static bool lgd_null_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->src = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_null_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->src)
|
||||
return NULL;
|
||||
|
||||
return this->src;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_NULL =
|
||||
{
|
||||
.name = "NULL",
|
||||
.create = lgd_null_create,
|
||||
.destroy = lgd_null_destroy,
|
||||
.initialize = lgd_null_initialize,
|
||||
.deinitialize = lgd_null_deinitialize,
|
||||
.get_out_format = lgd_null_get_out_format,
|
||||
.get_frame_pitch = lgd_null_get_frame_pitch,
|
||||
.get_frame_stride = lgd_null_get_frame_stride,
|
||||
.decode = lgd_null_decode,
|
||||
.get_buffer = lgd_null_get_buffer
|
||||
};
|
||||
169
client/decoders/src/yuv420.c
Normal file
169
client/decoders/src/yuv420.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "interface/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
struct Pixel
|
||||
{
|
||||
uint8_t b, g, r, a;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
struct Pixel * pixels;
|
||||
unsigned int yBytes;
|
||||
};
|
||||
|
||||
static bool lgd_yuv420_create (void ** opaque);
|
||||
static void lgd_yuv420_destroy (void * opaque);
|
||||
static bool lgd_yuv420_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_yuv420_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_yuv420_get_out_format (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque);
|
||||
static bool lgd_yuv420_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_yuv420_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_yuv420_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
this->yBytes = format.width * format.height;
|
||||
this->pixels = malloc(sizeof(struct Pixel) * (format.width * format.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
free(this->pixels);
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_yuv420_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_BGRA;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
//FIXME: implement this properly using GLSL
|
||||
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
const unsigned int hw = this->format.width / 2;
|
||||
const unsigned int hp = this->yBytes / 4;
|
||||
|
||||
for(size_t y = 0; y < this->format.height; ++y)
|
||||
for(size_t x = 0; x < this->format.width; ++x)
|
||||
{
|
||||
const unsigned int yoff = y * this->format.width + x;
|
||||
const unsigned int uoff = this->yBytes + ((y / 2) * hw + x / 2);
|
||||
const unsigned int voff = uoff + hp;
|
||||
|
||||
float b = 1.164f * ((float)src[yoff] - 16.0f) + 2.018f * ((float)src[uoff] - 128.0f);
|
||||
float g = 1.164f * ((float)src[yoff] - 16.0f) - 0.813f * ((float)src[voff] - 128.0f) - 0.391f * ((float)src[uoff] - 128.0f);
|
||||
float r = 1.164f * ((float)src[yoff] - 16.0f) + 1.596f * ((float)src[voff] - 128.0f);
|
||||
|
||||
#define CLAMP(x) (x < 0 ? 0 : (x > 255 ? 255 : x))
|
||||
this->pixels[yoff].b = CLAMP(b);
|
||||
this->pixels[yoff].g = CLAMP(g);
|
||||
this->pixels[yoff].r = CLAMP(r);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_yuv420_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return (uint8_t *)this->pixels;
|
||||
}
|
||||
|
||||
bool lgd_yuv420_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void lgd_yuv420_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool lgd_yuv420_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_YUV420 =
|
||||
{
|
||||
.name = "YUV420",
|
||||
.create = lgd_yuv420_create,
|
||||
.destroy = lgd_yuv420_destroy,
|
||||
.initialize = lgd_yuv420_initialize,
|
||||
.deinitialize = lgd_yuv420_deinitialize,
|
||||
.get_out_format = lgd_yuv420_get_out_format,
|
||||
.get_frame_pitch = lgd_yuv420_get_frame_pitch,
|
||||
.get_frame_stride = lgd_yuv420_get_frame_stride,
|
||||
.decode = lgd_yuv420_decode,
|
||||
.get_buffer = lgd_yuv420_get_buffer,
|
||||
|
||||
.has_gl = false, //FIXME: Implement this
|
||||
.init_gl_texture = lgd_yuv420_init_gl_texture,
|
||||
.free_gl_texture = lgd_yuv420_free_gl_texture,
|
||||
.update_gl_texture = lgd_yuv420_update_gl_texture
|
||||
};
|
||||
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}
|
||||
)
|
||||
153
client/fonts/SDL/src/sdl.c
Normal file
153
client/fonts/SDL/src/sdl.c
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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)
|
||||
{
|
||||
if (g_initCount++ == 0)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTF_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*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;
|
||||
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * font = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
||||
{
|
||||
this->font = TTF_OpenFont((char *)file, size);
|
||||
if (!this->font)
|
||||
{
|
||||
DEBUG_ERROR("TTL_OpenFont Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
return false;
|
||||
}
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
TTF_Quit();
|
||||
}
|
||||
|
||||
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 (!(surface = TTF_RenderText_Blended(this->font, text, color)))
|
||||
{
|
||||
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
|
||||
};
|
||||
58
client/include/interface/app.h
Normal file
58
client/include/interface/app.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*SuperEventFn)(SDL_Scancode key, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param key The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_release_keybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_release_keybind(KeybindHandle * handle);
|
||||
62
client/include/interface/clipboard.h
Normal file
62
client/include/interface/clipboard.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size);
|
||||
typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
typedef void (* LG_ClipboardReleaseFn)();
|
||||
typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardDataFn )(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
|
||||
typedef const char * (* LG_ClipboardGetName)();
|
||||
typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn);
|
||||
typedef void (* LG_ClipboardFree)();
|
||||
typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg);
|
||||
typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardRelease)();
|
||||
typedef void (* LG_ClipboardRequest)(LG_ClipboardData type);
|
||||
|
||||
typedef struct LG_Clipboard
|
||||
{
|
||||
LG_ClipboardGetName getName;
|
||||
LG_ClipboardInit init;
|
||||
LG_ClipboardFree free;
|
||||
LG_ClipboardWMEvent wmevent;
|
||||
LG_ClipboardNotice notice;
|
||||
LG_ClipboardRelease release;
|
||||
LG_ClipboardRequest request;
|
||||
}
|
||||
LG_Clipboard;
|
||||
75
client/include/interface/decoder.h
Normal file
75
client/include/interface/decoder.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef enum LG_OutFormat
|
||||
{
|
||||
LG_OUTPUT_INVALID,
|
||||
|
||||
LG_OUTPUT_BGRA,
|
||||
LG_OUTPUT_RGBA,
|
||||
LG_OUTPUT_RGBA10,
|
||||
LG_OUTPUT_YUV420
|
||||
}
|
||||
LG_OutFormat;
|
||||
|
||||
typedef bool (* LG_DecoderCreate )(void ** opaque);
|
||||
typedef void (* LG_DecoderDestroy )(void * opaque);
|
||||
typedef bool (* LG_DecoderInitialize )(void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
typedef void (* LG_DecoderDeInitialize )(void * opaque);
|
||||
typedef LG_OutFormat (* LG_DecoderGetOutFormat )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFramePitch )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFrameStride)(void * opaque);
|
||||
typedef bool (* LG_DecoderDecode )(void * opaque, const uint8_t * src, size_t srcSize);
|
||||
typedef const uint8_t * (* LG_DecoderGetBuffer )(void * opaque);
|
||||
|
||||
typedef bool (* LG_DecoderInitGLTexture )(void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
typedef void (* LG_DecoderFreeGLTexture )(void * opaque, void * ref);
|
||||
typedef bool (* LG_DecoderUpdateGLTexture)(void * opaque, void * ref);
|
||||
|
||||
typedef struct LG_Decoder
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_DecoderCreate create;
|
||||
LG_DecoderDestroy destroy;
|
||||
LG_DecoderInitialize initialize;
|
||||
LG_DecoderDeInitialize deinitialize;
|
||||
LG_DecoderGetOutFormat get_out_format;
|
||||
LG_DecoderGetFramePitch get_frame_pitch;
|
||||
LG_DecoderGetFrameStride get_frame_stride;
|
||||
LG_DecoderDecode decode;
|
||||
LG_DecoderGetBuffer get_buffer;
|
||||
|
||||
// optional support
|
||||
const bool has_gl;
|
||||
LG_DecoderInitGLTexture init_gl_texture;
|
||||
LG_DecoderFreeGLTexture free_gl_texture;
|
||||
LG_DecoderUpdateGLTexture update_gl_texture;
|
||||
}
|
||||
LG_Decoder;
|
||||
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;
|
||||
115
client/include/interface/renderer.h
Normal file
115
client/include/interface/renderer.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.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_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->on_alert && \
|
||||
(x)->render_startup && \
|
||||
(x)->render && \
|
||||
(x)->update_fps)
|
||||
|
||||
typedef struct LG_RendererParams
|
||||
{
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
bool showFPS;
|
||||
}
|
||||
LG_RendererParams;
|
||||
|
||||
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_RendererFormat;
|
||||
|
||||
typedef struct LG_RendererRect
|
||||
{
|
||||
bool valid;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
|
||||
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
|
||||
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer frame);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererSetup setup;
|
||||
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameEvent on_frame_event;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererRender render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
}
|
||||
LG_Renderer;
|
||||
33
client/include/lg-decoders.h
Normal file
33
client/include/lg-decoders.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 "interface/decoder.h"
|
||||
|
||||
extern const LG_Decoder LGD_NULL;
|
||||
extern const LG_Decoder LGD_YUV420;
|
||||
|
||||
const LG_Decoder * LG_Decoders[] =
|
||||
{
|
||||
&LGD_NULL,
|
||||
&LGD_YUV420,
|
||||
NULL // end of array sentinal
|
||||
};
|
||||
|
||||
#define LG_DECODER_COUNT ((sizeof(LG_Decoders) / sizeof(LG_Decoder *)) - 1)
|
||||
31
client/include/ll.h
Normal file
31
client/include/ll.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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);
|
||||
unsigned int ll_count (struct ll * list);
|
||||
|
||||
void ll_reset (struct ll * list);
|
||||
bool ll_walk (struct ll * list, void ** data);
|
||||
99
client/include/utils.h
Normal file
99
client/include/utils.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static inline uint64_t microtime()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
|
||||
return ((uint64_t)time.tv_sec * 1000000) + (time.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
static inline uint64_t nanotime()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
|
||||
return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec;
|
||||
}
|
||||
|
||||
static inline void nsleep(uint64_t ns)
|
||||
{
|
||||
const struct timespec ts =
|
||||
{
|
||||
.tv_sec = ns / 1e9,
|
||||
.tv_nsec = ns - ((ns / 1e9) * 1e9)
|
||||
};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
#ifdef ATOMIC_LOCKING
|
||||
#define LG_LOCK_MODE "Atomic"
|
||||
typedef volatile int LG_Lock;
|
||||
#define LG_LOCK_INIT(x) (x) = 0
|
||||
#define LG_LOCK(x) while(__sync_lock_test_and_set(&(x), 1)) {nsleep(100);}
|
||||
#define LG_UNLOCK(x) __sync_lock_release(&x)
|
||||
#define LG_LOCK_FREE(x)
|
||||
#else
|
||||
#include <SDL2/SDL.h>
|
||||
#define LG_LOCK_MODE "Mutex"
|
||||
typedef SDL_mutex * LG_Lock;
|
||||
#define LG_LOCK_INIT(x) (x = SDL_CreateMutex())
|
||||
#define LG_LOCK(x) SDL_LockMutex(x)
|
||||
#define LG_UNLOCK(x) SDL_UnlockMutex(x)
|
||||
#define LG_LOCK_FREE(x) SDL_DestroyMutex(x)
|
||||
#endif
|
||||
|
||||
static inline uint32_t get_bit(const uint8_t * const base, size_t * const offset)
|
||||
{
|
||||
uint32_t out = ((*(base + (*offset >> 0x3))) >> (0x7 - (*offset & 0x7))) & 0x1;
|
||||
++*offset;
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline uint32_t get_bits(const uint8_t * const base, size_t * const offset, const uint8_t bits)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
for (int i = 0; i < bits; ++i)
|
||||
value |= (get_bit(base, offset) ? 1 : 0) << (bits - i - 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline uint32_t decode_u_golomb(const uint8_t * const base, size_t * const offset)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
while(get_bit(base, offset) == 0)
|
||||
++i;
|
||||
|
||||
return ((1 << i) - 1 + get_bits(base, offset, i));
|
||||
}
|
||||
|
||||
static inline int32_t decode_s_golomb(const uint8_t * const base, size_t * const offset)
|
||||
{
|
||||
const uint32_t g = decode_u_golomb(base, offset);
|
||||
return (g & 0x1) ? (g + 1) / 2 : -(g / 2);
|
||||
}
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
bool file_get_contents(const char * filename, char ** buffer, size_t * length);
|
||||
@@ -1,546 +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/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;
|
||||
read(fd, &kick, sizeof(kick));
|
||||
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;
|
||||
}
|
||||
@@ -1,97 +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 <SDL_ttf.h>
|
||||
|
||||
#define IS_LG_RENDERER_VALID(x) \
|
||||
((x)->get_name && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->is_compatible && \
|
||||
(x)->on_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->render)
|
||||
|
||||
typedef struct LG_RendererParams
|
||||
{
|
||||
SDL_Window * window;
|
||||
TTF_Font * font;
|
||||
bool showFPS;
|
||||
bool resample;
|
||||
int width;
|
||||
int height;
|
||||
}
|
||||
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_RendererInitialize )(void ** opaque, const LG_RendererParams params, const LG_RendererFormat format);
|
||||
typedef void (* LG_RendererDeInitialize )(void * opaque);
|
||||
typedef bool (* LG_RendererIsCompatible )(void * opaque, const LG_RendererFormat format);
|
||||
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 uint8_t * data);
|
||||
typedef bool (* LG_RendererRender )(void * opaque);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererIsCompatible is_compatible;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameEvent on_frame_event;
|
||||
LG_RendererRender render;
|
||||
}
|
||||
LG_Renderer;
|
||||
978
client/main.c
978
client/main.c
@@ -1,978 +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 <getopt.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "utils.h"
|
||||
#include "KVMFR.h"
|
||||
#include "ivshmem/ivshmem.h"
|
||||
#include "spice/spice.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "lg-renderers.h"
|
||||
|
||||
struct AppState
|
||||
{
|
||||
bool running;
|
||||
bool started;
|
||||
|
||||
TTF_Font *font;
|
||||
SDL_Point srcSize;
|
||||
LG_RendererRect dstRect;
|
||||
SDL_Point cursor;
|
||||
bool haveCursorPos;
|
||||
float scaleX, scaleY;
|
||||
|
||||
const LG_Renderer * lgr ;
|
||||
void * lgrData;
|
||||
|
||||
SDL_Window * window;
|
||||
struct KVMFRHeader * shm;
|
||||
unsigned int shmSize;
|
||||
};
|
||||
|
||||
struct AppParams
|
||||
{
|
||||
bool autoResize;
|
||||
bool allowResize;
|
||||
bool keepAspect;
|
||||
bool borderless;
|
||||
bool center;
|
||||
int x, y;
|
||||
unsigned int w, h;
|
||||
const char * ivshmemSocket;
|
||||
bool useMipmap;
|
||||
bool showFPS;
|
||||
bool useSpice;
|
||||
const char * spiceHost;
|
||||
unsigned int spicePort;
|
||||
bool scaleMouseInput;
|
||||
bool hideMouse;
|
||||
};
|
||||
|
||||
struct AppState state;
|
||||
struct AppParams params =
|
||||
{
|
||||
.autoResize = false,
|
||||
.allowResize = true,
|
||||
.keepAspect = true,
|
||||
.borderless = false,
|
||||
.center = true,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.w = 1024,
|
||||
.h = 768,
|
||||
.ivshmemSocket = "/tmp/ivshmem_socket",
|
||||
.useMipmap = true,
|
||||
.showFPS = false,
|
||||
.useSpice = true,
|
||||
.spiceHost = "127.0.0.1",
|
||||
.spicePort = 5900,
|
||||
.scaleMouseInput = true,
|
||||
.hideMouse = true
|
||||
};
|
||||
|
||||
inline void updatePositionInfo()
|
||||
{
|
||||
int w, h;
|
||||
SDL_GetWindowSize(state.window, &w, &h);
|
||||
|
||||
if (params.keepAspect)
|
||||
{
|
||||
const float srcAspect = (float)state.srcSize.y / (float)state.srcSize.x;
|
||||
const float wndAspect = (float)h / (float)w;
|
||||
if (wndAspect < srcAspect)
|
||||
{
|
||||
state.dstRect.w = (float)h / srcAspect;
|
||||
state.dstRect.h = h;
|
||||
state.dstRect.x = (w >> 1) - (state.dstRect.w >> 1);
|
||||
state.dstRect.y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.dstRect.w = w;
|
||||
state.dstRect.h = (float)w * srcAspect;
|
||||
state.dstRect.x = 0;
|
||||
state.dstRect.y = (h >> 1) - (state.dstRect.h >> 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state.dstRect.x = 0;
|
||||
state.dstRect.y = 0;
|
||||
state.dstRect.w = w;
|
||||
state.dstRect.h = h;
|
||||
}
|
||||
|
||||
state.scaleX = (float)state.srcSize.y / (float)state.dstRect.h;
|
||||
state.scaleY = (float)state.srcSize.x / (float)state.dstRect.w;
|
||||
|
||||
if (state.lgr)
|
||||
state.lgr->on_resize(state.lgrData, w, h, state.dstRect);
|
||||
}
|
||||
|
||||
int renderThread(void * unused)
|
||||
{
|
||||
bool error = false;
|
||||
struct KVMFRHeader header;
|
||||
volatile uint32_t * updateCount = &state.shm->updateCount;
|
||||
|
||||
while(state.running)
|
||||
{
|
||||
// poll until we have a new frame, or we time out
|
||||
while(header.updateCount == *updateCount && state.running)
|
||||
{
|
||||
const struct timespec s =
|
||||
{
|
||||
.tv_sec = 0,
|
||||
.tv_nsec = 1000
|
||||
};
|
||||
nanosleep(&s, NULL);
|
||||
}
|
||||
|
||||
if (!state.running)
|
||||
break;
|
||||
|
||||
// we must take a copy of the header, both to let the guest advance and to
|
||||
// prevent the contained arguments being abused to overflow buffers
|
||||
memcpy(&header, state.shm, sizeof(struct KVMFRHeader));
|
||||
__sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_READY);
|
||||
|
||||
// check the header's magic and version are valid
|
||||
if (
|
||||
memcmp(header.magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0 ||
|
||||
header.version != KVMFR_HEADER_VERSION
|
||||
){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we have a frame
|
||||
if (header.flags & KVMFR_HEADER_FLAG_FRAME)
|
||||
{
|
||||
// sainty check of the frame format
|
||||
if (
|
||||
header.frame.type >= FRAME_TYPE_MAX ||
|
||||
header.frame.width == 0 ||
|
||||
header.frame.height == 0 ||
|
||||
header.frame.stride == 0 ||
|
||||
header.frame.dataPos == 0 ||
|
||||
header.frame.dataPos > state.shmSize
|
||||
){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// setup the renderer format with the frame format details
|
||||
LG_RendererFormat lgrFormat;
|
||||
lgrFormat.width = header.frame.width;
|
||||
lgrFormat.height = header.frame.height;
|
||||
lgrFormat.stride = header.frame.stride;
|
||||
|
||||
switch(header.frame.type)
|
||||
{
|
||||
case FRAME_TYPE_ARGB:
|
||||
lgrFormat.pitch = header.frame.stride * 4;
|
||||
lgrFormat.bpp = 32;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGB:
|
||||
lgrFormat.pitch = header.frame.stride * 3;
|
||||
lgrFormat.bpp = 24;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frameType");
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (error)
|
||||
break;
|
||||
|
||||
// check the header's dataPos is sane
|
||||
const size_t dataSize = lgrFormat.height * lgrFormat.pitch;
|
||||
if (header.frame.dataPos + dataSize > state.shmSize)
|
||||
{
|
||||
DEBUG_ERROR("The guest sent an invalid dataPos");
|
||||
break;
|
||||
}
|
||||
|
||||
// check if we have a compatible renderer
|
||||
if (!state.lgr || !state.lgr->is_compatible(state.lgrData, lgrFormat))
|
||||
{
|
||||
int width, height;
|
||||
SDL_GetWindowSize(state.window, &width, &height);
|
||||
|
||||
LG_RendererParams lgrParams;
|
||||
lgrParams.window = state.window;
|
||||
lgrParams.font = state.font;
|
||||
lgrParams.resample = params.useMipmap;
|
||||
lgrParams.showFPS = params.showFPS;
|
||||
lgrParams.width = width;
|
||||
lgrParams.height = height;
|
||||
|
||||
DEBUG_INFO("Data Format: w=%u, h=%u, s=%u, p=%u, bpp=%u",
|
||||
lgrFormat.width, lgrFormat.height, lgrFormat.stride, lgrFormat.pitch, lgrFormat.bpp);
|
||||
|
||||
// first try to reinitialize any existing renderer
|
||||
if (state.lgr)
|
||||
{
|
||||
state.lgr->deinitialize(state.lgrData);
|
||||
if (state.lgr->initialize(&state.lgrData, lgrParams, lgrFormat))
|
||||
{
|
||||
DEBUG_INFO("Reinitialized %s", state.lgr->get_name());
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to reinitialize %s, trying other renderers", state.lgr->get_name());
|
||||
state.lgr->deinitialize(state.lgrData);
|
||||
state.lgr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.lgr)
|
||||
{
|
||||
// probe for a a suitable renderer
|
||||
for(const LG_Renderer **r = &LG_Renderers[0]; *r; ++r)
|
||||
{
|
||||
if (!IS_LG_RENDERER_VALID(*r))
|
||||
{
|
||||
DEBUG_ERROR("FIXME: Renderer %d is invalid, skipping", (int)(r - &LG_Renderers[0]));
|
||||
continue;
|
||||
}
|
||||
|
||||
state.lgrData = NULL;
|
||||
if (!(*r)->initialize(&state.lgrData, lgrParams, lgrFormat))
|
||||
{
|
||||
(*r)->deinitialize(state.lgrData);
|
||||
continue;
|
||||
}
|
||||
|
||||
state.lgr = *r;
|
||||
DEBUG_INFO("Initialized %s", (*r)->get_name());
|
||||
break;
|
||||
}
|
||||
|
||||
if (!state.lgr)
|
||||
{
|
||||
DEBUG_INFO("Unable to find a suitable renderer");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
state.srcSize.x = header.frame.width;
|
||||
state.srcSize.y = header.frame.height;
|
||||
if (params.autoResize)
|
||||
SDL_SetWindowSize(state.window, header.frame.width, header.frame.height);
|
||||
updatePositionInfo();
|
||||
}
|
||||
|
||||
const uint8_t * data = (const uint8_t *)state.shm + header.frame.dataPos;
|
||||
if (!state.lgr->on_frame_event(state.lgrData, data))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render the frame");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have cursor data
|
||||
if (header.flags & KVMFR_HEADER_FLAG_CURSOR)
|
||||
{
|
||||
if (header.cursor.flags & KVMFR_CURSOR_FLAG_POS)
|
||||
{
|
||||
state.cursor.x = header.cursor.x;
|
||||
state.cursor.y = header.cursor.y;
|
||||
state.haveCursorPos = true;
|
||||
}
|
||||
|
||||
if (header.cursor.flags & KVMFR_CURSOR_FLAG_SHAPE)
|
||||
{
|
||||
LG_RendererCursor c = LG_CURSOR_COLOR;
|
||||
switch(header.cursor.type)
|
||||
{
|
||||
case CURSOR_TYPE_COLOR : c = LG_CURSOR_COLOR ; break;
|
||||
case CURSOR_TYPE_MONOCHROME : c = LG_CURSOR_MONOCHROME ; break;
|
||||
case CURSOR_TYPE_MASKED_COLOR: c = LG_CURSOR_MASKED_COLOR; break;
|
||||
default:
|
||||
DEBUG_ERROR("Invalid cursor type");
|
||||
break;
|
||||
}
|
||||
|
||||
if (state.lgr)
|
||||
{
|
||||
if (!state.lgr->on_mouse_shape(
|
||||
state.lgrData,
|
||||
c,
|
||||
header.cursor.w,
|
||||
header.cursor.h,
|
||||
header.cursor.pitch,
|
||||
header.cursor.shape
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to update mouse shape");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.lgr)
|
||||
{
|
||||
state.lgr->on_mouse_event(
|
||||
state.lgrData,
|
||||
(header.cursor.flags & KVMFR_CURSOR_FLAG_VISIBLE) != 0,
|
||||
state.cursor.x,
|
||||
state.cursor.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.lgr)
|
||||
state.lgr->render(state.lgrData);
|
||||
}
|
||||
|
||||
if (state.lgr)
|
||||
state.lgr->deinitialize(state.lgrData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ivshmemThread(void * arg)
|
||||
{
|
||||
while(state.running)
|
||||
if (!ivshmem_process())
|
||||
{
|
||||
if (state.running)
|
||||
{
|
||||
state.running = false;
|
||||
DEBUG_ERROR("failed to process ivshmem messages");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spiceThread(void * arg)
|
||||
{
|
||||
while(state.running)
|
||||
if (!spice_process())
|
||||
{
|
||||
if (state.running)
|
||||
{
|
||||
state.running = false;
|
||||
DEBUG_ERROR("failed to process spice messages");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
spice_disconnect();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline const uint32_t mapScancode(SDL_Scancode scancode)
|
||||
{
|
||||
uint32_t ps2;
|
||||
if (scancode > (sizeof(usb_to_ps2) / sizeof(uint32_t)) || (ps2 = usb_to_ps2[scancode]) == 0)
|
||||
{
|
||||
DEBUG_WARN("Unable to map USB scan code: %x\n", scancode);
|
||||
return 0;
|
||||
}
|
||||
return ps2;
|
||||
}
|
||||
|
||||
int eventThread(void * arg)
|
||||
{
|
||||
bool serverMode = false;
|
||||
bool realignGuest = true;
|
||||
bool keyDown[SDL_NUM_SCANCODES];
|
||||
|
||||
memset(keyDown, 0, sizeof(keyDown));
|
||||
|
||||
// ensure mouse acceleration is identical in server mode
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
||||
|
||||
while(state.running)
|
||||
{
|
||||
SDL_Event event;
|
||||
if (!SDL_PollEvent(&event))
|
||||
{
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.type == SDL_QUIT)
|
||||
{
|
||||
state.running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!params.useSpice)
|
||||
continue;
|
||||
|
||||
switch(event.type)
|
||||
{
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
SDL_Scancode sc = event.key.keysym.scancode;
|
||||
if (sc == SDL_SCANCODE_SCROLLLOCK)
|
||||
{
|
||||
if (event.key.repeat)
|
||||
break;
|
||||
|
||||
serverMode = !serverMode;
|
||||
spice_mouse_mode(serverMode);
|
||||
SDL_SetRelativeMouseMode(serverMode);
|
||||
|
||||
if (!serverMode)
|
||||
realignGuest = true;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t scancode = mapScancode(sc);
|
||||
if (scancode == 0)
|
||||
break;
|
||||
|
||||
if (spice_key_down(scancode))
|
||||
keyDown[sc] = true;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("SDL_KEYDOWN: failed to send message");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
SDL_Scancode sc = event.key.keysym.scancode;
|
||||
if (sc == SDL_SCANCODE_SCROLLLOCK)
|
||||
break;
|
||||
|
||||
// avoid sending key up events when we didn't send a down
|
||||
if (!keyDown[sc])
|
||||
break;
|
||||
|
||||
uint32_t scancode = mapScancode(sc);
|
||||
if (scancode == 0)
|
||||
break;
|
||||
|
||||
if (spice_key_up(scancode))
|
||||
keyDown[sc] = false;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("SDL_KEYUP: failed to send message");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (
|
||||
!spice_mouse_press (event.wheel.y == 1 ? 4 : 5) ||
|
||||
!spice_mouse_release(event.wheel.y == 1 ? 4 : 5)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("SDL_MOUSEWHEEL: failed to send messages");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
{
|
||||
if (
|
||||
!serverMode && (
|
||||
event.motion.x < state.dstRect.x ||
|
||||
event.motion.x > state.dstRect.x + state.dstRect.w ||
|
||||
event.motion.y < state.dstRect.y ||
|
||||
event.motion.y > state.dstRect.y + state.dstRect.h
|
||||
)
|
||||
)
|
||||
{
|
||||
realignGuest = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
if (realignGuest && state.haveCursorPos)
|
||||
{
|
||||
x = event.motion.x - state.dstRect.x;
|
||||
y = event.motion.y - state.dstRect.y;
|
||||
if (params.scaleMouseInput)
|
||||
{
|
||||
x = (float)x * state.scaleX;
|
||||
y = (float)y * state.scaleY;
|
||||
}
|
||||
x -= state.cursor.x;
|
||||
y -= state.cursor.y;
|
||||
realignGuest = false;
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message");
|
||||
break;
|
||||
}
|
||||
|
||||
x = event.motion.xrel;
|
||||
y = event.motion.yrel;
|
||||
if (x != 0 || y != 0)
|
||||
{
|
||||
if (params.scaleMouseInput)
|
||||
{
|
||||
x = (float)x * state.scaleX;
|
||||
y = (float)y * state.scaleY;
|
||||
}
|
||||
if (!spice_mouse_motion(x, y))
|
||||
{
|
||||
DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if (
|
||||
!spice_mouse_position(event.button.x, event.button.y) ||
|
||||
!spice_mouse_press(event.button.button)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (
|
||||
!spice_mouse_position(event.button.x, event.button.y) ||
|
||||
!spice_mouse_release(event.button.button)
|
||||
)
|
||||
{
|
||||
DEBUG_ERROR("SDL_MOUSEBUTTONUP: failed to send message");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
{
|
||||
switch(event.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
realignGuest = true;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
updatePositionInfo();
|
||||
realignGuest = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run()
|
||||
{
|
||||
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
|
||||
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.running = true;
|
||||
state.scaleX = 1.0f;
|
||||
state.scaleY = 1.0f;
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
DEBUG_ERROR("SDL_Init Failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (params.showFPS)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTL_Init Failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
FcConfig * config = FcInitLoadConfigAndFonts();
|
||||
if (!config)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)"FreeMono");
|
||||
FcConfigSubstitute (config, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * font = FcFontMatch(config, pat, &result);
|
||||
|
||||
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
||||
{
|
||||
state.font = TTF_OpenFont((char *)file, 14);
|
||||
if (!state.font)
|
||||
{
|
||||
DEBUG_ERROR("TTL_OpenFont Failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate a font for FPS display");
|
||||
return -1;
|
||||
}
|
||||
FcPatternDestroy(pat);
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0);
|
||||
|
||||
state.window = SDL_CreateWindow(
|
||||
"Looking Glass (Client)",
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
||||
params.w,
|
||||
params.h,
|
||||
(
|
||||
SDL_WINDOW_SHOWN |
|
||||
SDL_WINDOW_OPENGL |
|
||||
(params.allowResize ? SDL_WINDOW_RESIZABLE : 0) |
|
||||
(params.borderless ? SDL_WINDOW_BORDERLESS : 0)
|
||||
)
|
||||
);
|
||||
|
||||
// set the compositor hint to bypass for low latency
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (SDL_GetWindowWMInfo(state.window, &wminfo))
|
||||
{
|
||||
if (wminfo.subsystem == SDL_SYSWM_X11)
|
||||
{
|
||||
Atom NETWM_BYPASS_COMPOSITOR = XInternAtom(
|
||||
wminfo.info.x11.display,
|
||||
"NETWM_BYPASS_COMPOSITOR",
|
||||
False);
|
||||
|
||||
unsigned long value = 1;
|
||||
XChangeProperty(
|
||||
wminfo.info.x11.display,
|
||||
wminfo.info.x11.window,
|
||||
NETWM_BYPASS_COMPOSITOR,
|
||||
XA_CARDINAL,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char *)&value,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.window)
|
||||
{
|
||||
DEBUG_ERROR("failed to create window");
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_Cursor *cursor = NULL;
|
||||
if (params.hideMouse)
|
||||
{
|
||||
// work around SDL_ShowCursor being non functional
|
||||
int32_t cursorData[2] = {0, 0};
|
||||
cursor = SDL_CreateCursor((uint8_t*)cursorData, (uint8_t*)cursorData, 8, 8, 4, 4);
|
||||
SDL_SetCursor(cursor);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
|
||||
int shm_fd = 0;
|
||||
SDL_Thread *t_ivshmem = NULL;
|
||||
SDL_Thread *t_spice = NULL;
|
||||
SDL_Thread *t_event = NULL;
|
||||
|
||||
while(1)
|
||||
{
|
||||
if (!ivshmem_connect(params.ivshmemSocket))
|
||||
{
|
||||
DEBUG_ERROR("failed to connect to the ivshmem server");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(t_ivshmem = SDL_CreateThread(ivshmemThread, "ivshmemThread", NULL)))
|
||||
{
|
||||
DEBUG_ERROR("ivshmem create thread failed");
|
||||
break;
|
||||
}
|
||||
|
||||
state.shm = (struct KVMFRHeader *)ivshmem_get_map();
|
||||
if (!state.shm)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map memory");
|
||||
break;
|
||||
}
|
||||
state.shmSize = ivshmem_get_map_size();
|
||||
state.shm->hostID = ivshmem_get_id();
|
||||
|
||||
if (params.useSpice)
|
||||
{
|
||||
if (!spice_connect(params.spiceHost, params.spicePort, ""))
|
||||
{
|
||||
DEBUG_ERROR("Failed to connect to spice server");
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(state.running && !spice_ready())
|
||||
if (!spice_process())
|
||||
{
|
||||
state.running = false;
|
||||
DEBUG_ERROR("Failed to process spice messages");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL)))
|
||||
{
|
||||
DEBUG_ERROR("spice create thread failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(t_event = SDL_CreateThread(eventThread, "eventThread", NULL)))
|
||||
{
|
||||
DEBUG_ERROR("gpu create thread failed");
|
||||
break;
|
||||
}
|
||||
|
||||
// flag the host that we are starting up this is important so that
|
||||
// the host wakes up if it is waiting on an interrupt, the host will
|
||||
// also send us the current mouse shape since we won't know it yet
|
||||
DEBUG_INFO("Waiting for host to signal it's ready...");
|
||||
__sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART);
|
||||
while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART))
|
||||
usleep(1000);
|
||||
DEBUG_INFO("Host ready, starting session");
|
||||
|
||||
while(state.running)
|
||||
renderThread(NULL);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
state.running = false;
|
||||
|
||||
if (t_event)
|
||||
SDL_WaitThread(t_event, NULL);
|
||||
|
||||
// this needs to happen here to abort any waiting reads
|
||||
// as ivshmem uses recvmsg which has no timeout
|
||||
ivshmem_disconnect();
|
||||
if (t_ivshmem)
|
||||
SDL_WaitThread(t_ivshmem, NULL);
|
||||
|
||||
if (t_spice)
|
||||
SDL_WaitThread(t_spice, NULL);
|
||||
|
||||
if (state.window)
|
||||
SDL_DestroyWindow(state.window);
|
||||
|
||||
if (cursor)
|
||||
SDL_FreeCursor(cursor);
|
||||
|
||||
if (shm_fd)
|
||||
close(shm_fd);
|
||||
|
||||
TTF_Quit();
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void doHelp(char * app)
|
||||
{
|
||||
char x[8], y[8];
|
||||
snprintf(x, sizeof(x), "%d", params.x);
|
||||
snprintf(y, sizeof(y), "%d", params.y);
|
||||
|
||||
fprintf(stderr,
|
||||
"Looking Glass Client\n"
|
||||
"Usage: %s [OPTION]...\n"
|
||||
"Example: %s -h\n"
|
||||
"\n"
|
||||
" -h Print out this help\n"
|
||||
"\n"
|
||||
" -f PATH Specify the path to the ivshmem socket [current: %s]\n"
|
||||
"\n"
|
||||
" -s Disable spice client\n"
|
||||
" -c HOST Specify the spice host [current: %s]\n"
|
||||
" -p PORT Specify the spice port [current: %d]\n"
|
||||
" -j Disable cursor position scaling\n"
|
||||
" -M Don't hide the host cursor\n"
|
||||
"\n"
|
||||
" -m Disable mipmapping\n"
|
||||
" -v Disable VSync\n"
|
||||
" -k Enable FPS display\n"
|
||||
"\n"
|
||||
" -a Auto resize the window to the guest\n"
|
||||
" -n Don't allow the window to be manually resized\n"
|
||||
" -r Don't maintain the aspect ratio\n"
|
||||
" -d Borderless mode\n"
|
||||
" -x XPOS Initial window X position [current: %s]\n"
|
||||
" -y YPOS Initial window Y position [current: %s]\n"
|
||||
" -w WIDTH Initial window width [current: %u]\n"
|
||||
" -b HEIGHT Initial window height [current: %u]\n"
|
||||
"\n"
|
||||
" -l License information\n"
|
||||
"\n",
|
||||
app,
|
||||
app,
|
||||
params.ivshmemSocket,
|
||||
params.spiceHost,
|
||||
params.spicePort,
|
||||
params.center ? "center" : x,
|
||||
params.center ? "center" : y,
|
||||
params.w,
|
||||
params.h
|
||||
);
|
||||
}
|
||||
|
||||
void doLicense()
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\n"
|
||||
"Looking Glass - KVM FrameRelay (KVMFR) Client\n"
|
||||
"Copyright(C) 2017 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"
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
int c;
|
||||
while((c = getopt(argc, argv, "hf:sc:p:jMmkanrdx:y:w:b:l")) != -1)
|
||||
switch(c)
|
||||
{
|
||||
case '?':
|
||||
case 'h':
|
||||
default :
|
||||
doHelp(argv[0]);
|
||||
return (c == 'h') ? 0 : -1;
|
||||
|
||||
case 'f':
|
||||
params.ivshmemSocket = optarg;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
params.useSpice = false;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
params.spiceHost = optarg;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
params.spicePort = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'j':
|
||||
params.scaleMouseInput = false;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
params.hideMouse = false;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
params.useMipmap = false;
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
params.showFPS = true;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
params.autoResize = true;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
params.allowResize = false;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
params.keepAspect = false;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
params.borderless = true;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
params.center = false;
|
||||
params.x = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'y':
|
||||
params.center = false;
|
||||
params.y = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
params.w = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
params.h = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
doLicense();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return run();
|
||||
}
|
||||
1009
client/parsers/nal.c
Normal file
1009
client/parsers/nal.c
Normal file
File diff suppressed because it is too large
Load Diff
305
client/parsers/nal.h
Normal file
305
client/parsers/nal.h
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define NAL_TYPE_CODED_SLICE_NON_IDR 1
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_A 2
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_B 3
|
||||
#define NAL_TYPE_CODED_SLICE_DATA_PARTITION_C 4
|
||||
#define NAL_TYPE_CODED_SLICE_IDR 5
|
||||
#define NAL_TYPE_SPS 7
|
||||
#define NAL_TYPE_PPS 8
|
||||
#define NAL_TYPE_AUD 9
|
||||
#define NAL_TYPE_END_OF_SEQUENCE 10
|
||||
#define NAL_TYPE_END_OF_STREAM 11
|
||||
#define NAL_TYPE_CODED_SLICE_AUX 19
|
||||
|
||||
#define IDC_PROFILE_BASELINE 66
|
||||
#define IDC_PROFILE_MAIN 77
|
||||
#define IDC_PROFILE_EXTENDED 88
|
||||
#define IDC_PROFILE_HP 100
|
||||
#define IDC_PROFILE_Hi10P 110
|
||||
#define IDC_PROFILE_Hi422 122
|
||||
#define IDC_PROFILE_Hi444 244
|
||||
#define IDC_PROFILE_CAVLC444 44
|
||||
|
||||
#define IDC_CHROMA_FORMAT_YUV400 0
|
||||
#define IDC_CHROMA_FORMAT_YUV420 1
|
||||
#define IDC_CHROMA_FORMAT_YVU422 2
|
||||
#define IDC_CHROMA_FORMAT_YUV444 3
|
||||
|
||||
#define IDC_VUI_ASPECT_RATIO_EXTENDED_SAR 0xFF
|
||||
|
||||
#define NAL_PICTURE_TYPE_I 0
|
||||
#define NAL_PICTURE_TYPE_P 1
|
||||
#define NAL_PICTURE_TYPE_B 2
|
||||
|
||||
#define NAL_SLICE_TYPE_P 0
|
||||
#define NAL_SLICE_TYPE_B 1
|
||||
#define NAL_SLICE_TYPE_I 2
|
||||
#define NAL_SLICE_TYPE_SP 3
|
||||
#define NAL_SLICE_TYPE_SI 4
|
||||
|
||||
typedef struct NAL_SPS
|
||||
{
|
||||
uint8_t profile_idc;
|
||||
uint8_t constraint_set_flags[3];
|
||||
uint8_t level_idc;
|
||||
uint32_t seq_parameter_set_id;
|
||||
uint32_t chroma_format_idc;
|
||||
uint8_t seperate_colour_plane_flag;
|
||||
uint32_t bit_depth_luma_minus8;
|
||||
uint32_t bit_depth_chroma_minus8;
|
||||
uint8_t lossless_qpprime_y_zero_flag;
|
||||
uint8_t seq_scaling_matrix_present_flag;
|
||||
uint8_t seq_scaling_list_present_flag[12];
|
||||
uint32_t log2_max_frame_num_minus4;
|
||||
uint32_t pic_order_cnt_type;
|
||||
uint32_t log2_max_pic_order_cnt_lsb_minus4;
|
||||
uint8_t delta_pic_order_always_zero_flag;
|
||||
int32_t offset_for_non_ref_pic;
|
||||
int32_t offset_for_top_to_bottom_field;
|
||||
uint32_t num_ref_frames_in_pic_order_cnt_cycle;
|
||||
int32_t * offset_for_ref_frame;
|
||||
uint32_t num_ref_frames;
|
||||
uint8_t gaps_in_frame_num_value_allowed_flag;
|
||||
uint32_t pic_width_in_mbs_minus1;
|
||||
uint32_t pic_height_in_map_units_minus1;
|
||||
uint8_t frame_mbs_only_flag;
|
||||
uint8_t mb_adaptive_frame_field_flag;
|
||||
uint8_t direct_8x8_inference_flag;
|
||||
uint8_t frame_cropping_flag;
|
||||
uint32_t frame_crop_left_offset;
|
||||
uint32_t frame_crop_right_offset;
|
||||
uint32_t frame_crop_top_offset;
|
||||
uint32_t frame_crop_bottom_offset;
|
||||
uint8_t vui_parameters_present_flag;
|
||||
}
|
||||
NAL_SPS;
|
||||
|
||||
typedef struct NAL_CPB
|
||||
{
|
||||
uint32_t bit_rate_value_minus1;
|
||||
uint32_t cpb_size_value_minus1;
|
||||
uint8_t cbr_flag;
|
||||
}
|
||||
NAL_CPB;
|
||||
|
||||
typedef struct NAL_HRD
|
||||
{
|
||||
uint32_t cpb_cnt_minus1;
|
||||
uint8_t bit_rate_scale;
|
||||
uint8_t cpb_size_scale;
|
||||
uint8_t cpb_size_count;
|
||||
NAL_CPB * cpb;
|
||||
uint8_t initial_cpb_removal_delay_length_minus1;
|
||||
uint8_t cpb_removal_delay_length_minus1;
|
||||
uint8_t dpb_output_delay_length_minus1;
|
||||
uint8_t time_offset_length;
|
||||
}
|
||||
NAL_HRD;
|
||||
|
||||
typedef struct NAL_VUI
|
||||
{
|
||||
uint8_t aspect_ratio_info_present_flag;
|
||||
uint8_t aspect_ratio_idc;
|
||||
uint16_t sar_width;
|
||||
uint16_t sar_height;
|
||||
uint8_t overscan_info_present_flag;
|
||||
uint8_t overscan_appropriate_flag;
|
||||
uint8_t video_signal_type_present_flag;
|
||||
uint8_t video_format;
|
||||
uint8_t video_full_range_flag;
|
||||
uint8_t colour_description_present_flag;
|
||||
uint8_t colour_primaries;
|
||||
uint8_t transfer_characteristics;
|
||||
uint8_t matrix_coefficients;
|
||||
uint8_t chroma_loc_info_present_flag;
|
||||
uint32_t chroma_sample_loc_type_top_field;
|
||||
uint32_t chroma_sample_loc_type_bottom_field;
|
||||
uint8_t timing_info_present_flag;
|
||||
uint32_t num_units_in_tick;
|
||||
uint32_t time_scale;
|
||||
uint8_t fixed_frame_rate_flag;
|
||||
uint8_t nal_hrd_parameters_present_flag;
|
||||
NAL_HRD nal_hrd_parameters;
|
||||
uint8_t vcl_hrd_parameters_present_flag;
|
||||
NAL_HRD vcl_hrd_parameters;
|
||||
uint8_t low_delay_hrd_flag;
|
||||
uint8_t pic_struct_present_flag;
|
||||
uint8_t bitstream_restriction_flag;
|
||||
uint8_t motion_vectors_over_pic_boundaries_flag;
|
||||
uint32_t max_bytes_per_pic_denom;
|
||||
uint32_t max_bits_per_mb_denom;
|
||||
uint32_t log2_max_mv_length_horizontal;
|
||||
uint32_t log2_max_mv_length_vertical;
|
||||
uint32_t num_reorder_frames;
|
||||
uint32_t max_dec_frame_buffering;
|
||||
}
|
||||
NAL_VUI;
|
||||
|
||||
typedef struct NAL_SLICE_GROUP_T0
|
||||
{
|
||||
uint32_t run_length_minus1;
|
||||
}
|
||||
NAL_SLICE_GROUP_T0;
|
||||
|
||||
typedef struct NAL_SLICE_GROUP_T2
|
||||
{
|
||||
uint32_t top_left;
|
||||
uint32_t bottom_right;
|
||||
}
|
||||
NAL_SLICE_GROUP_T2;
|
||||
|
||||
typedef union NAL_SLICE_GROUP
|
||||
{
|
||||
NAL_SLICE_GROUP_T0 t0;
|
||||
NAL_SLICE_GROUP_T2 t2;
|
||||
}
|
||||
NAL_SLICE_GROUP;
|
||||
|
||||
typedef struct NAL_PPS
|
||||
{
|
||||
uint32_t pic_parameter_set_id;
|
||||
uint32_t seq_parameter_set_id;
|
||||
uint8_t entropy_coding_mode_flag;
|
||||
uint8_t pic_order_present_flag;
|
||||
uint32_t num_slice_groups_minus1;
|
||||
NAL_SLICE_GROUP * slice_groups;
|
||||
uint32_t slice_group_map_type;
|
||||
uint8_t slice_group_change_direction_flag;
|
||||
uint32_t slice_group_change_rate_minus1;
|
||||
uint32_t pic_size_in_map_units_minus1;
|
||||
uint32_t * slice_group_id;
|
||||
uint32_t num_ref_idx_l0_active_minus1;
|
||||
uint32_t num_ref_idx_l1_active_minus1;
|
||||
uint8_t weighted_pred_flag;
|
||||
uint8_t weighted_bipred_idc;
|
||||
int32_t pic_init_qp_minus26;
|
||||
int32_t pic_init_qs_minus26;
|
||||
int32_t chroma_qp_index_offset;
|
||||
uint8_t deblocking_filter_control_present_flag;
|
||||
uint8_t constrained_intra_pred_flag;
|
||||
uint8_t redundant_pic_cnt_present_flag;
|
||||
|
||||
uint8_t transform_8x8_mode_flag;
|
||||
uint8_t pic_scaling_matrix_present_flag;
|
||||
uint8_t pic_scaling_list_present_flag[6];
|
||||
int32_t scaling_list_4x4[6];
|
||||
int32_t scaling_list_8x8[2];
|
||||
int32_t second_chroma_qp_index_offset;
|
||||
}
|
||||
NAL_PPS;
|
||||
|
||||
typedef struct NAL_RPL_REORDER_L
|
||||
{
|
||||
bool valid;
|
||||
uint32_t reordering_of_pic_nums_idc;
|
||||
uint32_t abs_diff_pic_num_minus1;
|
||||
uint32_t long_term_pic_num;
|
||||
}
|
||||
NAL_RPL_REORDER_L;
|
||||
|
||||
typedef struct NAL_RPL_REORDER
|
||||
{
|
||||
uint8_t ref_pic_list_reordering_flag_l0;
|
||||
NAL_RPL_REORDER_L l0[3];
|
||||
uint8_t ref_pic_list_reordering_flag_l1;
|
||||
NAL_RPL_REORDER_L l1[3];
|
||||
}
|
||||
NAL_RPL_REORDER;
|
||||
|
||||
typedef struct NAL_PW_TABLE_L
|
||||
{
|
||||
int32_t luma_weight;
|
||||
int32_t luma_offset;
|
||||
int32_t chroma_weight[2];
|
||||
int32_t chroma_offset[2];
|
||||
}
|
||||
NAL_PW_TABLE_L;
|
||||
|
||||
typedef struct NAL_PW_TABLE
|
||||
{
|
||||
uint32_t luma_log2_weight_denom;
|
||||
uint32_t chroma_log2_weight_denom;
|
||||
uint8_t luma_weight_flag[2];
|
||||
uint8_t chroma_weight_flag[2];
|
||||
NAL_PW_TABLE_L * l0;
|
||||
NAL_PW_TABLE_L * l1;
|
||||
}
|
||||
NAL_PW_TABLE;
|
||||
|
||||
typedef struct NAL_RP_MARKING
|
||||
{
|
||||
uint8_t no_output_of_prior_pics_flag;
|
||||
uint8_t long_term_reference_flag;
|
||||
uint8_t adaptive_ref_pic_marking_mode_flag;
|
||||
uint32_t memory_management_control_operation;
|
||||
uint32_t difference_of_pic_nums_minus1;
|
||||
uint32_t long_term_pic_num;
|
||||
uint32_t long_term_frame_idx;
|
||||
uint32_t max_long_term_frame_idx_plus1;
|
||||
}
|
||||
NAL_RP_MARKING;
|
||||
|
||||
typedef struct NAL_SLICE
|
||||
{
|
||||
uint8_t nal_ref_idc;
|
||||
uint32_t first_mb_in_slice;
|
||||
uint32_t slice_type;
|
||||
uint32_t pic_parameter_set_id;
|
||||
uint32_t frame_num;
|
||||
uint8_t field_pic_flag;
|
||||
uint8_t bottom_field_flag;
|
||||
uint32_t idr_pic_id;
|
||||
uint32_t pic_order_cnt_lsb;
|
||||
int32_t delta_pic_order_cnt_bottom;
|
||||
int32_t delta_pic_order_cnt[2];
|
||||
uint32_t redundant_pic_cnt;
|
||||
uint8_t direct_spatial_mv_pred_flag;
|
||||
uint8_t num_ref_idx_active_override_flag;
|
||||
uint32_t num_ref_idx_l0_active_minus1;
|
||||
uint32_t num_ref_idx_l1_active_minus1;
|
||||
NAL_RPL_REORDER ref_pic_list_reordering;
|
||||
NAL_PW_TABLE pred_weight_table;
|
||||
NAL_RP_MARKING dec_ref_pic_marking;
|
||||
uint32_t cabac_init_idc;
|
||||
int32_t slice_qp_delta;
|
||||
uint8_t sp_for_switch_flag;
|
||||
int32_t slice_qs_delta;
|
||||
uint32_t disable_deblocking_filter_idc;
|
||||
int32_t slice_alpha_c0_offset_div2;
|
||||
int32_t slice_beta_offset_div2;
|
||||
uint32_t slice_group_change_cycle;
|
||||
}
|
||||
NAL_SLICE;
|
||||
|
||||
typedef struct NAL * NAL;
|
||||
|
||||
bool nal_initialize (NAL * ptr);
|
||||
void nal_deinitialize(NAL this );
|
||||
bool nal_parse (NAL this, const uint8_t * src, size_t size, size_t * seek);
|
||||
|
||||
bool nal_get_primary_picture_type(NAL this, uint8_t * pic_type);
|
||||
bool nal_get_sps (NAL this, const NAL_SPS ** sps );
|
||||
bool nal_get_pps (NAL this, const NAL_PPS ** pps );
|
||||
bool nal_get_slice(NAL this, const NAL_SLICE ** slice);
|
||||
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})
|
||||
63
client/renderers/EGL/CMakeLists.txt
Normal file
63
client/renderers/EGL/CMakeLists.txt
Normal file
@@ -0,0 +1,63 @@
|
||||
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/desktop_yuv.frag
|
||||
shader/cursor.vert
|
||||
shader/cursor_rgb.frag
|
||||
shader/cursor_mono.frag
|
||||
shader/fps.vert
|
||||
shader/fps.frag
|
||||
shader/fps_bg.frag
|
||||
shader/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
|
||||
debug.c
|
||||
shader.c
|
||||
texture.c
|
||||
model.c
|
||||
desktop.c
|
||||
cursor.c
|
||||
fps.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}
|
||||
)
|
||||
218
client/renderers/EGL/alert.c
Normal file
218
client/renderers/EGL/alert.c
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
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 "utils.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))
|
||||
{
|
||||
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
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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,24 +17,17 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool ivshmem_connect(const char * unix_socket);
|
||||
void ivshmem_disconnect();
|
||||
bool ivshmem_process();
|
||||
#include "interface/font.h"
|
||||
|
||||
uint16_t ivshmem_get_id();
|
||||
void * ivshmem_get_map();
|
||||
size_t ivshmem_get_map_size();
|
||||
typedef struct EGL_Alert EGL_Alert;
|
||||
|
||||
enum IVSHMEMWaitResult
|
||||
{
|
||||
IVSHMEM_WAIT_RESULT_OK,
|
||||
IVSHMEM_WAIT_RESULT_TIMEOUT,
|
||||
IVSHMEM_WAIT_RESULT_ERROR
|
||||
};
|
||||
bool egl_alert_init(EGL_Alert ** alert, const LG_Font * font, LG_FontObj fontObj);
|
||||
void egl_alert_free(EGL_Alert ** alert);
|
||||
|
||||
enum IVSHMEMWaitResult ivshmem_wait_irq(uint16_t vector, unsigned int timeout);
|
||||
bool ivshmem_kick_irq(uint16_t clientID, uint16_t vector);
|
||||
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);
|
||||
289
client/renderers/EGL/cursor.c
Normal file
289
client/renderers/EGL/cursor.c
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
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 "utils.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 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;
|
||||
|
||||
// textures
|
||||
struct EGL_Texture * texture;
|
||||
struct EGL_Texture * textureMono;
|
||||
|
||||
// shaders
|
||||
struct EGL_Shader * shader;
|
||||
struct EGL_Shader * shaderMono;
|
||||
|
||||
// uniforms
|
||||
GLuint uMousePos;
|
||||
GLuint uMousePosMono;
|
||||
|
||||
// model
|
||||
struct EGL_Model * model;
|
||||
};
|
||||
|
||||
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_texture_init(&(*cursor)->texture))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_texture_init(&(*cursor)->textureMono))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor mono texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*cursor)->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_init(&(*cursor)->shaderMono))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor mono shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(
|
||||
(*cursor)->shader,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_rgb_frag, b_shader_cursor_rgb_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_shader_compile(
|
||||
(*cursor)->shaderMono,
|
||||
b_shader_cursor_vert , b_shader_cursor_vert_size,
|
||||
b_shader_cursor_mono_frag, b_shader_cursor_mono_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the cursor mono shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
(*cursor)->uMousePos = egl_shader_get_uniform_location((*cursor)->shader , "mouse");
|
||||
(*cursor)->uMousePosMono = egl_shader_get_uniform_location((*cursor)->shaderMono, "mouse");
|
||||
|
||||
if (!egl_model_init(&(*cursor)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the cursor model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*cursor)->model);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_cursor_free(EGL_Cursor ** cursor)
|
||||
{
|
||||
if (!*cursor)
|
||||
return;
|
||||
|
||||
LG_LOCK_FREE((*cursor)->lock);
|
||||
if ((*cursor)->data)
|
||||
free((*cursor)->data);
|
||||
|
||||
egl_texture_free(&(*cursor)->texture );
|
||||
egl_texture_free(&(*cursor)->textureMono);
|
||||
egl_shader_free (&(*cursor)->shader );
|
||||
egl_shader_free (&(*cursor)->shaderMono );
|
||||
egl_model_free (&(*cursor)->model );
|
||||
|
||||
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)
|
||||
{
|
||||
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->texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride, false);
|
||||
egl_texture_update(cursor->texture, data);
|
||||
egl_model_set_texture(cursor->model, cursor->texture);
|
||||
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->texture , EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
|
||||
egl_texture_setup (cursor->textureMono, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false);
|
||||
egl_texture_update(cursor->texture , (uint8_t *)and);
|
||||
egl_texture_update(cursor->textureMono, (uint8_t *)xor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
egl_shader_use(cursor->shader);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_model_set_texture(cursor->model, cursor->texture);
|
||||
egl_model_render(cursor->model);
|
||||
|
||||
egl_shader_use(cursor->shaderMono);
|
||||
glUniform4f(cursor->uMousePosMono, cursor->x, cursor->y, cursor->w, cursor->h / 2);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_set_texture(cursor->model, cursor->textureMono);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->shader);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
egl_shader_use(cursor->shaderMono);
|
||||
glUniform4f(cursor->uMousePos, cursor->x, cursor->y, cursor->w, cursor->h);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_model_render(cursor->model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
34
client/renderers/EGL/cursor.h
Normal file
34
client/renderers/EGL/cursor.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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);
|
||||
58
client/renderers/EGL/debug.c
Normal file
58
client/renderers/EGL/debug.c
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
||||
https://looking-glass.hostfission.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void egl_debug_printf(char * format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
|
||||
GLenum error = glGetError();
|
||||
switch(error)
|
||||
{
|
||||
case GL_NO_ERROR:
|
||||
fprintf(stderr, " (GL_NO_ERROR)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_ENUM:
|
||||
fprintf(stderr, " (GL_INVALID_ENUM)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_VALUE:
|
||||
fprintf(stderr, " (GL_INVALID_VALUE)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
fprintf(stderr, " (GL_INVALID_FRAMEBUFFER_OPERATION)\n");
|
||||
break;
|
||||
|
||||
case GL_OUT_OF_MEMORY:
|
||||
fprintf(stderr, " (GL_OUT_OF_MEMORY)\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
27
client/renderers/EGL/debug.h
Normal file
27
client/renderers/EGL/debug.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 <common/debug.h>
|
||||
|
||||
#define EGL_DEBUG_PRINT(type, fmt, ...) do {egl_debug_printf(type " %20s:%-4u | %-30s | " fmt, STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
#define EGL_ERROR(fmt, ...) EGL_DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
|
||||
|
||||
void egl_debug_printf(char * format, ...);
|
||||
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-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 "desktop.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "texture.h"
|
||||
#include "shader.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "desktop.vert.h"
|
||||
#include "desktop_rgb.frag.h"
|
||||
#include "desktop_yuv.frag.h"
|
||||
|
||||
struct DesktopShader
|
||||
{
|
||||
EGL_Shader * shader;
|
||||
GLint uDesktopPos;
|
||||
GLint uDesktopSize;
|
||||
GLint uNearest;
|
||||
GLint uNV, uNVGain;
|
||||
};
|
||||
|
||||
struct EGL_Desktop
|
||||
{
|
||||
EGL_Texture * texture;
|
||||
struct DesktopShader * shader; // the active shader
|
||||
EGL_Model * model;
|
||||
|
||||
// shader instances
|
||||
struct DesktopShader shader_generic;
|
||||
struct DesktopShader shader_yuv;
|
||||
|
||||
// internals
|
||||
LG_Lock updateLock;
|
||||
enum EGL_PixelFormat pixFmt;
|
||||
unsigned int width, height;
|
||||
unsigned int pitch;
|
||||
FrameBuffer frame;
|
||||
bool update;
|
||||
|
||||
// night vision
|
||||
KeybindHandle kbNV;
|
||||
int nvMax;
|
||||
int nvGain;
|
||||
};
|
||||
|
||||
// forwards
|
||||
void egl_desktop_toggle_nv(SDL_Scancode 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->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" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop)
|
||||
{
|
||||
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
|
||||
if (!*desktop)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc EGL_Desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(*desktop, 0, sizeof(EGL_Desktop));
|
||||
|
||||
if (!egl_texture_init(&(*desktop)->texture))
|
||||
{
|
||||
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_init_desktop_shader(
|
||||
&(*desktop)->shader_yuv,
|
||||
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
||||
b_shader_desktop_yuv_frag, b_shader_desktop_yuv_frag_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the yuv desktop shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_model_init(&(*desktop)->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop model");
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_model_set_default((*desktop)->model);
|
||||
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
|
||||
|
||||
LG_LOCK_INIT((*desktop)->updateLock);
|
||||
|
||||
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
|
||||
|
||||
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
|
||||
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void egl_desktop_toggle_nv(SDL_Scancode 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;
|
||||
|
||||
LG_LOCK_FREE((*desktop)->updateLock);
|
||||
|
||||
egl_texture_free(&(*desktop)->texture );
|
||||
egl_shader_free (&(*desktop)->shader_generic.shader);
|
||||
egl_shader_free (&(*desktop)->shader_yuv.shader );
|
||||
egl_model_free (&(*desktop)->model );
|
||||
|
||||
app_release_keybind(&(*desktop)->kbNV);
|
||||
|
||||
free(*desktop);
|
||||
*desktop = NULL;
|
||||
}
|
||||
|
||||
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame)
|
||||
{
|
||||
if (sourceChanged)
|
||||
{
|
||||
LG_LOCK(desktop->updateLock);
|
||||
switch(format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA:
|
||||
desktop->pixFmt = EGL_PF_BGRA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA:
|
||||
desktop->pixFmt = EGL_PF_RGBA;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_RGBA10:
|
||||
desktop->pixFmt = EGL_PF_RGBA10;
|
||||
desktop->shader = &desktop->shader_generic;
|
||||
break;
|
||||
|
||||
case FRAME_TYPE_YUV420:
|
||||
desktop->pixFmt = EGL_PF_YUV420;
|
||||
desktop->shader = &desktop->shader_yuv;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported frame format");
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
desktop->width = format.width;
|
||||
desktop->height = format.height;
|
||||
desktop->pitch = format.pitch;
|
||||
desktop->frame = frame;
|
||||
desktop->update = true;
|
||||
|
||||
/* defer the actual update as the format has changed and we need to issue GL commands first */
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* update the texture now */
|
||||
return egl_texture_update_from_frame(desktop->texture, frame);
|
||||
}
|
||||
|
||||
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
|
||||
{
|
||||
if (sourceChanged)
|
||||
{
|
||||
LG_LOCK(desktop->updateLock);
|
||||
if (!egl_texture_setup(
|
||||
desktop->texture,
|
||||
desktop->pixFmt,
|
||||
desktop->width,
|
||||
desktop->height,
|
||||
desktop->pitch,
|
||||
true // streaming texture
|
||||
))
|
||||
{
|
||||
DEBUG_ERROR("Failed to setup the desktop texture");
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
return;
|
||||
}
|
||||
LG_UNLOCK(desktop->updateLock);
|
||||
}
|
||||
|
||||
if (desktop->update)
|
||||
{
|
||||
desktop->update = false;
|
||||
egl_texture_update_from_frame(desktop->texture, desktop->frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest)
|
||||
{
|
||||
if (!desktop->shader)
|
||||
return false;
|
||||
|
||||
if (egl_texture_process(desktop->texture) != EGL_TEX_STATUS_OK)
|
||||
return false;
|
||||
|
||||
const struct DesktopShader * shader = desktop->shader;
|
||||
egl_shader_use(shader->shader);
|
||||
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
|
||||
glUniform1i(shader->uNearest , nearest ? 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);
|
||||
|
||||
egl_model_render(desktop->model);
|
||||
return true;
|
||||
}
|
||||
@@ -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,17 @@ 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>
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password);
|
||||
void spice_disconnect();
|
||||
bool spice_process();
|
||||
bool spice_ready();
|
||||
#include "interface/renderer.h"
|
||||
|
||||
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);
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
|
||||
bool egl_desktop_init(EGL_Desktop ** desktop);
|
||||
void egl_desktop_free(EGL_Desktop ** desktop);
|
||||
|
||||
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame);
|
||||
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
|
||||
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);
|
||||
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);
|
||||
569
client/renderers/EGL/egl.c
Normal file
569
client/renderers/EGL/egl.c
Normal file
@@ -0,0 +1,569 @@
|
||||
/*
|
||||
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 "utils.h"
|
||||
#include "dynamic/fonts.h"
|
||||
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "desktop.h"
|
||||
#include "cursor.h"
|
||||
#include "fps.h"
|
||||
#include "splash.h"
|
||||
#include "alert.h"
|
||||
|
||||
#define SPLASH_FADE_TIME 1000000
|
||||
#define ALERT_TIMEOUT 2000000
|
||||
|
||||
struct Options
|
||||
{
|
||||
bool vsync;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererParams params;
|
||||
struct Options opt;
|
||||
|
||||
EGLNativeDisplayType nativeDisp;
|
||||
EGLNativeWindowType nativeWind;
|
||||
EGLDisplay display;
|
||||
EGLConfig configs;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
|
||||
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
|
||||
|
||||
LG_RendererFormat format;
|
||||
bool sourceChanged;
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
bool showAlert;
|
||||
uint64_t alertTimeout;
|
||||
bool useCloseFlag;
|
||||
bool closeFlag;
|
||||
|
||||
int width, height;
|
||||
LG_RendererRect destRect;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
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 = true
|
||||
},
|
||||
{
|
||||
.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
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
void update_mouse_shape(struct Inst * this);
|
||||
|
||||
const char * egl_get_name()
|
||||
{
|
||||
return "EGL";
|
||||
}
|
||||
|
||||
void egl_setup()
|
||||
{
|
||||
option_register(egl_options);
|
||||
}
|
||||
|
||||
bool egl_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));
|
||||
|
||||
// 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->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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_initialize(void * opaque, Uint32 * sdlFlags)
|
||||
{
|
||||
const bool doubleBuffer = option_get_bool("egl", "doubleBuffer");
|
||||
DEBUG_INFO("Double buffering is %s", doubleBuffer ? "on" : "off");
|
||||
|
||||
*sdlFlags = SDL_WINDOW_OPENGL;
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , doubleBuffer ? 1 : 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
if (option_get_bool("egl", "multisample"))
|
||||
{
|
||||
int maxSamples = sysinfo_gfx_max_multisample();
|
||||
if (maxSamples > 1)
|
||||
{
|
||||
if (maxSamples > 4)
|
||||
maxSamples = 4;
|
||||
|
||||
DEBUG_INFO("Multsampling enabled, max samples: %d", maxSamples);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->font && this->fontObj)
|
||||
this->font->destroy(this->fontObj);
|
||||
|
||||
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 );
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
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;
|
||||
}
|
||||
|
||||
this->mouseScaleX = 2.0f / this->format.width ;
|
||||
this->mouseScaleY = 2.0f / this->format.height;
|
||||
egl_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
|
||||
);
|
||||
|
||||
this->splashRatio = (float)width / (float)height;
|
||||
this->screenScaleX = 1.0f / width;
|
||||
this->screenScaleY = 1.0f / height;
|
||||
|
||||
egl_cursor_set_state(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
);
|
||||
}
|
||||
|
||||
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_cursor_set_size(this->cursor,
|
||||
(this->mouseWidth * (1.0f / this->format.width )) * this->scaleX,
|
||||
(this->mouseHeight * (1.0f / this->format.height)) * this->scaleY
|
||||
);
|
||||
|
||||
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_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
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->sourceChanged = (
|
||||
this->sourceChanged ||
|
||||
this->format.type != format.type ||
|
||||
this->format.width != format.width ||
|
||||
this->format.height != format.height ||
|
||||
this->format.pitch != format.pitch
|
||||
);
|
||||
|
||||
if (this->sourceChanged)
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
this->useNearest = this->width < format.width || this->height < format.height;
|
||||
|
||||
if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, frame))
|
||||
{
|
||||
DEBUG_INFO("Failed to prepare to update the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool egl_render_startup(void * opaque, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
{
|
||||
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
int width, height;
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
this->nativeDisp = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->display = eglGetDisplay(this->nativeDisp);
|
||||
if (this->display == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("eglGetDisplay failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eglInitialize(this->display, NULL, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Unable to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 32,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SAMPLE_BUFFERS , 1,
|
||||
EGL_SAMPLES , 4,
|
||||
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;
|
||||
}
|
||||
|
||||
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, NULL);
|
||||
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;
|
||||
}
|
||||
|
||||
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
|
||||
|
||||
DEBUG_INFO("Vendor : %s", glGetString(GL_VENDOR ));
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
|
||||
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
||||
|
||||
if (!egl_desktop_init(&this->desktop))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_render(void * opaque, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
|
||||
{
|
||||
if (!this->waitFadeTime)
|
||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||
egl_cursor_render(this->cursor);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
eglSwapBuffers(this->display, this->surface);
|
||||
|
||||
// defer texture uploads until after the flip to avoid stalling
|
||||
egl_desktop_perform_update(this->desktop, this->sourceChanged);
|
||||
|
||||
this->sourceChanged = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->params.showFPS)
|
||||
return;
|
||||
|
||||
egl_fps_update(this->fps, avgUPS, avgFPS);
|
||||
}
|
||||
|
||||
struct LG_Renderer LGR_EGL =
|
||||
{
|
||||
.get_name = egl_get_name,
|
||||
.setup = egl_setup,
|
||||
.create = egl_create,
|
||||
.initialize = egl_initialize,
|
||||
.deinitialize = egl_deinitialize,
|
||||
.on_resize = egl_on_resize,
|
||||
.on_mouse_shape = egl_on_mouse_shape,
|
||||
.on_mouse_event = egl_on_mouse_event,
|
||||
.on_frame_event = egl_on_frame_event,
|
||||
.on_alert = egl_on_alert,
|
||||
.render_startup = egl_render_startup,
|
||||
.render = egl_render,
|
||||
.update_fps = egl_update_fps
|
||||
};
|
||||
190
client/renderers/EGL/fps.c
Normal file
190
client/renderers/EGL/fps.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
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 "utils.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 ready;
|
||||
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))
|
||||
{
|
||||
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_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
egl_texture_setup(
|
||||
fps->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false
|
||||
);
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
fps->texture,
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
fps->width = bmp->width;
|
||||
fps->height = bmp->height;
|
||||
fps->ready = true;
|
||||
|
||||
fps->font->release(fps->fontObj, bmp);
|
||||
}
|
||||
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
{
|
||||
if (!fps->ready)
|
||||
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);
|
||||
}
|
||||
32
client/renderers/EGL/fps.h
Normal file
32
client/renderers/EGL/fps.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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_update(EGL_FPS * fps, const float avgUPS, const float avgFPS);
|
||||
void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY);
|
||||
219
client/renderers/EGL/model.c
Normal file
219
client/renderers/EGL/model.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
|
||||
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 "utils.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <SDL2/SDL_egl.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);
|
||||
225
client/renderers/EGL/shader.c
Normal file
225
client/renderers/EGL/shader.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
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 "utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <SDL2/SDL_egl.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 (!file_get_contents(vertex_file, &vertex_code, &vertex_size))
|
||||
{
|
||||
DEBUG_ERROR("Failed to read vertex shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Loaded vertex shader: %s", vertex_file);
|
||||
|
||||
if (!file_get_contents(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;
|
||||
}
|
||||
25
client/renderers/EGL/shader/cursor.vert
Normal file
25
client/renderers/EGL/shader/cursor.vert
Normal file
@@ -0,0 +1,25 @@
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
layout(location = 1) in vec2 vertexUV;
|
||||
|
||||
uniform vec4 mouse;
|
||||
|
||||
out highp vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position.xyz = vertexPosition_modelspace;
|
||||
gl_Position.w = 1.0;
|
||||
|
||||
gl_Position.x += 1.0f;
|
||||
gl_Position.y -= 1.0f;
|
||||
|
||||
gl_Position.x *= mouse.z;
|
||||
gl_Position.y *= mouse.w;
|
||||
|
||||
gl_Position.x += mouse.x;
|
||||
gl_Position.y -= mouse.y;
|
||||
|
||||
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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user