mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
Compare commits
1010 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6000b85b1a | ||
|
|
d661568536 | ||
|
|
c4e3687c01 | ||
|
|
a6b8371178 | ||
|
|
f546ee8551 | ||
|
|
db28239939 | ||
|
|
9f87401173 | ||
|
|
4100f790ee | ||
|
|
a4acaf15b0 | ||
|
|
0de52efdb1 | ||
|
|
1d242aed7c | ||
|
|
4d3c9b0be8 | ||
|
|
8164c09ea0 | ||
|
|
b464902f25 | ||
|
|
844f041a58 | ||
|
|
de628e843e | ||
|
|
de6779ed83 | ||
|
|
2afa3a4390 | ||
|
|
094ab4eb1a | ||
|
|
c184f16e28 | ||
|
|
15aca474eb | ||
|
|
d963bd1daa | ||
|
|
d38392aea0 | ||
|
|
b7ef109d95 | ||
|
|
dc8bda0e1b | ||
|
|
7cc5e1345c | ||
|
|
49147694de | ||
|
|
58a71bf3e7 | ||
|
|
5ac84fd03f | ||
|
|
1439c8d951 | ||
|
|
ce9934ca52 | ||
|
|
7451c1bd17 | ||
|
|
7340f7660b | ||
|
|
a9af8472df | ||
|
|
b5fa9bb1be | ||
|
|
f850b037bc | ||
|
|
da3c39e9e3 | ||
|
|
7d4df19cdc | ||
|
|
d19c883067 | ||
|
|
42eda48ce4 | ||
|
|
fd0dc9ab8e | ||
|
|
1415c8b200 | ||
|
|
8ba3c073e7 | ||
|
|
92f1313993 | ||
|
|
2b3c514aad | ||
|
|
819501c4e7 | ||
|
|
3b2226c4ea | ||
|
|
00405892cb | ||
|
|
3cd3d261e9 | ||
|
|
4f07672cfa | ||
|
|
25e21ee807 | ||
|
|
270d4ddffe | ||
|
|
46dede3381 | ||
|
|
7019756fa1 | ||
|
|
a1d8cc2296 | ||
|
|
c61b31e8a6 | ||
|
|
802e5c79fa | ||
|
|
6b64149591 | ||
|
|
8fc8884dbc | ||
|
|
2b76111e51 | ||
|
|
54221ae938 | ||
|
|
f07171315f | ||
|
|
f4074341a5 | ||
|
|
9c4763af75 | ||
|
|
6dc10a4d60 | ||
|
|
4b332d6fa0 | ||
|
|
12a361a3d9 | ||
|
|
840013ec78 | ||
|
|
e45452b9bc | ||
|
|
8509260194 | ||
|
|
2a13697fef | ||
|
|
bd51a7d5fa | ||
|
|
1cf0360520 | ||
|
|
4a0d632c6e | ||
|
|
d336de68a2 | ||
|
|
1175a3184e | ||
|
|
dde56c2bb7 | ||
|
|
7da652e299 | ||
|
|
3663e35ecf | ||
|
|
e78ec5c2ce | ||
|
|
70ae7a2fa9 | ||
|
|
651d75fce7 | ||
|
|
c3dfcc1f11 | ||
|
|
a02d314dde | ||
|
|
b5fd69a9ed | ||
|
|
2d969c2ccc | ||
|
|
211b25848f | ||
|
|
d051b58190 | ||
|
|
ab0a6b5fa6 | ||
|
|
0c827690ec | ||
|
|
f5d7cf7692 | ||
|
|
dd73f45175 | ||
|
|
e86207606a | ||
|
|
ca21e6a8ac | ||
|
|
9990b9b04b | ||
|
|
341bc98730 | ||
|
|
1a1cf20152 | ||
|
|
7a5890469c | ||
|
|
65c4f01998 | ||
|
|
676b5559ec | ||
|
|
e9b46201fd | ||
|
|
9b4fc8a270 | ||
|
|
7cb0286414 | ||
|
|
3b41888c66 | ||
|
|
4a65d2cafc | ||
|
|
34f1e89366 | ||
|
|
91cf3bcdcc | ||
|
|
e7ec6050d9 | ||
|
|
0f3eaf0f84 | ||
|
|
c75efd3999 | ||
|
|
f2934c620b | ||
|
|
4f6b001483 | ||
|
|
de3c37969f | ||
|
|
c719ddfc31 | ||
|
|
a5e56cf47d | ||
|
|
84fd2c5766 | ||
|
|
68ba1db24a | ||
|
|
ca00949851 | ||
|
|
88925c705f | ||
|
|
c2752262e5 | ||
|
|
496e51d93a | ||
|
|
6c04065ba7 | ||
|
|
fff419a7ff | ||
|
|
a3e3da3136 | ||
|
|
49bfd2ba1f | ||
|
|
93aebf461a | ||
|
|
c409022df5 | ||
|
|
c4441db606 | ||
|
|
0d0496adf3 | ||
|
|
ca6f02c953 | ||
|
|
8b86abfceb | ||
|
|
5135c16bda | ||
|
|
e6d6d47be1 | ||
|
|
639af4f08a | ||
|
|
12bf5cffc5 | ||
|
|
6741997e59 | ||
|
|
369a941c48 | ||
|
|
2a69385aed | ||
|
|
31f7faa6a5 | ||
|
|
dc4393a583 | ||
|
|
5eb3e7c75f | ||
|
|
6858f9c8d4 | ||
|
|
af342c8cc9 | ||
|
|
4e4a5c8df8 | ||
|
|
cc2d982ceb | ||
|
|
e2bef750b4 | ||
|
|
ffdcf0fea8 | ||
|
|
2ea414d1d4 | ||
|
|
c4977b5393 | ||
|
|
13c2dce3f1 | ||
|
|
b0a02a5985 | ||
|
|
b0df4eabd1 | ||
|
|
ceb7f5f41a | ||
|
|
aa46b8e293 | ||
|
|
63a83cdf7a | ||
|
|
bd033f9e15 | ||
|
|
62ca9c31a0 | ||
|
|
e8ef708034 | ||
|
|
ec450be8b5 | ||
|
|
d4df041210 | ||
|
|
ced0029abc | ||
|
|
a9cf0ebf18 | ||
|
|
3d6611fd50 | ||
|
|
74736c6b76 | ||
|
|
0e0b2ce366 | ||
|
|
3b49deac25 | ||
|
|
734400dc77 | ||
|
|
ef9abf2f15 | ||
|
|
b286c06144 | ||
|
|
521335c387 | ||
|
|
780339d91b | ||
|
|
b332633b07 | ||
|
|
4cd1014bac | ||
|
|
7a1e5f43d9 | ||
|
|
c4054c75a7 | ||
|
|
f36d81954b | ||
|
|
eacae3954e | ||
|
|
56cf3e4ede | ||
|
|
293ee266af | ||
|
|
536df759ae | ||
|
|
4bdf8375cc | ||
|
|
a6921fffad | ||
|
|
1ad0c93ad8 | ||
|
|
97f333c970 | ||
|
|
b854807d40 | ||
|
|
a9b9d1bd09 | ||
|
|
0044ec1d52 | ||
|
|
9c976a23de | ||
|
|
9930880ee6 | ||
|
|
82e1b61a31 | ||
|
|
5bb197ccfc | ||
|
|
20c0426ace | ||
|
|
0a9cc511ed | ||
|
|
2a7af03f9a | ||
|
|
7910ac78a5 | ||
|
|
67762aa445 | ||
|
|
7f22774e08 | ||
|
|
cbafe09396 | ||
|
|
615f7e5875 | ||
|
|
3f309832f7 | ||
|
|
719f4cef59 | ||
|
|
da582198db | ||
|
|
04a2ecaff4 | ||
|
|
3b8b4653e9 | ||
|
|
ea27955b16 | ||
|
|
e223ba53f9 | ||
|
|
907d0bfcd5 | ||
|
|
a014391ae7 | ||
|
|
84584c950b | ||
|
|
f1d82a7d09 | ||
|
|
01155ef4a3 | ||
|
|
88cf616a48 | ||
|
|
b3cdadca86 | ||
|
|
3bd9f6b710 | ||
|
|
e28cc5e645 | ||
|
|
045970bcc5 | ||
|
|
6fca2c593c | ||
|
|
e61bbe87b4 | ||
|
|
80ebc9982e | ||
|
|
9a2689692a | ||
|
|
a31c6c4cd0 | ||
|
|
1f79f4ed12 | ||
|
|
6da0483951 | ||
|
|
3abb199697 | ||
|
|
4c7afc05c7 | ||
|
|
222e53781d | ||
|
|
8d4bd87ad2 | ||
|
|
861331be1c | ||
|
|
2fa2afed56 | ||
|
|
5e910b5fab | ||
|
|
1520cb7bf9 | ||
|
|
4b658cd86b | ||
|
|
f6311ceb3f | ||
|
|
9b2321a722 | ||
|
|
1e91356155 | ||
|
|
fb7a3a0758 | ||
|
|
c2d4ffdaed | ||
|
|
c5e6b84e3d | ||
|
|
68f35f6ab1 | ||
|
|
2b450839a1 | ||
|
|
df3e7c5ca1 | ||
|
|
a4fa42fa96 | ||
|
|
d6183430ef | ||
|
|
f54a32feac | ||
|
|
37edcb1b55 | ||
|
|
320b691086 | ||
|
|
5163ec93b4 | ||
|
|
2be7790415 | ||
|
|
27d2735454 | ||
|
|
361e5f7ea7 | ||
|
|
4a483078ad | ||
|
|
4dff0c8384 | ||
|
|
543254691b | ||
|
|
96205dc48d | ||
|
|
ec5ea7cffb | ||
|
|
8bb7a63479 | ||
|
|
95302485a0 | ||
|
|
7a1347c1f5 | ||
|
|
a78ad20142 | ||
|
|
9dd6576b26 | ||
|
|
cf8a843b03 | ||
|
|
8b423315d0 | ||
|
|
7417430659 | ||
|
|
a494bfa1e9 | ||
|
|
e239751f50 | ||
|
|
91465ed257 | ||
|
|
830fa6567b | ||
|
|
f347089312 | ||
|
|
a577f76d71 | ||
|
|
89f05711e0 | ||
|
|
b19803564c | ||
|
|
a9988cb346 | ||
|
|
b288993b86 | ||
|
|
37e608a889 | ||
|
|
067214a9f9 | ||
|
|
b5896c8f26 | ||
|
|
9f4ad530ca | ||
|
|
c6d489da11 | ||
|
|
c3d498e30c | ||
|
|
bd8ba66023 | ||
|
|
cf859b7c13 | ||
|
|
3b2c70422a | ||
|
|
a9ebdf6785 | ||
|
|
8164b514e1 | ||
|
|
398bf9819e | ||
|
|
59959d6397 | ||
|
|
cbbd813c5a | ||
|
|
f4344ade53 | ||
|
|
8e1b718d7f | ||
|
|
ed62a1f93d | ||
|
|
592cb002bd | ||
|
|
ebc581e165 | ||
|
|
c7a3f7f46e | ||
|
|
41df9c658b | ||
|
|
4bada2d9f6 | ||
|
|
83da80f631 | ||
|
|
2bcb713b82 | ||
|
|
e5239583b5 | ||
|
|
4126a1a98e | ||
|
|
abf2a5ea4e | ||
|
|
a9bbadf8ad | ||
|
|
38dcdcba2f | ||
|
|
4a50fcafd8 | ||
|
|
67f35dfc5f | ||
|
|
de9136c61e | ||
|
|
dd7736e806 | ||
|
|
720f44c03b | ||
|
|
3981ca0840 | ||
|
|
9f707f9015 | ||
|
|
1b6bf8fa34 | ||
|
|
41e6344995 | ||
|
|
5993deadb2 | ||
|
|
7306a3468d | ||
|
|
1513880f63 | ||
|
|
41044ab97f | ||
|
|
1b392e3921 | ||
|
|
870b6e2675 | ||
|
|
ac7fcfe056 | ||
|
|
55ac123d03 | ||
|
|
c748980f58 | ||
|
|
d1a41ad8fe | ||
|
|
db6e2a57ad | ||
|
|
f2c53a52c2 | ||
|
|
adfb3619c3 | ||
|
|
8781fbcc20 | ||
|
|
2c0873332c | ||
|
|
a2c45f8de0 | ||
|
|
cefcaed358 | ||
|
|
6ba4b56f32 | ||
|
|
bfda8558ea | ||
|
|
cc5c1a9b90 | ||
|
|
c5d782dac5 | ||
|
|
60d63fcd21 | ||
|
|
8f99d6cf01 | ||
|
|
4c6a0cdc37 | ||
|
|
0cad1bf6a5 | ||
|
|
95a478e55e | ||
|
|
41a30b5826 | ||
|
|
b1030b6601 | ||
|
|
5dfd5d8027 | ||
|
|
51b7dc5b5c | ||
|
|
7bfbdd5bc0 | ||
|
|
aa3137c0a9 | ||
|
|
c2027a5481 | ||
|
|
909e36b80d | ||
|
|
4fa2624495 | ||
|
|
a97c88c45b | ||
|
|
c579f638fc | ||
|
|
65fdf8f6d1 | ||
|
|
f6fd1f7e84 | ||
|
|
732f5bf21d | ||
|
|
6d2d3eca18 | ||
|
|
997738816d | ||
|
|
41906b6fab | ||
|
|
c910de12ff | ||
|
|
2baed357f2 | ||
|
|
ee513939aa | ||
|
|
b843ab7b97 | ||
|
|
8c37fa8d8b | ||
|
|
a46a14c6ac | ||
|
|
2fec2bfc51 | ||
|
|
ea1e6f20a8 | ||
|
|
aeb72cba02 | ||
|
|
279fb8803e | ||
|
|
59394e23f4 | ||
|
|
8f78b599ae | ||
|
|
cc4ec1b526 | ||
|
|
18a977fdf1 | ||
|
|
bc945df0a7 | ||
|
|
32867d1bbf | ||
|
|
c844290c81 | ||
|
|
4a1f5194cc | ||
|
|
0db8e634a8 | ||
|
|
8309ee965a | ||
|
|
0fa4a89223 | ||
|
|
9350afbe6a | ||
|
|
7ce9d27a67 | ||
|
|
83a4440cad | ||
|
|
85cd54fdfe | ||
|
|
c3eabebd91 | ||
|
|
26aff26eb0 | ||
|
|
07b974d638 | ||
|
|
75cdac5dbf | ||
|
|
7c6fecf13d | ||
|
|
7c96ee1e00 | ||
|
|
975c4e6b26 | ||
|
|
c4838424db | ||
|
|
e07279707a | ||
|
|
1e0db7df4e | ||
|
|
b41cbc8ab4 | ||
|
|
906870d36f | ||
|
|
95baeed75e | ||
|
|
a622c1591e | ||
|
|
030269b596 | ||
|
|
5ff5d46ba5 | ||
|
|
d1ab44912b | ||
|
|
23c6e455fe | ||
|
|
7b5ac1c869 | ||
|
|
143ca274f5 | ||
|
|
7d51a4bfbf | ||
|
|
23e64f23a8 | ||
|
|
a5e2df11eb | ||
|
|
4406f7428b | ||
|
|
17770fe130 | ||
|
|
ff47a13bc3 | ||
|
|
ed38b7e86c | ||
|
|
3f2ee64293 | ||
|
|
2512e7f445 | ||
|
|
9fbfca5699 | ||
|
|
1039160d3a | ||
|
|
214478760b | ||
|
|
c49cbd3c3c | ||
|
|
de8cff072d | ||
|
|
2b0e1fb9dc | ||
|
|
f6d9061441 | ||
|
|
71b5495569 | ||
|
|
0fa68397b7 | ||
|
|
2561886189 | ||
|
|
b73ddc232b | ||
|
|
65b9b653d0 | ||
|
|
ca8917dd1b | ||
|
|
bbdf9618ea | ||
|
|
23f9474e9e | ||
|
|
69642d2db3 | ||
|
|
47a7c5e27b | ||
|
|
502bf8ebf8 | ||
|
|
ecba80372d | ||
|
|
f272b865cd | ||
|
|
89cfbc6bd3 | ||
|
|
e2d3fef9db | ||
|
|
c5b8deff41 | ||
|
|
8478ccca5d | ||
|
|
a268cb552d | ||
|
|
1b0978f252 | ||
|
|
8730980ab8 | ||
|
|
caf70d703e | ||
|
|
95ced89d5e | ||
|
|
e4c9c292e5 | ||
|
|
fe7a7f4d77 | ||
|
|
cb0b32f90a | ||
|
|
9e93bb2dd8 | ||
|
|
0e6c41f823 | ||
|
|
725212b8a4 | ||
|
|
a275ee6b65 | ||
|
|
997095ce39 | ||
|
|
8b9cd51134 | ||
|
|
e7cbfb3ee9 | ||
|
|
1f7bdb1b2a | ||
|
|
9f14b2278d | ||
|
|
4177b02064 | ||
|
|
847d7b6980 | ||
|
|
ef1114512f | ||
|
|
38c36c00c9 | ||
|
|
846820f4ec | ||
|
|
c39e90b9b0 | ||
|
|
096d7587c3 | ||
|
|
27f5bb60e5 | ||
|
|
70ed5ce829 | ||
|
|
e58dc8e446 | ||
|
|
60e3538adc | ||
|
|
e2fb02323c | ||
|
|
57c7945847 | ||
|
|
68c723e135 | ||
|
|
0241414dfb | ||
|
|
c5a356f3e7 | ||
|
|
deecd19af2 | ||
|
|
f4cb13aa0e | ||
|
|
4385014049 | ||
|
|
fcb84132f4 | ||
|
|
50bd3094b4 | ||
|
|
d7cb71f877 | ||
|
|
e287404992 | ||
|
|
d332f11101 | ||
|
|
f78a9e2ccf | ||
|
|
bd7294e672 | ||
|
|
b3304a059d | ||
|
|
d0529fb234 | ||
|
|
bb912786bd | ||
|
|
8f47190ffc | ||
|
|
196f1f7471 | ||
|
|
e3cc25f23f | ||
|
|
a963b31c1d | ||
|
|
4d1689d6e9 | ||
|
|
fed329568c | ||
|
|
e3f642ac25 | ||
|
|
c94d922282 | ||
|
|
7563a0fa1f | ||
|
|
f5db0e438b | ||
|
|
9982ae4675 | ||
|
|
69eba9c493 | ||
|
|
2c67d73f08 | ||
|
|
7f4b9cf36c | ||
|
|
9c0ea17ada | ||
|
|
91a6e55f74 | ||
|
|
fea650fbe4 | ||
|
|
35e0497f86 | ||
|
|
fbc3735f44 | ||
|
|
9be80c103e | ||
|
|
640f2b1c55 | ||
|
|
d8df57130a | ||
|
|
3ba0533773 | ||
|
|
b1224032a1 | ||
|
|
1a7ed53559 | ||
|
|
9e9487617a | ||
|
|
d68b8138a5 | ||
|
|
10ca72f934 | ||
|
|
9dbf0df67d | ||
|
|
b072af3082 | ||
|
|
35c8b74bb4 | ||
|
|
7fbe9ba34f | ||
|
|
81c6ca5915 | ||
|
|
d73a4a38e5 | ||
|
|
6309f478a2 | ||
|
|
4ca2c0e740 | ||
|
|
ea9ada8d20 | ||
|
|
302b61090b | ||
|
|
75d224cd67 | ||
|
|
2ff9a129c0 | ||
|
|
a1a4ce1af8 | ||
|
|
44ac873100 | ||
|
|
3d179a869a | ||
|
|
530f2de79e | ||
|
|
512e581d56 | ||
|
|
e5c2ad2069 | ||
|
|
6af961199e | ||
|
|
276aa23a61 | ||
|
|
e33a7ff53b | ||
|
|
d9d50d8943 | ||
|
|
7c753e2289 | ||
|
|
ff81a286bb | ||
|
|
c7c3ac7c9c | ||
|
|
74f673e23c | ||
|
|
7c51fbfd18 | ||
|
|
7839ff8057 | ||
|
|
8b91e168e5 | ||
|
|
08f056bb3f | ||
|
|
808af7fce1 | ||
|
|
090d353f3d | ||
|
|
c4b371ccc9 | ||
|
|
06a1119512 | ||
|
|
b80c4253f0 | ||
|
|
3f306de5e1 | ||
|
|
39f9506446 | ||
|
|
00de30d336 | ||
|
|
b59df48dde | ||
|
|
3840b3c561 | ||
|
|
b5424ec671 | ||
|
|
ec184fb2ab | ||
|
|
4a750c7b16 | ||
|
|
f38bbf90bb | ||
|
|
05dcff4f87 | ||
|
|
b458118e34 | ||
|
|
7a920da06d | ||
|
|
ec84f43b80 | ||
|
|
03d572fe10 | ||
|
|
c41df22c88 | ||
|
|
3b3b9e2bd9 | ||
|
|
ebf9dbe931 | ||
|
|
0b3b78891b | ||
|
|
12af30b75b | ||
|
|
a9775a0686 | ||
|
|
ca9809ca7e | ||
|
|
97e14de63e | ||
|
|
0f4cdc2d21 | ||
|
|
869b6ed89d | ||
|
|
1876de0a68 | ||
|
|
019d064d8e | ||
|
|
d6b45eb825 | ||
|
|
556a6aaf33 | ||
|
|
fc7ec9e538 | ||
|
|
1f239214a1 | ||
|
|
9e224987f7 | ||
|
|
81317ce672 | ||
|
|
fce23c482c | ||
|
|
1d2e042240 | ||
|
|
d852bb82a3 | ||
|
|
fdb7754043 | ||
|
|
62c3faaacb | ||
|
|
b58279beea | ||
|
|
898d62bad9 | ||
|
|
446c8ace82 | ||
|
|
e007ee9976 | ||
|
|
6721155155 | ||
|
|
b80619fb0f | ||
|
|
e75e26467f | ||
|
|
2187f0b198 | ||
|
|
3382a5d03c | ||
|
|
fff9145680 | ||
|
|
13e57bb183 | ||
|
|
948500ae41 | ||
|
|
17bcdd7902 | ||
|
|
03837a9308 | ||
|
|
b8bfc13cf9 | ||
|
|
24403cdd25 | ||
|
|
13d0106feb | ||
|
|
dce64fc487 | ||
|
|
0629fe7846 | ||
|
|
315ec47523 | ||
|
|
9ed2141fc8 | ||
|
|
5b40c5008f | ||
|
|
62db9ae01a | ||
|
|
8c2cd2f60d | ||
|
|
0812f6f3c3 | ||
|
|
065e9e718a | ||
|
|
27f1dc318b | ||
|
|
ac5f439839 | ||
|
|
b4255e22aa | ||
|
|
d15c1af152 | ||
|
|
0140989f3a | ||
|
|
793e329fa5 | ||
|
|
6702802829 | ||
|
|
dae9a67fe2 | ||
|
|
b8e11b1272 | ||
|
|
f08b6abc96 | ||
|
|
965812bc19 | ||
|
|
7f643345ce | ||
|
|
28fecbc50c | ||
|
|
6c11e9f27d | ||
|
|
2af179630a | ||
|
|
c243e82047 | ||
|
|
3de77b1849 | ||
|
|
37d09b0bdb | ||
|
|
9a5d23ebde | ||
|
|
da3ed5ff79 | ||
|
|
2e8b462fe5 | ||
|
|
87b2b708f8 | ||
|
|
7ddf8bbe94 | ||
|
|
a87782b025 | ||
|
|
c39f2b0c1f | ||
|
|
a07ad3e479 | ||
|
|
c58f95b79e | ||
|
|
d9f7952710 | ||
|
|
ab70a056fc | ||
|
|
844ba53f54 | ||
|
|
63b920cd7b | ||
|
|
2a5fd78789 | ||
|
|
bc52fe9b82 | ||
|
|
667c113d5b | ||
|
|
38915859ba | ||
|
|
70d0be4b9a | ||
|
|
dad446ea41 | ||
|
|
d283900e43 | ||
|
|
4b043e31fe | ||
|
|
f65882e42a | ||
|
|
1fda8f6219 | ||
|
|
620c629bb4 | ||
|
|
a1f63da057 | ||
|
|
5ff5942258 | ||
|
|
8c803e6a34 | ||
|
|
a93a640d42 | ||
|
|
62abd1ee19 | ||
|
|
d55c6ee84c | ||
|
|
d3419697a5 | ||
|
|
a7171d77db | ||
|
|
fccb8d080d | ||
|
|
24acfabe07 | ||
|
|
61be0eb051 | ||
|
|
c4b4393177 | ||
|
|
388e4696ff | ||
|
|
43dc7cf7c0 | ||
|
|
e8feb00b33 | ||
|
|
42472bec85 | ||
|
|
769c4c8f99 | ||
|
|
049044f768 | ||
|
|
971c784f14 | ||
|
|
747ba7f23a | ||
|
|
4a3f3622b8 | ||
|
|
7d2705424a | ||
|
|
b228fd371c | ||
|
|
cbd2a8269c | ||
|
|
7f9467d759 | ||
|
|
9cf7c14a26 | ||
|
|
f09aea81fe | ||
|
|
8249d41848 | ||
|
|
d33f742241 | ||
|
|
6d0499b0e3 | ||
|
|
fd7a6d070b | ||
|
|
3d81b0fe7a | ||
|
|
1d37a19468 | ||
|
|
cc3cf60015 | ||
|
|
8d4e55fcbb | ||
|
|
25aa8b41a5 | ||
|
|
667878d2eb | ||
|
|
53a9f365ce | ||
|
|
a587338701 | ||
|
|
a52f547726 | ||
|
|
136e9c43a5 | ||
|
|
616f62fb9f | ||
|
|
9d10eaeef4 | ||
|
|
f23fcee382 | ||
|
|
21b1a4a336 | ||
|
|
b346ac2eb0 | ||
|
|
db5a9363ba | ||
|
|
0a637b2272 | ||
|
|
169a53b568 | ||
|
|
109b48a108 | ||
|
|
118707f37a | ||
|
|
e169259f6f | ||
|
|
355df9a615 | ||
|
|
96cfb1f368 | ||
|
|
545cca792b | ||
|
|
ae04c4afbb | ||
|
|
e8fadd2848 | ||
|
|
beb6bdfadb | ||
|
|
8bf4ade9d8 | ||
|
|
ea928c53b4 | ||
|
|
c1697c8562 | ||
|
|
57c4986f0e | ||
|
|
0cc7e35ed9 | ||
|
|
d6eceaf0dc | ||
|
|
cf7eb14573 | ||
|
|
ed5de34800 | ||
|
|
27d4f6063f | ||
|
|
62662edc8d | ||
|
|
b67600962a | ||
|
|
1eda45a81a | ||
|
|
926e95f527 | ||
|
|
029194cb60 | ||
|
|
7e3abefc2c | ||
|
|
cf9eb961fc | ||
|
|
0a05c28df8 | ||
|
|
0034438c9e | ||
|
|
2691489dab | ||
|
|
9e7ecf8db2 | ||
|
|
32e6054435 | ||
|
|
bd2d846557 | ||
|
|
0932d8bab9 | ||
|
|
82ac3becd8 | ||
|
|
7b86ea9e87 | ||
|
|
63d15333f2 | ||
|
|
ac862f9157 | ||
|
|
23b09e3d41 | ||
|
|
4024378772 | ||
|
|
64e1132579 | ||
|
|
6749b64ed5 | ||
|
|
0c43b83598 | ||
|
|
c47e3cb020 | ||
|
|
f3b07efaf3 | ||
|
|
02360f7aef | ||
|
|
c21301a423 | ||
|
|
64a6c1419b | ||
|
|
6f95d360b5 | ||
|
|
71cef4b700 | ||
|
|
56bb4ca865 | ||
|
|
fa0ff91d23 | ||
|
|
13caabaec8 | ||
|
|
8f2e00f31b | ||
|
|
78244a48d2 | ||
|
|
8be7c563a9 | ||
|
|
3fe134d8a6 | ||
|
|
f4c145136c | ||
|
|
ee10a795bd | ||
|
|
8e32c8e6f4 | ||
|
|
30f7742f51 | ||
|
|
55ce46e6ed | ||
|
|
9e03cdda42 | ||
|
|
76f34be395 | ||
|
|
ee89b211de | ||
|
|
0c5daa44a1 | ||
|
|
5495c8367a | ||
|
|
988bf33b40 | ||
|
|
e53f65c305 | ||
|
|
1afd1b7c94 | ||
|
|
33fbd5f431 | ||
|
|
17d3fcf0d0 | ||
|
|
cf77b301bc | ||
|
|
9d52174d6b | ||
|
|
e2874b40c1 | ||
|
|
914f329eea | ||
|
|
877c739f1b | ||
|
|
61b195e9f4 | ||
|
|
c53564dd31 | ||
|
|
9eb4eadda6 | ||
|
|
44ebc4846b | ||
|
|
3a3d4928f9 | ||
|
|
84c55d6efc | ||
|
|
7f636a25a8 | ||
|
|
825a706139 | ||
|
|
4df57a722e | ||
|
|
45f9ce3a20 | ||
|
|
f2863cceb7 | ||
|
|
6b9ed7fcb3 | ||
|
|
f33a587218 | ||
|
|
bdb9ed9001 | ||
|
|
41911a26fb | ||
|
|
0942f2ee7f | ||
|
|
a05a3b355c | ||
|
|
ff1722bbd1 | ||
|
|
dc281119af | ||
|
|
b848db8f2b | ||
|
|
bc261fddf2 | ||
|
|
bb251d5046 | ||
|
|
ec9481392a | ||
|
|
8961b0462e | ||
|
|
ffb80c5fc3 | ||
|
|
4835366a0c | ||
|
|
c09855f703 | ||
|
|
5761b05f3b | ||
|
|
ff1ea1a63e | ||
|
|
0ea6363172 | ||
|
|
fe5375f17b | ||
|
|
fd43ebc2bf | ||
|
|
113e7a52d4 | ||
|
|
9f6b4ed93b | ||
|
|
7aff15f743 | ||
|
|
1c4435312e | ||
|
|
dcb32eaaf7 | ||
|
|
ba07fd510e | ||
|
|
2fb5f8a7d0 | ||
|
|
b119121e10 | ||
|
|
cb101eca16 | ||
|
|
29674b8ed0 | ||
|
|
c6a0eeef3f | ||
|
|
825efb512a | ||
|
|
cd870bdcdd | ||
|
|
0868d898f6 | ||
|
|
66a78f0d40 | ||
|
|
2b04e1428c | ||
|
|
57f444357d | ||
|
|
fe08c241ec | ||
|
|
3d8a99f541 | ||
|
|
03236a50e5 | ||
|
|
a6c1649493 | ||
|
|
33a330fd6c | ||
|
|
3901e404a9 | ||
|
|
381e8bcfaa | ||
|
|
1050978246 | ||
|
|
9e48e58221 | ||
|
|
5d313b509e | ||
|
|
022b2202f6 | ||
|
|
2e9a1cfbba | ||
|
|
27a1144217 | ||
|
|
1d84c8f9ce | ||
|
|
04421d84a3 | ||
|
|
92cd8648fa | ||
|
|
41cc9a3e80 | ||
|
|
b97c902d10 | ||
|
|
25309f21ee | ||
|
|
1ba8da9780 | ||
|
|
66bc335d3f | ||
|
|
e834f375fb | ||
|
|
9c41972b65 | ||
|
|
41c30e9cfd | ||
|
|
fd8cbf0c7d | ||
|
|
4fe90dcbd6 | ||
|
|
377b086882 | ||
|
|
ade2ef3a15 | ||
|
|
6f428eb316 | ||
|
|
2970ad662c | ||
|
|
1dfe49e765 | ||
|
|
7a93a72710 | ||
|
|
661c8260e5 | ||
|
|
13c2da07e8 | ||
|
|
71b214ca43 | ||
|
|
513942c888 | ||
|
|
67df04e0a2 | ||
|
|
7b45f0d899 | ||
|
|
0f661e467e | ||
|
|
0232d8027c | ||
|
|
3a0377851d | ||
|
|
a93bad4cf3 | ||
|
|
88c3828ad3 | ||
|
|
c19853f03f | ||
|
|
1b7e103ef6 | ||
|
|
900d59b3ac | ||
|
|
37a9256587 | ||
|
|
127b5501d9 | ||
|
|
a081a9f5c4 | ||
|
|
fe8c2ceab9 | ||
|
|
1be5b4787f | ||
|
|
bb88d6f828 | ||
|
|
a7030641f3 | ||
|
|
208de3dae9 | ||
|
|
e81db118d5 | ||
|
|
488d8e5fc2 | ||
|
|
0049b36471 | ||
|
|
66b4b70023 | ||
|
|
438ae6a761 | ||
|
|
0cfb440cf6 | ||
|
|
a613280bbd | ||
|
|
6112c81db7 | ||
|
|
3ec4bf52e1 | ||
|
|
ec44a4391a | ||
|
|
3f26a4fd21 | ||
|
|
bb5b003dd5 | ||
|
|
99777c8e82 | ||
|
|
4daaa1f089 | ||
|
|
0828cc3f83 | ||
|
|
790835f6c6 | ||
|
|
fe3784454a | ||
|
|
b336bf2fcb | ||
|
|
87be37293e | ||
|
|
7abcfc0390 | ||
|
|
020e2069cb | ||
|
|
83dd07469e | ||
|
|
0a883ab651 | ||
|
|
7b39b93bb2 | ||
|
|
30496c79ab | ||
|
|
415dec37ad | ||
|
|
fbbe396416 | ||
|
|
c6da7d31d0 | ||
|
|
1f9c6945ec | ||
|
|
4094aec2eb | ||
|
|
ff7a5aa1ea | ||
|
|
bfe59f6cf2 | ||
|
|
d8b4c66d7e | ||
|
|
6f92c42ad8 | ||
|
|
1b1d514d10 | ||
|
|
af1135d455 | ||
|
|
e10c9a1fa1 | ||
|
|
1a233ca4aa | ||
|
|
6878dcbf37 | ||
|
|
8d735e5611 | ||
|
|
1862662b3a | ||
|
|
1102ac9f3b | ||
|
|
12115a8a75 | ||
|
|
7ee59669da | ||
|
|
22418cb613 | ||
|
|
4bccec1c39 | ||
|
|
8b1a288ef3 | ||
|
|
fa489531b0 | ||
|
|
2e52402e27 | ||
|
|
519f7a8bf1 | ||
|
|
17e9b803db | ||
|
|
48b4dbd81c | ||
|
|
70bf11ec27 | ||
|
|
8ea9d34729 | ||
|
|
8c4519a2d0 | ||
|
|
ea266b979f | ||
|
|
2f978b3159 | ||
|
|
d81ba12aa8 | ||
|
|
dd13131fe6 | ||
|
|
5a4055f313 | ||
|
|
87f3ab0181 | ||
|
|
c7d6e959e0 | ||
|
|
0b1a69a067 | ||
|
|
525e8b3c6d | ||
|
|
ad7f93c3cb | ||
|
|
c7a72553c4 | ||
|
|
679f74e53c | ||
|
|
7edaa0cce0 | ||
|
|
c21038af88 | ||
|
|
65f44cc885 | ||
|
|
ceb784c648 | ||
|
|
8e3df43caf | ||
|
|
afbca0f6cd | ||
|
|
f4e99a1bd6 | ||
|
|
ed6b919a4d | ||
|
|
2d2fd77bc1 | ||
|
|
15f347c69c | ||
|
|
9fdaf2117f | ||
|
|
631be974ee | ||
|
|
bd3d1d6988 | ||
|
|
f2636b163e | ||
|
|
df5daa045a | ||
|
|
4de547228c | ||
|
|
3d595ce927 | ||
|
|
456d33cf77 | ||
|
|
3ceb9b37a0 | ||
|
|
94181fd047 | ||
|
|
55705af922 | ||
|
|
a9423574fe | ||
|
|
34b1ef8779 | ||
|
|
68a7cece49 | ||
|
|
ad87463784 | ||
|
|
ad34fef228 | ||
|
|
25de180d6e | ||
|
|
c0116e0cdd | ||
|
|
e571b29868 | ||
|
|
5bbca8f517 | ||
|
|
5502df5512 | ||
|
|
87779b0ec8 | ||
|
|
92f51622cc | ||
|
|
e0721aa104 | ||
|
|
b4f1ef1127 | ||
|
|
1362abc70d | ||
|
|
1d6c6046a2 | ||
|
|
41f4b71c99 | ||
|
|
38ec7c22fb | ||
|
|
02e842b066 | ||
|
|
2ae7d2e363 | ||
|
|
f170e736c5 | ||
|
|
da246d6417 | ||
|
|
bb95d6ab52 | ||
|
|
11d447d7eb | ||
|
|
f56e7fc50d | ||
|
|
5d606bf567 | ||
|
|
4b216d6676 | ||
|
|
834f7b9063 | ||
|
|
5cd0fd76bf | ||
|
|
753f57c71b | ||
|
|
c71d2739b1 | ||
|
|
b10c971374 | ||
|
|
3a6c18279e | ||
|
|
7f1b157a5d | ||
|
|
58e0ee4ade | ||
|
|
0b9ee6348b | ||
|
|
120d168a62 | ||
|
|
439ddd8c41 | ||
|
|
ab8b25901a | ||
|
|
dfa7389c92 | ||
|
|
d503b72596 | ||
|
|
d1ae569167 | ||
|
|
7b8efeae70 | ||
|
|
009c7a7bdc | ||
|
|
c0ff2c79e8 | ||
|
|
11d25b0203 | ||
|
|
90dbc6fc26 | ||
|
|
b9dcf3571a | ||
|
|
b2f0a1dcd0 |
72
.clang-format
Normal file
72
.clang-format
Normal file
@@ -0,0 +1,72 @@
|
||||
# This file is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
# Generated from CLion C/C++ Code Style settings
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignOperands: Align
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Always
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 2
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: None
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: Never
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 2
|
||||
Cpp11BracedListStyle: false
|
||||
UseTab: Never
|
||||
6
.flake8
Normal file
6
.flake8
Normal file
@@ -0,0 +1,6 @@
|
||||
[flake8]
|
||||
filename =
|
||||
*.py
|
||||
max-line-length = 120
|
||||
extend-exclude =
|
||||
venv/
|
||||
102
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
102
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: Bug Report
|
||||
description: Create a bug report to help us improve.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
**THIS IS NOT THE PLACE TO ASK FOR SUPPORT!**
|
||||
Please use [Discord](https://docs.lizardbyte.dev/en/latest/about/support.html#discord) for support issues.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
description: A clear and concise description of the bug.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the bug here.
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Host Operating System
|
||||
description: What version operating system are you running the software on?
|
||||
options:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- other
|
||||
- type: input
|
||||
id: os-version
|
||||
attributes:
|
||||
label: Operating System Version
|
||||
description: Provide the version of the operating system. Additionally a build number would be helpful.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-architecture
|
||||
attributes:
|
||||
label: Architecture
|
||||
placeholder: e.g. 32 bit, 64 bit, arm
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Sunshine Version
|
||||
placeholder: eg. 0.14.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: graphics_type
|
||||
attributes:
|
||||
label: GPU Type
|
||||
description: The type of the installed graphics card.
|
||||
placeholder: e.g. Intel, AMD, Nvidia
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: graphics_model
|
||||
attributes:
|
||||
label: GPU Model
|
||||
description: The model of the installed graphics card.
|
||||
placeholder: e.g. GeForce RTX 2080 SUPER
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: graphics_driver
|
||||
attributes:
|
||||
label: GPU Driver/Mesa Version
|
||||
description: The driver/mesa version of the installed graphics card.
|
||||
placeholder: e.g. 497.29
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: capture_method
|
||||
attributes:
|
||||
label: Capture Method (Linux Only)
|
||||
description: The driver/mesa version of the installed graphics card.
|
||||
placeholder: e.g. PipeWire/KVM/X11
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: |
|
||||
Please copy and paste any relevant log output. This will be automatically formatted into code,
|
||||
so no need for backticks.
|
||||
render: Shell
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Make sure to close your issue when it's solved! If you found the solution yourself please comment
|
||||
so that others benefit from it.
|
||||
19
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord support
|
||||
url: https://docs.lizardbyte.dev/about/support.html#discord
|
||||
about: Ask questions in Discord
|
||||
- name: Reddit support
|
||||
url: https://www.reddit.com/r/LizardByte
|
||||
about: Get community support on Reddit
|
||||
- name: Facebook support
|
||||
url: https://www.facebook.com/groups/lizardbyte
|
||||
about: Get community support on Facebook
|
||||
- name: Feature request
|
||||
url: https://feedback.lizardbyte.dev
|
||||
about: Share your suggestions or ideas to help us improve
|
||||
30
.github/dependabot.yml
vendored
Normal file
30
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "00:00"
|
||||
target-branch: "nightly"
|
||||
open-pull-requests-limit: 10
|
||||
49
.github/label-actions.yml
vendored
Normal file
49
.github/label-actions.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
added:
|
||||
comment: >
|
||||
This feature has been added and will be available in the next release.
|
||||
fixed:
|
||||
comment: >
|
||||
This issue has been fixed and will be available in the next release.
|
||||
invalid:duplicate:
|
||||
comment: >
|
||||
:wave: @{issue-author}, this appears to be a duplicate of a pre-existing issue.
|
||||
close: true
|
||||
lock: true
|
||||
unlabel: 'status:awaiting-triage'
|
||||
|
||||
-invalid:duplicate:
|
||||
reopen: true
|
||||
unlock: true
|
||||
|
||||
invalid:support:
|
||||
comment: >
|
||||
:wave: @{issue-author}, we use the issue tracker exclusively for bug reports.
|
||||
However, this issue appears to be a support request. Please use
|
||||
[Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. Thanks.
|
||||
close: true
|
||||
lock: true
|
||||
lock-reason: 'off-topic'
|
||||
unlabel: 'status:awaiting-triage'
|
||||
|
||||
-invalid:support:
|
||||
reopen: true
|
||||
unlock: true
|
||||
|
||||
invalid:template-incomplete:
|
||||
issues:
|
||||
comment: >
|
||||
:wave: @{issue-author}, please edit your issue to complete the template with
|
||||
all the required info. Your issue will be automatically closed in 5 days if
|
||||
the template is not completed. Thanks.
|
||||
prs:
|
||||
comment: >
|
||||
:wave: @{issue-author}, please edit your PR to complete the template with
|
||||
all the required info. Your PR will be automatically closed in 5 days if
|
||||
the template is not completed. Thanks.
|
||||
24
.github/pr_release_template.md
vendored
Normal file
24
.github/pr_release_template.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
## Description
|
||||
<!--- Please include a summary of the changes. --->
|
||||
This PR was created automatically.
|
||||
|
||||
|
||||
### Screenshot
|
||||
<!--- Include screenshots if the changes are UI-related. --->
|
||||
|
||||
|
||||
### Issues Fixed or Closed
|
||||
<!--- Close issue example: `- Closes #1` --->
|
||||
<!--- Fix bug issue example: `- Fixes #2` --->
|
||||
<!--- Resolve issue example: `- Resolves #3` --->
|
||||
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Documentation update (changes to documentation)
|
||||
- [ ] Repository update (changes to repository files)
|
||||
|
||||
## Changelog Summary
|
||||
<!--- Summarize all the changes in a bulleted list. --->
|
||||
22
.github/pull_request_template.md
vendored
Normal file
22
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
## Description
|
||||
<!--- Please include a summary of the changes. --->
|
||||
|
||||
### Screenshot
|
||||
<!--- Include screenshots if the changes are UI-related. --->
|
||||
|
||||
### Issues Fixed or Closed
|
||||
<!--- Delete if not relevant. --->
|
||||
- Fixes #(issue)
|
||||
|
||||
## Type of Change
|
||||
<!--- Please delete options that are not relevant. --->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
|
||||
## Checklist
|
||||
<!--- DO NOT delete any options here. It is okay to have items unchecked! --->
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have added or updated the docstring/documentation-blocks for new or existing methods/components
|
||||
805
.github/workflows/CI.yml
vendored
Normal file
805
.github/workflows/CI.yml
vendored
Normal file
@@ -0,0 +1,805 @@
|
||||
---
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, nightly]
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
github_env:
|
||||
name: GitHub Env Debug
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Dump github context
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
|
||||
check_changelog:
|
||||
name: Check Changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Verify Changelog
|
||||
id: verify_changelog
|
||||
if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }}
|
||||
# base_ref for pull request check, ref for push
|
||||
uses: LizardByte/.github/actions/verify_changelog@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
outputs:
|
||||
next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }}
|
||||
next_version_bare: ${{ steps.verify_changelog.outputs.changelog_parser_version_bare }}
|
||||
last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }}
|
||||
release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }}
|
||||
|
||||
check_versions:
|
||||
name: Check Versions
|
||||
runs-on: ubuntu-latest
|
||||
needs: check_changelog
|
||||
if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }}
|
||||
# base_ref for pull request check, ref for push
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check CMakeLists.txt Version
|
||||
run: |
|
||||
version=$(grep -o -E '^project\(Sunshine VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | \
|
||||
grep -o -E '[0-9]+\.[0-9]+\.[0-9]+')
|
||||
echo "cmakelists_version=${version}" >> $GITHUB_ENV
|
||||
|
||||
- name: Compare CMakeList.txt Version
|
||||
if: ${{ env.cmakelists_version != needs.check_changelog.outputs.next_version_bare }}
|
||||
run: |
|
||||
echo CMakeLists version: "$cmakelists_version"
|
||||
echo Changelog version: "${{ needs.check_changelog.outputs.next_version_bare }}"
|
||||
echo Within 'CMakeLists.txt' change "project(Sunshine [VERSION $cmakelists_version]" to \
|
||||
"project(Sunshine [VERSION ${{ needs.check_changelog.outputs.next_version_bare }}]"
|
||||
exit 1
|
||||
|
||||
build_linux_aur:
|
||||
name: Linux AUR
|
||||
runs-on: ubuntu-latest
|
||||
needs: check_changelog
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Dependencies Linux AUR
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
cmake
|
||||
|
||||
- name: Configure PKGBUILD files
|
||||
run: |
|
||||
# variables for manifest
|
||||
echo "aur_publish=false" >> $GITHUB_ENV
|
||||
aur_pkg=sunshine-dev
|
||||
sub_version=""
|
||||
conflicts="'sunshine'"
|
||||
provides="'sunshine'"
|
||||
|
||||
branch=${GITHUB_HEAD_REF}
|
||||
|
||||
# check the branch variable
|
||||
if [ -z "$branch" ]
|
||||
then
|
||||
echo "This is a PUSH event"
|
||||
commit=${{ github.sha }}
|
||||
clone_url=${{ github.event.repository.clone_url }}
|
||||
|
||||
if [[ ${{ github.ref == 'refs/heads/master' }} ]]; then
|
||||
aur_pkg=sunshine
|
||||
conflicts=""
|
||||
provides=""
|
||||
|
||||
echo "aur_publish=true" >> $GITHUB_ENV
|
||||
elif [[ ${{ github.ref == 'refs/heads/nightly' }} ]]; then
|
||||
aur_pkg=sunshine-git
|
||||
sub_version=".r${commit}"
|
||||
|
||||
echo "aur_publish=true" >> $GITHUB_ENV
|
||||
fi
|
||||
else
|
||||
echo "This is a PR event"
|
||||
commit=${{ github.event.pull_request.head.sha }}
|
||||
clone_url=${{ github.event.pull_request.head.repo.clone_url }}
|
||||
|
||||
sub_version=".r${commit}"
|
||||
fi
|
||||
echo "Commit: ${commit}"
|
||||
echo "Clone URL: ${clone_url}"
|
||||
|
||||
echo "aur_pkg=${aur_pkg}" >> $GITHUB_ENV
|
||||
|
||||
mkdir -p artifacts
|
||||
mkdir -p build
|
||||
|
||||
cd build
|
||||
cmake -DSUNSHINE_CONFIGURE_AUR=ON \
|
||||
-DSUNSHINE_AUR_PKG=${aur_pkg} \
|
||||
-DSUNSHINE_SUB_VERSION=${sub_version} \
|
||||
-DSUNSHINE_AUR_CONFLICTS=${conflicts} \
|
||||
-DSUNSHINE_AUR_PROVIDES=${provides} \
|
||||
-DGITHUB_CLONE_URL=${clone_url} \
|
||||
-DGITHUB_COMMIT=${commit} \
|
||||
-DSUNSHINE_CONFIGURE_ONLY=ON \
|
||||
..
|
||||
cd ..
|
||||
|
||||
mv ./build/PKGBUILD ./artifacts/
|
||||
|
||||
- name: Validate package
|
||||
uses: hapakaien/archlinux-package-action@v2.2.0
|
||||
with:
|
||||
path: artifacts
|
||||
flags: '--syncdeps --noconfirm'
|
||||
namcap: true
|
||||
srcinfo: true
|
||||
aur: true # workaround mirror problem
|
||||
|
||||
- name: Upload Artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine-linux-aur
|
||||
path: artifacts/
|
||||
|
||||
- name: Publish AUR package
|
||||
if: ${{ env.aur_publish == 'true' }}
|
||||
uses: KSXGitHub/github-actions-deploy-aur@v2.4.1
|
||||
with:
|
||||
pkgname: ${{ env.aur_pkg }}
|
||||
pkgbuild: ./artifacts/PKGBUILD
|
||||
assets: |
|
||||
./artifacts/*
|
||||
commit_username: ${{ secrets.AUR_USERNAME }}
|
||||
commit_email: ${{ secrets.AUR_EMAIL }}
|
||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
commit_message: Automatic update from GitHub ${{ github.repository }} per ${{ github.ref }}
|
||||
allow_empty_commits: false
|
||||
|
||||
build_linux_flatpak:
|
||||
name: Linux Flatpak
|
||||
runs-on: ubuntu-latest
|
||||
needs: check_changelog
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Dependencies Linux Flatpak
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
cmake \
|
||||
flatpak
|
||||
sudo su $(whoami) -c 'flatpak remote-add --user --if-not-exists flathub \
|
||||
https://flathub.org/repo/flathub.flatpakrepo'
|
||||
sudo su $(whoami) -c 'flatpak install --user flathub \
|
||||
org.flatpak.Builder org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 -y'
|
||||
|
||||
- name: Configure Flatpak Manifest
|
||||
run: |
|
||||
# variables for manifest
|
||||
branch=${GITHUB_HEAD_REF}
|
||||
|
||||
# check the branch variable
|
||||
if [ -z "$branch" ]
|
||||
then
|
||||
echo "This is a PUSH event"
|
||||
branch=${{ github.ref_name }}
|
||||
commit=${{ github.sha }}
|
||||
clone_url=${{ github.event.repository.clone_url }}
|
||||
else
|
||||
echo "This is a PR event"
|
||||
commit=${{ github.event.pull_request.head.sha }}
|
||||
clone_url=${{ github.event.pull_request.head.repo.clone_url }}
|
||||
fi
|
||||
echo "Branch: ${branch}"
|
||||
echo "Commit: ${commit}"
|
||||
echo "Clone URL: ${clone_url}"
|
||||
|
||||
mkdir -p build
|
||||
mkdir -p artifacts
|
||||
|
||||
cd build
|
||||
cmake -DGITHUB_CLONE_URL=${clone_url} \
|
||||
-DGITHUB_BRANCH=${branch} \
|
||||
-DGITHUB_COMMIT=${commit} \
|
||||
-DSUNSHINE_CONFIGURE_FLATPAK=ON \
|
||||
-DSUNSHINE_CONFIGURE_ONLY=ON \
|
||||
..
|
||||
|
||||
- name: Build Linux Flatpak
|
||||
working-directory: build
|
||||
run: |
|
||||
sudo su $(whoami) -c 'flatpak run org.flatpak.Builder --repo=repo --force-clean build-sunshine \
|
||||
dev.lizardbyte.sunshine.yml'
|
||||
sudo su $(whoami) -c 'flatpak build-bundle ./repo ../artifacts/sunshine.flatpak dev.lizardbyte.sunshine'
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine-linux-flatpak
|
||||
path: artifacts/
|
||||
|
||||
- name: Create Release
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: LizardByte/.github/actions/create_release@master
|
||||
with:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
next_version: ${{ needs.check_changelog.outputs.next_version }}
|
||||
last_version: ${{ needs.check_changelog.outputs.last_version }}
|
||||
release_body: ${{ needs.check_changelog.outputs.release_body }}
|
||||
|
||||
build_linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-20.04
|
||||
needs: check_changelog
|
||||
strategy:
|
||||
fail-fast: false # false to test all, true to fail entire job if any fail
|
||||
matrix:
|
||||
include: # package these differently
|
||||
- type: cpack
|
||||
CMAKE_INSTALL_PREFIX: '/usr'
|
||||
SUNSHINE_ASSETS_DIR: 'local/sunshine/assets'
|
||||
SUNSHINE_CONFIG_DIR: 'local/sunshine/config'
|
||||
EXTRA_ARGS: ''
|
||||
- type: appimage
|
||||
CMAKE_INSTALL_PREFIX: '/usr'
|
||||
SUNSHINE_ASSETS_DIR: 'sunshine.AppImage.config'
|
||||
SUNSHINE_CONFIG_DIR: 'sunshine.AppImage.home'
|
||||
EXTRA_ARGS: '-DSUNSHINE_CONFIGURE_APPIMAGE=ON'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Dependencies Linux
|
||||
run: |
|
||||
sudo add-apt-repository ppa:savoury1/ffmpeg4 -y
|
||||
# sudo add-apt-repository ppa:savoury1/boost-defaults-1.71 -y
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc-10 \
|
||||
git \
|
||||
g++-10 \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
wget
|
||||
# # Ubuntu 20.04+ packages
|
||||
# libboost-filesystem-dev
|
||||
# libboost-log-dev
|
||||
# libboost-thread-dev
|
||||
|
||||
# # Ubuntu 18.04 packages
|
||||
# libboost-filesystem1.71-dev \
|
||||
# libboost-log1.71-dev \
|
||||
# libboost-regex1.71-dev \
|
||||
# libboost-thread1.71-dev \
|
||||
|
||||
# clean apt cache
|
||||
sudo apt-get clean
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Update gcc alias
|
||||
sudo update-alternatives --install \
|
||||
/usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10
|
||||
|
||||
# Install CuDA
|
||||
sudo wget \
|
||||
https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run \
|
||||
--progress=bar:force:noscroll -q --show-progress -O /root/cuda.run
|
||||
sudo chmod a+x /root/cuda.run
|
||||
sudo /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm
|
||||
sudo rm /root/cuda.run
|
||||
|
||||
# # Install cmake (necessary for 18.04)
|
||||
# wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh
|
||||
# chmod +x cmake-3.22.2-linux-x86_64.sh
|
||||
# mkdir /opt/cmake
|
||||
# ./cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license
|
||||
# ln --force --symbolic /opt/cmake/bin/cmake /usr/local/bin/cmake
|
||||
# cmake --version
|
||||
|
||||
- name: Build Linux
|
||||
run: |
|
||||
mkdir -p build
|
||||
mkdir -p artifacts
|
||||
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=${{ matrix.CMAKE_INSTALL_PREFIX }} \
|
||||
-DSUNSHINE_ASSETS_DIR=${{ matrix.SUNSHINE_ASSETS_DIR }} \
|
||||
-DSUNSHINE_CONFIG_DIR=${{ matrix.SUNSHINE_CONFIG_DIR }} \
|
||||
-DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-DSUNSHINE_ENABLE_WAYLAND=ON \
|
||||
-DSUNSHINE_ENABLE_X11=ON \
|
||||
-DSUNSHINE_ENABLE_DRM=ON \
|
||||
-DSUNSHINE_ENABLE_CUDA=ON \
|
||||
${{ matrix.EXTRA_ARGS }} \
|
||||
..
|
||||
make -j ${nproc}
|
||||
|
||||
- name: Package Linux - CPACK
|
||||
if: ${{ matrix.type == 'cpack' }}
|
||||
working-directory: build
|
||||
run: |
|
||||
# package
|
||||
cpack -G DEB
|
||||
cpack -G RPM
|
||||
|
||||
# move
|
||||
mv ./cpack_artifacts/Sunshine.deb ../artifacts/sunshine.deb
|
||||
mv ./cpack_artifacts/Sunshine.rpm ../artifacts/sunshine.rpm
|
||||
|
||||
- name: Set AppImage Version
|
||||
if: ${{ matrix.type == 'appimage' && ( needs.check_changelog.outputs.next_version_bare != needs.check_changelog.outputs.latest_version ) }} # yamllint disable-line rule:line-length
|
||||
run: |
|
||||
version=${{ needs.check_changelog.outputs.next_version_bare }}
|
||||
echo "VERSION=${version}" >> $GITHUB_ENV
|
||||
|
||||
- name: Package Linux - AppImage
|
||||
if: ${{ matrix.type == 'appimage' }}
|
||||
working-directory: build
|
||||
run: |
|
||||
# install sunshine to the DESTDIR
|
||||
make install DESTDIR=AppDir
|
||||
|
||||
# portable home and config
|
||||
# todo - this is ugly... we should use a custom AppRun script to take care of this
|
||||
mv ./AppDir${{ matrix.CMAKE_INSTALL_PREFIX }}/sunshine.AppImage.* ../artifacts/
|
||||
mkdir -p ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}
|
||||
cp ../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/apps.json \
|
||||
../artifacts/${{ matrix.SUNSHINE_CONFIG_DIR }}/.config/sunshine/${{ matrix.SUNSHINE_CONFIG_DIR }}/
|
||||
|
||||
# variables
|
||||
DESKTOP_FILE="${DESKTOP_FILE:-sunshine.desktop}"
|
||||
ICON_FILE="${ICON_FILE:-sunshine.png}"
|
||||
|
||||
# AppImage
|
||||
# https://docs.appimage.org/packaging-guide/index.html
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod +x linuxdeploy-x86_64.AppImage
|
||||
|
||||
# # https://github.com/linuxdeploy/linuxdeploy-plugin-gtk
|
||||
# sudo apt-get install libgtk-3-dev librsvg2-dev -y
|
||||
# wget https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh
|
||||
# chmod +x linuxdeploy-plugin-gtk.sh
|
||||
# export DEPLOY_GTK_VERSION=3
|
||||
|
||||
./linuxdeploy-x86_64.AppImage \
|
||||
--appdir ./AppDir \
|
||||
--executable ./sunshine \
|
||||
--icon-file "../$ICON_FILE" \
|
||||
--desktop-file "./$DESKTOP_FILE" \
|
||||
--library /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 \
|
||||
--library /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 \
|
||||
--library /usr/lib/x86_64-linux-gnu/libpangoft2-1.0.so.0 \
|
||||
--output appimage
|
||||
# # add this argument back if using gtk plugin
|
||||
# --plugin gtk \
|
||||
|
||||
# move
|
||||
mv Sunshine*.AppImage ../artifacts/sunshine.AppImage
|
||||
|
||||
# permissions
|
||||
chmod +x ../artifacts/sunshine.AppImage
|
||||
|
||||
- name: Verify AppImage
|
||||
if: ${{ matrix.type == 'appimage' }}
|
||||
run: |
|
||||
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
|
||||
chmod +x appimagelint-x86_64.AppImage
|
||||
|
||||
# rm -rf ~/.cache/appimagelint/
|
||||
|
||||
./appimagelint-x86_64.AppImage ./artifacts/sunshine.AppImage
|
||||
|
||||
- name: Archive AppImage
|
||||
if: ${{ matrix.type == 'appimage' }}
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
chmod +x ./sunshine.AppImage
|
||||
|
||||
zip --recurse-paths --move --test ./sunshine-appimage.zip ./*
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine-linux-${{ matrix.type }}
|
||||
path: artifacts/
|
||||
|
||||
- name: Create Release
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: LizardByte/.github/actions/create_release@master
|
||||
with:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
next_version: ${{ needs.check_changelog.outputs.next_version }}
|
||||
last_version: ${{ needs.check_changelog.outputs.last_version }}
|
||||
release_body: ${{ needs.check_changelog.outputs.release_body }}
|
||||
|
||||
build_mac:
|
||||
name: MacOS
|
||||
runs-on: macos-11
|
||||
needs: check_changelog
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Dependencies MacOS
|
||||
run: |
|
||||
# install dependencies using homebrew
|
||||
brew install boost cmake ffmpeg opus
|
||||
|
||||
# fix openssl header not found
|
||||
ln -sf /usr/local/opt/openssl/include/openssl /usr/local/include/openssl
|
||||
|
||||
- name: Build MacOS
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DSUNSHINE_ASSETS_DIR=local/sunshine/assets \
|
||||
-DSUNSHINE_CONFIG_DIR=local/sunshine/config \
|
||||
..
|
||||
make -j ${nproc}
|
||||
|
||||
- name: Package MacOS
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
cd build
|
||||
|
||||
# package
|
||||
cpack -G DragNDrop
|
||||
mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-dragndrop.dmg
|
||||
|
||||
cpack -G Bundle
|
||||
mv ./cpack_artifacts/Sunshine.dmg ../artifacts/sunshine-macos-experimental-bundle.dmg
|
||||
|
||||
cpack -G ZIP
|
||||
mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-macos-experimental-archive.zip
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine-macos
|
||||
path: artifacts/
|
||||
|
||||
# this step can be removed after packages are fixed
|
||||
- name: Delete experimental packages
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
rm -f ./sunshine-macos-experimental-dragndrop.dmg
|
||||
rm -f ./sunshine-macos-experimental-bundle.dmg
|
||||
rm -f ./sunshine-macos-experimental-archive.zip
|
||||
|
||||
## no artifacts to release currently
|
||||
# - name: Create Release
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
# uses: LizardByte/.github/actions/create_release@master
|
||||
# with:
|
||||
# token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
# next_version: ${{ needs.check_changelog.outputs.next_version }}
|
||||
# last_version: ${{ needs.check_changelog.outputs.last_version }}
|
||||
# release_body: ${{ needs.check_changelog.outputs.release_body }}
|
||||
|
||||
build_mac_port:
|
||||
name: Macports
|
||||
needs: check_changelog
|
||||
runs-on: macos-11
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout ports
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: macports/macports-ports
|
||||
fetch-depth: 64
|
||||
path: ports
|
||||
|
||||
- name: Checkout mpbb
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: macports/mpbb
|
||||
path: mpbb
|
||||
|
||||
- name: Setup Dependencies Macports
|
||||
run: |
|
||||
# install dependencies using homebrew
|
||||
brew install cmake
|
||||
|
||||
- name: Configure Portfile
|
||||
run: |
|
||||
# variables for Portfile
|
||||
branch=${GITHUB_HEAD_REF}
|
||||
|
||||
# check the branch variable
|
||||
if [ -z "$branch" ]
|
||||
then
|
||||
echo "This is a PUSH event"
|
||||
commit=${{ github.sha }}
|
||||
clone_url=${{ github.event.repository.clone_url }}
|
||||
else
|
||||
echo "This is a PR event"
|
||||
commit=${{ github.event.pull_request.head.sha }}
|
||||
clone_url=${{ github.event.pull_request.head.repo.clone_url }}
|
||||
fi
|
||||
echo "Commit: ${commit}"
|
||||
echo "Clone URL: ${clone_url}"
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DGITHUB_COMMIT=${commit} \
|
||||
-DGITHUB_CLONE_URL=${clone_url} \
|
||||
-DSUNSHINE_CONFIGURE_PORTFILE=ON \
|
||||
-DSUNSHINE_CONFIGURE_ONLY=ON \
|
||||
..
|
||||
cd ..
|
||||
|
||||
# copy Portfile to artifacts
|
||||
mkdir -p artifacts
|
||||
cp -f ./build/Portfile ./artifacts/
|
||||
|
||||
# copy Portfile to ports
|
||||
mkdir -p ./ports/multimedia/Sunshine
|
||||
cp -f ./build/Portfile ./ports/multimedia/Sunshine/Portfile
|
||||
|
||||
# testing
|
||||
cat ./artifacts/Portfile
|
||||
|
||||
- name: Bootstrap MacPorts
|
||||
run: |
|
||||
. ports/.github/workflows/bootstrap.sh
|
||||
|
||||
# Add getopt, mpbb and the MacPorts paths to $PATH for the subsequent steps.
|
||||
echo "/opt/mports/bin" >> $GITHUB_PATH
|
||||
echo "${PWD}/mpbb" >> $GITHUB_PATH
|
||||
echo "/opt/local/bin" >> $GITHUB_PATH
|
||||
echo "/opt/local/sbin" >> $GITHUB_PATH
|
||||
|
||||
- name: Determine list of subports
|
||||
id: subportlist
|
||||
run: |
|
||||
set -eu
|
||||
port=Sunshine
|
||||
subportlist=""
|
||||
|
||||
echo "Listing subports for Sunshine"
|
||||
new_subports=$(mpbb \
|
||||
--work-dir /tmp/mpbb \
|
||||
list-subports \
|
||||
--archive-site= \
|
||||
--archive-site-private= \
|
||||
--include-deps=no \
|
||||
"$port" \
|
||||
| tr '\n' ' ')
|
||||
for subport in $new_subports; do
|
||||
echo "$subport"
|
||||
subportlist="$subportlist $subport"
|
||||
done
|
||||
echo "::set-output name=subportlist::${subportlist}"
|
||||
|
||||
- name: Run port lint for all subports
|
||||
run: |
|
||||
set -eu
|
||||
fail=0
|
||||
for subport in $subportlist; do
|
||||
echo "::group::${subport}"
|
||||
path=$(port file "$subport")
|
||||
messagetype="warning"
|
||||
if ! messages=$(port -q lint "$subport" 2>&1); then
|
||||
messagetype="error"
|
||||
fail=1
|
||||
fi
|
||||
if [ -n "$messages" ]; then
|
||||
echo "$messages"
|
||||
# See https://github.com/actions/toolkit/issues/193#issuecomment-605394935
|
||||
encoded_messages="port lint ${subport}:%0A"
|
||||
encoded_messages+="$(echo "${messages}" | sed -E 's/$/%0A/g' | tr -d '\n')"
|
||||
echo "::${messagetype} file=${path#${PWD}/ports/},line=1,col=1::${encoded_messages}"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
exit "$fail"
|
||||
env:
|
||||
subportlist: ${{ steps.subportlist.outputs.subportlist }}
|
||||
|
||||
- name: Build subports
|
||||
run: |
|
||||
set -eu
|
||||
fail=0
|
||||
for subport in $subportlist; do
|
||||
workdir="/tmp/mpbb/$subport"
|
||||
mkdir -p "$workdir/logs"
|
||||
touch "$workdir/logs/dependencies-progress.txt"
|
||||
echo "::group::Cleaning up between ports"
|
||||
sudo mpbb --work-dir "$workdir" cleanup
|
||||
echo "::endgroup::"
|
||||
echo "::group::Installing dependencies for ${subport}"
|
||||
sudo mpbb \
|
||||
--work-dir "$workdir" \
|
||||
install-dependencies \
|
||||
"$subport" >"$workdir/logs/install-dependencies.log" 2>&1 &
|
||||
deps_pid=$!
|
||||
tail -f "$workdir/logs/dependencies-progress.txt" 2>/dev/null &
|
||||
tail_pid=$!
|
||||
set +e
|
||||
wait "$deps_pid"
|
||||
deps_exit=$?
|
||||
set -e
|
||||
kill "$tail_pid" || true
|
||||
if [ "$deps_exit" -ne 0 ]; then
|
||||
echo "::endgroup::"
|
||||
echo "::error::Failed to install dependencies for ${subport}"
|
||||
fail=1
|
||||
continue
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
echo "::group::Installing ${subport}"
|
||||
set +e
|
||||
sudo mpbb \
|
||||
--work-dir "$workdir" \
|
||||
install-port \
|
||||
--source \
|
||||
"$subport"
|
||||
install_exit=$?
|
||||
set -e
|
||||
if [ "$install_exit" -ne 0 ]; then
|
||||
echo "::endgroup::"
|
||||
echo "::error::Failed to install ${subport}"
|
||||
fail=1
|
||||
continue
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
exit "$fail"
|
||||
env:
|
||||
subportlist: ${{ steps.subportlist.outputs.subportlist }}
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
# create packages
|
||||
sudo port pkg sunshine
|
||||
sudo port dmg sunshine
|
||||
|
||||
work=$(port work sunshine)
|
||||
echo "Sunshine port work directory: ${work}"
|
||||
|
||||
# move components out of port work directory
|
||||
sudo mv ${work}/Sunshine*component.pkg /tmp/
|
||||
|
||||
# copy artifacts
|
||||
sudo mv ${work}/Sunshine*.pkg ./artifacts/sunshine.pkg
|
||||
sudo mv ${work}/Sunshine*.dmg ./artifacts/sunshine.dmg
|
||||
|
||||
# move components back
|
||||
# sudo mv /tmp/Sunshine*component.pkg ${work}/
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine-macports
|
||||
path: artifacts/
|
||||
|
||||
- name: Create Release
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: LizardByte/.github/actions/create_release@master
|
||||
with:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
next_version: ${{ needs.check_changelog.outputs.next_version }}
|
||||
last_version: ${{ needs.check_changelog.outputs.last_version }}
|
||||
release_body: ${{ needs.check_changelog.outputs.release_body }}
|
||||
|
||||
build_win:
|
||||
name: Windows
|
||||
runs-on: windows-2019
|
||||
needs: check_changelog
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Dependencies Windows
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
update: true
|
||||
install: >-
|
||||
base-devel
|
||||
diffutils
|
||||
git
|
||||
make
|
||||
mingw-w64-x86_64-binutils
|
||||
mingw-w64-x86_64-boost
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-nsis
|
||||
mingw-w64-x86_64-openssl
|
||||
mingw-w64-x86_64-opus
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-x265
|
||||
nasm
|
||||
yasm
|
||||
|
||||
- name: Build Windows
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DSUNSHINE_ASSETS_DIR=assets \
|
||||
-DSUNSHINE_CONFIG_DIR=config \
|
||||
-G "MinGW Makefiles" \
|
||||
..
|
||||
mingw32-make -j2
|
||||
|
||||
- name: Package Windows
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
cd build
|
||||
|
||||
# package
|
||||
cpack -G NSIS
|
||||
cpack -G ZIP
|
||||
|
||||
# move
|
||||
mv ./cpack_artifacts/Sunshine.exe ../artifacts/sunshine-windows.exe
|
||||
mv ./cpack_artifacts/Sunshine.zip ../artifacts/sunshine-windows.zip
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sunshine-windows
|
||||
path: artifacts/
|
||||
|
||||
- name: Create Release
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: LizardByte/.github/actions/create_release@master
|
||||
with:
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
next_version: ${{ needs.check_changelog.outputs.next_version }}
|
||||
last_version: ${{ needs.check_changelog.outputs.last_version }}
|
||||
release_body: ${{ needs.check_changelog.outputs.release_body }}
|
||||
31
.github/workflows/auto-create-pr.yml
vendored
Normal file
31
.github/workflows/auto-create-pr.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Auto create PR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'nightly'
|
||||
|
||||
jobs:
|
||||
create_pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
source_branch: "" # should be "nightly" as it's the triggering branch
|
||||
destination_branch: "master"
|
||||
pr_title: "Pulling ${{ github.ref_name }} into master"
|
||||
pr_template: ".github/pr_release_template.md"
|
||||
pr_assignee: "${{ secrets.GH_BOT_NAME }}"
|
||||
pr_draft: true
|
||||
pr_allow_empty: false
|
||||
github_token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
59
.github/workflows/automerge.yml
vendored
Normal file
59
.github/workflows/automerge.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Automerge PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
autoapprove:
|
||||
if: >
|
||||
contains(fromJson('["LizardByte-bot"]'), github.event.pull_request.user.login) &&
|
||||
contains(fromJson('["LizardByte-bot"]'), github.actor)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Autoapproving
|
||||
uses: hmarr/auto-approve-action@v2
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Label autoapproved
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['autoapproved', 'autoupdate']
|
||||
})
|
||||
|
||||
automerge:
|
||||
needs: [autoapprove]
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: automerge-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: Automerging
|
||||
uses: pascalgn/automerge-action@v0.15.3
|
||||
env:
|
||||
BASE_BRANCHES: nightly
|
||||
GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
GITHUB_LOGIN: ${{ secrets.GH_BOT_NAME }}
|
||||
MERGE_LABELS: ""
|
||||
MERGE_METHOD: "squash"
|
||||
MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})"
|
||||
MERGE_DELETE_BRANCH: true
|
||||
MERGE_ERROR_FAIL: true
|
||||
MERGE_FILTER_AUTHOR: ${{ secrets.GH_BOT_NAME }}
|
||||
MERGE_RETRIES: "240" # 1 hour
|
||||
MERGE_RETRY_SLEEP: "15000" # 15 seconds
|
||||
32
.github/workflows/autoupdate.yml
vendored
Normal file
32
.github/workflows/autoupdate.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
# This workflow is designed to work with:
|
||||
# - automerge workflows
|
||||
|
||||
# It uses GitHub Action that auto-updates pull requests branches, when changes are pushed to their destination branch.
|
||||
# Auto-updating to the latest destination branch works only in the context of upstream repo and not forks.
|
||||
|
||||
name: autoupdate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'nightly'
|
||||
|
||||
jobs:
|
||||
autoupdate-for-bot:
|
||||
name: Autoupdate autoapproved PR created in the upstream
|
||||
if: startsWith(github.repository, 'LizardByte/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update
|
||||
uses: docker://chinthakagodawita/autoupdate-action:v1
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.GH_BOT_TOKEN }}'
|
||||
PR_FILTER: "labelled"
|
||||
PR_LABELS: "autoupdate"
|
||||
PR_READY_STATE: "ready_for_review"
|
||||
MERGE_CONFLICT_ACTION: "ignore"
|
||||
60
.github/workflows/cpp-clang-format-lint.yml
vendored
Normal file
60
.github/workflows/cpp-clang-format-lint.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Clang Format Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, nightly]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check_src:
|
||||
name: Check src
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check
|
||||
id: check
|
||||
run: |
|
||||
if [ -d "./src" ]
|
||||
then
|
||||
FOUND=true
|
||||
else
|
||||
FOUND=false
|
||||
fi
|
||||
|
||||
echo "::set-output name=src::${FOUND}"
|
||||
|
||||
outputs:
|
||||
src: ${{ steps.check.outputs.src }}
|
||||
|
||||
lint:
|
||||
name: Clang Format Lint
|
||||
needs: [check_src]
|
||||
if: ${{ needs.check_src.outputs.src == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Clang format lint
|
||||
uses: DoozyX/clang-format-lint-action@v0.14
|
||||
with:
|
||||
source: './src'
|
||||
extensions: 'cpp,h,m,mm'
|
||||
clangFormatVersion: 13
|
||||
style: file
|
||||
inplace: false
|
||||
|
||||
- name: Upload Artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: clang-format-fixes
|
||||
path: src/
|
||||
54
.github/workflows/issues-stale.yml
vendored
Normal file
54
.github/workflows/issues-stale.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Stale Issues / PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '00 00 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
name: Check Stale Issues / PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Stale
|
||||
uses: actions/stale@v5
|
||||
with:
|
||||
close-issue-message: >
|
||||
This issue was closed because it has been stalled for 5 days with no activity.
|
||||
close-pr-message: >
|
||||
This PR was closed because it has been stalled for 10 days with no activity.
|
||||
days-before-stale: 90
|
||||
days-before-close: 10
|
||||
exempt-all-assignees: true
|
||||
exempt-issue-labels: 'added,fixed'
|
||||
exempt-pr-labels: 'dependencies,l10n'
|
||||
stale-issue-label: 'stale'
|
||||
stale-issue-message: >
|
||||
This issue is stale because it has been open for 30 days with no activity.
|
||||
Comment or remove the stale label, otherwise this will be closed in 5 days.
|
||||
stale-pr-label: 'stale'
|
||||
stale-pr-message: >
|
||||
This PR is stale because it has been open for 90 days with no activity.
|
||||
Comment or remove the stale label, otherwise this will be closed in 10 days.
|
||||
|
||||
- name: Invalid Template
|
||||
uses: actions/stale@v5
|
||||
with:
|
||||
close-issue-message: >
|
||||
This issue was closed because the the template was not completed after 5 days.
|
||||
close-pr-message: >
|
||||
This PR was closed because the the template was not completed after 5 days.
|
||||
days-before-stale: 0
|
||||
days-before-close: 5
|
||||
exempt-pr-labels: 'dependencies,l10n'
|
||||
only-labels: 'invalid:template-incomplete'
|
||||
stale-issue-label: 'invalid:template-incomplete'
|
||||
stale-issue-message: >
|
||||
Invalid issues template.
|
||||
stale-pr-label: 'invalid:template-incomplete'
|
||||
stale-pr-message: >
|
||||
Invalid PR template.
|
||||
22
.github/workflows/issues.yml
vendored
Normal file
22
.github/workflows/issues.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled]
|
||||
discussion:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: Label Actions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label Actions
|
||||
uses: dessant/label-actions@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
95
.github/workflows/localize.yml
vendored
Normal file
95
.github/workflows/localize.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: localize
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [nightly]
|
||||
paths: # prevents workflow from running unless these files change
|
||||
- '.github/workflows/localize.yml'
|
||||
- 'src/**'
|
||||
- 'locale/sunshine.po'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
file: ./locale/sunshine.po
|
||||
|
||||
jobs:
|
||||
localize:
|
||||
name: Update Localization
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Python 3.9
|
||||
uses: actions/setup-python@v4 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Set up Python 3.9 Dependencies
|
||||
run: |
|
||||
cd ./scripts
|
||||
python -m pip install --upgrade pip setuptools
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
- name: Set up xgettext
|
||||
run: |
|
||||
sudo apt-get update -y && \
|
||||
sudo apt-get --reinstall install -y \
|
||||
gettext
|
||||
|
||||
- name: Update Strings
|
||||
run: |
|
||||
# first, try to remove existing file as xgettext does not remove unused translations
|
||||
if [ -f "${{ env.file }}" ];
|
||||
then
|
||||
rm ${{ env.file }}
|
||||
echo "new_file=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "new_file=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# extract the new strings
|
||||
python ./scripts/_locale.py --extract
|
||||
|
||||
- name: git diff
|
||||
if: ${{ env.new_file == 'false' }}
|
||||
run: |
|
||||
# disable the pager
|
||||
git config --global pager.diff false
|
||||
|
||||
# print the git diff
|
||||
git diff locale/sunshine.po
|
||||
|
||||
# set the variable with minimal output
|
||||
OUTPUT=$(git diff --numstat locale/sunshine.po)
|
||||
echo "git_diff=${OUTPUT}" >> $GITHUB_ENV
|
||||
|
||||
- name: git reset
|
||||
# only run if a single line changed (date/time) and file already existed
|
||||
# \t in next line is a tab character
|
||||
if: ${{ env.git_diff == '1\t1\tlocale/sunshine.po' && env.new_file == 'false' }}
|
||||
run: |
|
||||
git reset --hard
|
||||
|
||||
- name: Create/Update Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
add-paths: |
|
||||
locale/*.po
|
||||
token: ${{ secrets.GH_BOT_TOKEN }} # must trigger PR tests
|
||||
commit-message: New localization template
|
||||
branch: localize/update
|
||||
delete-branch: true
|
||||
base: nightly
|
||||
title: New Babel Updates
|
||||
body: |
|
||||
Update report
|
||||
- Updated with *today's* date
|
||||
- Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
labels: |
|
||||
babel
|
||||
l10n
|
||||
27
.github/workflows/pull-requests.yml
vendored
Normal file
27
.github/workflows/pull-requests.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Pull Requests
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, edited, reopened]
|
||||
|
||||
jobs:
|
||||
check-pull-request:
|
||||
name: Check Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Vankka/pr-target-branch-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
target: master
|
||||
exclude: nightly # Don't prevent going from nightly -> master
|
||||
change-to: nightly
|
||||
comment: |
|
||||
Your PR was set to `master`, PRs should be sent to `nightly`.
|
||||
The base branch of this PR has been automatically changed to `nightly`.
|
||||
Please check that there are no merge conflicts
|
||||
31
.github/workflows/python-flake8.yml
vendored
Normal file
31
.github/workflows/python-flake8.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: flake8
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, nightly]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
flake8:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools flake8
|
||||
|
||||
- name: Test with flake8
|
||||
run: |
|
||||
python -m flake8 --verbose
|
||||
81
.github/workflows/release-notifier.yml
vendored
Normal file
81
.github/workflows/release-notifier.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: Release Notifications
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onevent_nametypes
|
||||
|
||||
jobs:
|
||||
discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: discord
|
||||
uses: sarisia/actions-status-discord@v1 # https://github.com/sarisia/actions-status-discord
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
|
||||
nodetail: true
|
||||
nofail: false
|
||||
username: ${{ secrets.DISCORD_USERNAME }}
|
||||
avatar_url: ${{ secrets.ORG_LOGO_URL }}
|
||||
title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released
|
||||
description: ${{ github.event.release.body }}
|
||||
color: 0xFF4500
|
||||
|
||||
facebook_group:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: facebook-post-action
|
||||
uses: ReenigneArcher/facebook-post-action@v1 # https://github.com/ReenigneArcher/facebook-post-action
|
||||
with:
|
||||
page_id: ${{ secrets.FACEBOOK_GROUP_ID }}
|
||||
access_token: ${{ secrets.FACEBOOK_ACCESS_TOKEN }}
|
||||
message: |
|
||||
${{ github.event.repository.name }} ${{ github.ref_name }} Released
|
||||
${{ github.event.release.body }}
|
||||
url: ${{ github.event.release.html_url }}
|
||||
|
||||
facebook_page:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: facebook-post-action
|
||||
uses: ReenigneArcher/facebook-post-action@v1 # https://github.com/ReenigneArcher/facebook-post-action
|
||||
with:
|
||||
page_id: ${{ secrets.FACEBOOK_PAGE_ID }}
|
||||
access_token: ${{ secrets.FACEBOOK_ACCESS_TOKEN }}
|
||||
message: |
|
||||
${{ github.event.repository.name }} ${{ github.ref_name }} Released
|
||||
${{ github.event.release.body }}
|
||||
url: ${{ github.event.release.html_url }}
|
||||
|
||||
reddit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: reddit
|
||||
uses: bluwy/release-for-reddit-action@v1 # https://github.com/bluwy/release-for-reddit-action
|
||||
with:
|
||||
username: ${{ secrets.REDDIT_USERNAME }}
|
||||
password: ${{ secrets.REDDIT_PASSWORD }}
|
||||
app-id: ${{ secrets.REDDIT_CLIENT_ID }}
|
||||
app-secret: ${{ secrets.REDDIT_CLIENT_SECRET }}
|
||||
subreddit: ${{ secrets.REDDIT_SUBREDDIT }}
|
||||
title: ${{ github.event.repository.name }} ${{ github.ref_name }} Released
|
||||
url: ${{ github.event.release.html_url }}
|
||||
flair-id: ${{ secrets.REDDIT_FLAIR_ID }} # https://www.reddit.com/r/<subreddit>>/api/link_flair.json
|
||||
comment: ${{ github.event.release.body }}
|
||||
|
||||
twitter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: twitter
|
||||
uses: ethomson/send-tweet-action@v1 # https://github.com/ethomson/send-tweet-action
|
||||
with:
|
||||
consumer-key: ${{ secrets.TWITTER_API_KEY }}
|
||||
consumer-secret: ${{ secrets.TWITTER_API_SECRET }}
|
||||
access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
status: ${{ github.event.release.html_url }}
|
||||
17
.github/workflows/winget.yml
vendored
Normal file
17
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Publish to WinGet
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
winget-releaser:
|
||||
name: winget releaser
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: winget releaser
|
||||
uses: vedantmgoyal2009/winget-releaser@latest
|
||||
with:
|
||||
identifier: LizardByte.Sunshine
|
||||
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||
46
.github/workflows/yaml-lint.yml
vendored
Normal file
46
.github/workflows/yaml-lint.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
# This action is centrally managed in https://github.com/<organization>/.github/
|
||||
# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in
|
||||
# the above-mentioned repo.
|
||||
|
||||
name: yaml lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, nightly]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
yaml-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: yaml lint
|
||||
id: yaml-lint
|
||||
uses: ibiqlik/action-yamllint@v3
|
||||
with:
|
||||
# https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration
|
||||
config_data: |
|
||||
extends: default
|
||||
rules:
|
||||
comments:
|
||||
level: error
|
||||
line-length:
|
||||
max: 120
|
||||
truthy:
|
||||
allowed-values: ['true', 'false', 'on'] # GitHub uses "on" for workflow event triggers
|
||||
check-keys: true
|
||||
level: error
|
||||
|
||||
- name: Log
|
||||
run: |
|
||||
echo ${{ steps.yaml-lint.outputs.logfile }}
|
||||
|
||||
- name: Upload logs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: failure()
|
||||
with:
|
||||
name: yamllint-logfile
|
||||
path: ${{ steps.yaml-lint.outputs.logfile }}
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,8 +1,24 @@
|
||||
build
|
||||
cmake-build-*
|
||||
cmake-build*
|
||||
.DS_Store
|
||||
|
||||
.vscode
|
||||
.vs
|
||||
*.swp
|
||||
*.kdev4
|
||||
|
||||
.cache
|
||||
.idea
|
||||
|
||||
# Extra FontAwesome files
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*.css
|
||||
!/src_assets/common/assets/web/fonts/fontawesome-free-web/css/*min.css
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/js/
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/less/
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/metadata/
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/scss/
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/sprites/
|
||||
/src_assets/common/assets/web/fonts/fontawesome-free-web/svgs/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
18
.gitmodules
vendored
18
.gitmodules
vendored
@@ -1,12 +1,18 @@
|
||||
[submodule "moonlight-common-c"]
|
||||
path = moonlight-common-c
|
||||
path = third-party/moonlight-common-c
|
||||
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||
[submodule "Simple-Web-Server"]
|
||||
path = Simple-Web-Server
|
||||
path = third-party/Simple-Web-Server
|
||||
url = https://github.com/loki-47-6F-64/Simple-Web-Server.git
|
||||
[submodule "ViGEmClient"]
|
||||
path = ViGEmClient
|
||||
path = third-party/ViGEmClient
|
||||
url = https://github.com/ViGEm/ViGEmClient
|
||||
[submodule "pre-compiled"]
|
||||
path = pre-compiled
|
||||
url = https://github.com/loki-47-6F-64/pre-compiled.git
|
||||
[submodule "third-party/miniupnp"]
|
||||
path = third-party/miniupnp
|
||||
url = https://github.com/miniupnp/miniupnp
|
||||
[submodule "third-party/nv-codec-headers"]
|
||||
path = third-party/nv-codec-headers
|
||||
url = https://github.com/FFmpeg/nv-codec-headers
|
||||
[submodule "third-party/TPCircularBuffer"]
|
||||
path = third-party/TPCircularBuffer
|
||||
url = https://github.com/michaeltyson/TPCircularBuffer
|
||||
|
||||
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
45
.readthedocs.yaml
Normal file
45
.readthedocs.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.9"
|
||||
|
||||
## apt packages required packages to run cmake on sunshine, note that additional packages are required
|
||||
# apt_packages:
|
||||
# - cmake
|
||||
# - ffmpeg
|
||||
# - libboost-filesystem-dev
|
||||
# - libboost-log-dev
|
||||
# - libboost-thread-dev
|
||||
|
||||
## run cmake
|
||||
# jobs:
|
||||
# pre_build:
|
||||
# - cmake .
|
||||
|
||||
## Include the submodules, required for cmake
|
||||
# submodules:
|
||||
# include: all
|
||||
# recursive: true
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
builder: html
|
||||
configuration: docs/source/conf.py
|
||||
fail_on_warning: true
|
||||
|
||||
# Using Sphinx, build docs in additional formats
|
||||
formats: all
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: ./scripts/requirements.txt
|
||||
system_packages: true
|
||||
152
CHANGELOG.md
Normal file
152
CHANGELOG.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Changelog
|
||||
|
||||
## [0.14.1] - 2022-08-09
|
||||
### Added
|
||||
- (Linux) Flatpak package added
|
||||
- (Linux) AUR package automated updates
|
||||
- (Windows) Winget package automated updates
|
||||
### Changed
|
||||
- (General) Moved repo to @LizardByte GitHub org
|
||||
- (WebUI) Fixed button spacing on home page
|
||||
- (WebUI) Added Discord WidgetBot Crate
|
||||
### Fixed
|
||||
- (Linux/Mac) Default config and app files now copied to user home directory
|
||||
- (Windows) Default config and app files now copied to working directory
|
||||
|
||||
## [0.14.0] - 2022-06-15
|
||||
### Added
|
||||
- (Documentation) Added Sphinx documentation available at https://sunshinestream.readthedocs.io/en/latest/
|
||||
- (Development) Initial support for Localization
|
||||
- (Linux) Add rpm package as release asset
|
||||
- (MacOS) Add Portfile as release asset
|
||||
- (Windows) Add DwmFlush() call to improve capture
|
||||
- (Windows) Add Windows installer
|
||||
### Fixed
|
||||
- (AMD) Fixed hwdevice being destroyed before context
|
||||
- (Linux) Added missing dependencies to AppImage
|
||||
- (Linux) Fixed rumble events causing game to freeze
|
||||
- (Linux) Improved Pulse/Pipewire compatibility
|
||||
- (Linux) Moved to single deb package
|
||||
- (MacOS) Fixed missing TPCircularBuffer submodule
|
||||
- (Stream) Properly catch exceptions in stream broadcast handlers
|
||||
- (Stream/Video) AVPacket fix
|
||||
|
||||
## [0.13.0] - 2022-02-27
|
||||
### Added
|
||||
- (MacOS) Initial support for MacOS (#40)
|
||||
|
||||
## [0.12.0] - 2022-02-13
|
||||
### Added
|
||||
- New command line argument `--version`
|
||||
- Custom png poster support
|
||||
### Changed
|
||||
- Correct software bitrate calculation
|
||||
- Increase vbv-bufsize to 1/10 of requested bitrate
|
||||
- Improvements to Web UI
|
||||
|
||||
## [0.11.1] - 2021-10-04
|
||||
### Changed
|
||||
- (Linux) Fix search path for config file and assets
|
||||
|
||||
## [0.11.0] - 2021-10-04
|
||||
### Added
|
||||
- (Linux) Added support for wlroots based compositors on Wayland.
|
||||
- (Windows) Added an icon for the executable
|
||||
### Changed
|
||||
- Fixed a bug causing segfault when connecting multiple controllers.
|
||||
- (Linux) Improved NVENC, it now offloads converting images from RGB to NV12
|
||||
- (Linux) Fixed a bug causes stuttering
|
||||
|
||||
## [0.10.1] - 2021-08-21
|
||||
### Changed
|
||||
- (Linux) Re-enabled KMS
|
||||
|
||||
## [0.10.0] - 2021-08-20
|
||||
### Added
|
||||
- Added support for Rumble with gamepads.
|
||||
- Added support for keyboard shortcuts <--- See the README for details.
|
||||
- (Windows) A very basic script has been added in Sunshine-Windows\tools <-- This will start Sunshine at boot with the highest privileges which is needed to display the login prompt.
|
||||
### Changed
|
||||
- Some cosmetic changes to the WebUI.
|
||||
- The first time the WebUI is opened, it will request the creation of a username/password pair from the user.
|
||||
- Fixed audio crackling introduced in version 0.8.0
|
||||
- (Linux) VAAPI hardware encoding now works on Intel i7-6700 at least. <-- For the best experience, using ffmpeg version 4.3 or higher is recommended.
|
||||
- (Windows) Installing from debian package shouldn't overwrite your configuration files anymore. <-- It's recommended that you back up `/etc/sunshine/` before testing this.
|
||||
|
||||
## [0.9.0] - 2021-07-11
|
||||
### Added
|
||||
- Added audio encryption
|
||||
- (Linux) Added basic NVENC support on Linux
|
||||
- (Windows) The Windows version can now capture the lock screen and the UAC prompt as long as it's run through `PsExec.exe` https://docs.microsoft.com/en-us/sysinternals/downloads/psexec
|
||||
### Changed
|
||||
- Sunshine will now accept expired or not-yet-valid certificates, as long as they are signed properly.
|
||||
- Fixed compatibility with iOS version of Moonlight
|
||||
- Drastically reduced chance of being forced to skip error correction due to video frame size
|
||||
- (Linux) sunshine.service will be installed automatically.
|
||||
|
||||
## [0.8.0] - 2021-06-30
|
||||
### Added
|
||||
- Added mDNS support: Moonlight will automatically find Sunshine.
|
||||
- Added UPnP support. It's off by default.
|
||||
|
||||
## [0.7.7] - 2021-06-24
|
||||
### Added
|
||||
- (Linux) Added installation package for Debian
|
||||
### Changed
|
||||
- Fixed incorrect scaling for absolute mouse coordinates when using multiple monitors.
|
||||
- Fixed incorrect colors when scaling for software encoder
|
||||
|
||||
## [0.7.1] - 2021-06-18
|
||||
### Changed
|
||||
- (Linux) Fixed an issue where it was impossible to start sunshine on ubuntu 20.04
|
||||
|
||||
## [0.7.0] - 2021-06-16
|
||||
### Added
|
||||
- Added a Web Manager. Accessible through: https://localhost:47990 or https://<ip of your pc>:47990
|
||||
- (Linux) Added hardware encoding support for AMD on Linux
|
||||
### Changed
|
||||
- (Linux) Moved certificates and saved pairings generated during runtime to .config/sunshine on Linux
|
||||
|
||||
## [0.6.0] - 2021-05-26
|
||||
### Added
|
||||
- Added support for surround audio
|
||||
### Changed
|
||||
- Maintain aspect ratio when scaling video
|
||||
- Fix issue where Sunshine is forced to drop frames when they are too large
|
||||
|
||||
## [0.5.0] - 2021-05-13
|
||||
### Added
|
||||
- Added support for absolute mouse coordinates
|
||||
- (Linux) Added support for streaming specific monitor on Linux
|
||||
- (Windows) Added support for AMF on Windows
|
||||
|
||||
## [0.4.0] - 2020-05-03
|
||||
### Changed
|
||||
- prep-cmd is now optional in apps.json
|
||||
- Fixed bug causing video artifacts
|
||||
- Fixed bug preventing Moonlight from closing app on exit
|
||||
- Fixed bug causing preventing keyboard keys from repeating on latest version of Moonlight
|
||||
- Fixed bug causing segfault when another session of sunshine was already running
|
||||
- Fixed bug causing crash when monitor has resolution 1366x768
|
||||
|
||||
## [0.3.1] - 2020-04-24
|
||||
### Changed
|
||||
- Fix a memory leak.
|
||||
|
||||
## [0.3.0] - 2020-04-23
|
||||
### Changed
|
||||
- Hardware acceleration on NVidia GPU's for Video encoding on Windows
|
||||
|
||||
## [0.2.0] - 2020-03-21
|
||||
### Changed
|
||||
- Multicasting is now supported: You can set the maximum simultaneous connections with the configurable option: channels
|
||||
- Configuration variables can be overwritten on the command line: "name=value" --> it can be useful to set min_log_level=debug without modifying the configuration file
|
||||
- Switches to make testing the pairing mechanism more convenient has been added, see "sunshine --help" for details
|
||||
|
||||
## [0.1.1] - 2020-01-30
|
||||
### Added
|
||||
- (Linux) Added deb package and service for Linux
|
||||
|
||||
## [0.1.0] - 2020-01-27
|
||||
### Added
|
||||
- The first official release for Sunshine!
|
||||
692
CMakeLists.txt
692
CMakeLists.txt
@@ -1,180 +1,446 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
project(Sunshine)
|
||||
project(Sunshine VERSION 0.14.1
|
||||
DESCRIPTION "Sunshine is a Gamestream host for Moonlight."
|
||||
HOMEPAGE_URL "https://app.lizardbyte.dev"
|
||||
)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(PROJECT_LONG_DESCRIPTION "Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, \
|
||||
Intel, and Nvidia GPUs. It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield. \
|
||||
Connect to Sunshine from any Moonlight client, available for nearly any device imaginable.")
|
||||
|
||||
# On MSYS2, building a stand-alone binary that links with ffmpeg is not possible,
|
||||
# Therefore, ffmpeg, libx264 and libx265 must be build from source
|
||||
if(WIN32)
|
||||
option(SUNSHINE_STANDALONE "Compile stand-alone binary of Sunshine" OFF)
|
||||
if(SUNSHINE_STANDALONE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
|
||||
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES)
|
||||
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/pre-compiled/windows")
|
||||
endif()
|
||||
list(PREPEND PLATFORM_LIBRARIES
|
||||
C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/${CMAKE_CXX_COMPILER_VERSION}/libstdc++.a
|
||||
C:/msys64/mingw64/x86_64-w64-mingw32/lib/libwinpthread.a
|
||||
)
|
||||
option(SUNSHINE_CONFIGURE_APPIMAGE "Configure files required for AppImage." OFF)
|
||||
option(SUNSHINE_CONFIGURE_AUR "Configure files required for AUR." OFF)
|
||||
option(SUNSHINE_CONFIGURE_FLATPAK "Configure files required for Flatpak." OFF)
|
||||
option(SUNSHINE_CONFIGURE_PORTFILE "Configure macOS Portfile." OFF)
|
||||
option(SUNSHINE_CONFIGURE_ONLY "Configure special files only, then exit." OFF)
|
||||
|
||||
set(FFMPEG_INCLUDE_DIRS
|
||||
${SUNSHINE_PREPARED_BINARIES}/include)
|
||||
set(FFMPEG_LIBRARIES
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
|
||||
z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a)
|
||||
endif()
|
||||
else()
|
||||
set(SUNSHINE_STANDALONE OFF)
|
||||
if(${SUNSHINE_CONFIGURE_APPIMAGE})
|
||||
configure_file(packaging/linux/sunshine.desktop sunshine.desktop @ONLY)
|
||||
elseif(${SUNSHINE_CONFIGURE_AUR})
|
||||
configure_file(packaging/linux/aur/PKGBUILD PKGBUILD @ONLY)
|
||||
elseif(${SUNSHINE_CONFIGURE_FLATPAK})
|
||||
configure_file(packaging/linux/flatpak/dev.lizardbyte.sunshine.yml dev.lizardbyte.sunshine.yml @ONLY)
|
||||
elseif(${SUNSHINE_CONFIGURE_PORTFILE})
|
||||
configure_file(packaging/macos/Portfile Portfile @ONLY)
|
||||
endif()
|
||||
|
||||
add_subdirectory(Simple-Web-Server)
|
||||
add_subdirectory(moonlight-common-c/enet)
|
||||
# return if configure only is set
|
||||
if(${SUNSHINE_CONFIGURE_ONLY})
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
set(SUNSHINE_SOURCE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src_assets")
|
||||
|
||||
if(WIN32)
|
||||
# Ugly hack to compile with #include <qos2.h>
|
||||
add_compile_definitions(
|
||||
QOS_FLOWID=UINT32
|
||||
PQOS_FLOWID=UINT32*
|
||||
QOS_NON_ADAPTIVE_FLOW=2)
|
||||
endif()
|
||||
if(APPLE)
|
||||
macro(ADD_FRAMEWORK fwname appname)
|
||||
find_library(FRAMEWORK_${fwname}
|
||||
NAMES ${fwname}
|
||||
PATHS ${CMAKE_OSX_SYSROOT}/System/Library
|
||||
PATH_SUFFIXES Frameworks
|
||||
NO_DEFAULT_PATH)
|
||||
if( ${FRAMEWORK_${fwname}} STREQUAL FRAMEWORK_${fwname}-NOTFOUND)
|
||||
MESSAGE(ERROR ": Framework ${fwname} not found")
|
||||
else()
|
||||
TARGET_LINK_LIBRARIES(${appname} "${FRAMEWORK_${fwname}}/${fwname}")
|
||||
MESSAGE(STATUS "Framework ${fwname} found at ${FRAMEWORK_${fwname}}")
|
||||
endif()
|
||||
endmacro(ADD_FRAMEWORK)
|
||||
endif()
|
||||
|
||||
add_subdirectory(third-party/moonlight-common-c/enet)
|
||||
add_subdirectory(third-party/Simple-Web-Server)
|
||||
|
||||
set(UPNPC_BUILD_SHARED OFF CACHE BOOL "no shared libraries")
|
||||
set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Don't build tests for miniupnpc")
|
||||
set(UPNPC_BUILD_SAMPLE OFF CACHE BOOL "Don't build samples for miniupnpc")
|
||||
set(UPNPC_NO_INSTALL ON CACHE BOOL "Don't install any libraries build for miniupnpc")
|
||||
add_subdirectory(third-party/miniupnp/miniupnpc)
|
||||
include_directories(third-party/miniupnp)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
if(NOT SUNSHINE_STANDALONE)
|
||||
find_package(FFmpeg REQUIRED)
|
||||
if(NOT APPLE)
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
endif()
|
||||
find_package(Boost COMPONENTS log filesystem REQUIRED)
|
||||
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||
|
||||
if(WIN32)
|
||||
enable_language(RC)
|
||||
set(CMAKE_RC_COMPILER windres)
|
||||
file(
|
||||
DOWNLOAD "https://github.com/TheElixZammuto/sunshine-prebuilt/releases/download/1.0.0/pre-compiled.zip" "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip"
|
||||
TIMEOUT 60
|
||||
EXPECTED_HASH SHA256=5d59986bd7f619eaaf82b2dd56b5127b747c9cbe8db61e3b898ff6b485298ed6)
|
||||
|
||||
file(ARCHIVE_EXTRACT
|
||||
INPUT "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled.zip"
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pre-compiled)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
|
||||
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES)
|
||||
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_BINARY_DIR}/pre-compiled/windows")
|
||||
endif()
|
||||
|
||||
add_compile_definitions(SUNSHINE_PLATFORM="windows")
|
||||
add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now
|
||||
|
||||
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json")
|
||||
include_directories(
|
||||
ViGEmClient/include)
|
||||
include_directories(third-party/ViGEmClient/include)
|
||||
|
||||
if(NOT DEFINED SUNSHINE_ICON_PATH)
|
||||
set(SUNSHINE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/sunshine.ico")
|
||||
endif()
|
||||
configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY)
|
||||
set(PLATFORM_TARGET_FILES
|
||||
sunshine/platform/windows.cpp
|
||||
sunshine/platform/windows_dxgi.cpp
|
||||
sunshine/platform/windows_wasapi.cpp
|
||||
ViGEmClient/src/ViGEmClient.cpp
|
||||
ViGEmClient/include/ViGEm/Client.h
|
||||
ViGEmClient/include/ViGEm/Common.h
|
||||
ViGEmClient/include/ViGEm/Util.h
|
||||
ViGEmClient/include/ViGEm/km/BusShared.h)
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/windows.rc"
|
||||
src/platform/windows/publish.cpp
|
||||
src/platform/windows/misc.h
|
||||
src/platform/windows/misc.cpp
|
||||
src/platform/windows/input.cpp
|
||||
src/platform/windows/display.h
|
||||
src/platform/windows/display_base.cpp
|
||||
src/platform/windows/display_vram.cpp
|
||||
src/platform/windows/display_ram.cpp
|
||||
src/platform/windows/audio.cpp
|
||||
third-party/ViGEmClient/src/ViGEmClient.cpp
|
||||
third-party/ViGEmClient/include/ViGEm/Client.h
|
||||
third-party/ViGEmClient/include/ViGEm/Common.h
|
||||
third-party/ViGEmClient/include/ViGEm/Util.h
|
||||
third-party/ViGEmClient/include/ViGEm/km/BusShared.h)
|
||||
|
||||
set(OPENSSL_LIBRARIES
|
||||
libssl.a
|
||||
libcrypto.a)
|
||||
|
||||
set(FFMPEG_INCLUDE_DIRS
|
||||
${SUNSHINE_PREPARED_BINARIES}/include)
|
||||
set(FFMPEG_LIBRARIES
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libhdr10plus.a
|
||||
z lzma bcrypt libiconv.a)
|
||||
|
||||
list(PREPEND PLATFORM_LIBRARIES
|
||||
winmm
|
||||
libstdc++.a
|
||||
libwinpthread.a
|
||||
libssp.a
|
||||
ksuser
|
||||
wsock32
|
||||
ws2_32
|
||||
iphlpapi
|
||||
windowsapp
|
||||
d3d11 dxgi
|
||||
d3d11 dxgi D3DCompiler
|
||||
setupapi
|
||||
dwmapi
|
||||
)
|
||||
|
||||
set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650")
|
||||
set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess")
|
||||
else()
|
||||
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json")
|
||||
set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650")
|
||||
set_source_files_properties(third-party/ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess")
|
||||
elseif(APPLE)
|
||||
add_compile_definitions(SUNSHINE_PLATFORM="macos")
|
||||
|
||||
option(SUNSHINE_MACOS_PACKAGE "Should only be used when creating a MACOS package/dmg." OFF)
|
||||
|
||||
link_directories(/opt/local/lib)
|
||||
link_directories(/usr/local/lib)
|
||||
ADD_DEFINITIONS(-DBOOST_LOG_DYN_LINK)
|
||||
|
||||
find_package(FFmpeg REQUIRED)
|
||||
FIND_LIBRARY(APP_SERVICES_LIBRARY ApplicationServices )
|
||||
FIND_LIBRARY(AV_FOUNDATION_LIBRARY AVFoundation )
|
||||
FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia )
|
||||
FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo )
|
||||
FIND_LIBRARY(FOUNDATION_LIBRARY Foundation )
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${APP_SERVICES_LIBRARY}
|
||||
${AV_FOUNDATION_LIBRARY}
|
||||
${CORE_MEDIA_LIBRARY}
|
||||
${CORE_VIDEO_LIBRARY}
|
||||
${FOUNDATION_LIBRARY})
|
||||
|
||||
set(PLATFORM_INCLUDE_DIRS
|
||||
${Boost_INCLUDE_DIR})
|
||||
|
||||
set(APPLE_PLIST_FILE ${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/Info.plist)
|
||||
|
||||
find_package(X11 REQUIRED)
|
||||
set(PLATFORM_TARGET_FILES
|
||||
sunshine/platform/linux.cpp
|
||||
sunshine/platform/linux_evdev.cpp)
|
||||
|
||||
set(PLATFORM_LIBRARIES
|
||||
Xfixes
|
||||
Xtst
|
||||
xcb
|
||||
xcb-shm
|
||||
xcb-xfixes
|
||||
${X11_LIBRARIES}
|
||||
src/platform/macos/av_audio.h
|
||||
src/platform/macos/av_audio.m
|
||||
src/platform/macos/av_img_t.h
|
||||
src/platform/macos/av_video.h
|
||||
src/platform/macos/av_video.m
|
||||
src/platform/macos/display.mm
|
||||
src/platform/macos/input.cpp
|
||||
src/platform/macos/microphone.mm
|
||||
src/platform/macos/misc.cpp
|
||||
src/platform/macos/misc.h
|
||||
src/platform/macos/nv12_zero_device.cpp
|
||||
src/platform/macos/nv12_zero_device.h
|
||||
src/platform/macos/publish.cpp
|
||||
third-party/TPCircularBuffer/TPCircularBuffer.c
|
||||
third-party/TPCircularBuffer/TPCircularBuffer.h
|
||||
${APPLE_PLIST_FILE})
|
||||
else()
|
||||
add_compile_definitions(SUNSHINE_PLATFORM="linux")
|
||||
|
||||
option(SUNSHINE_ENABLE_DRM "Enable KMS grab if available" ON)
|
||||
option(SUNSHINE_ENABLE_X11 "Enable X11 grab if available" ON)
|
||||
option(SUNSHINE_ENABLE_WAYLAND "Enable building wayland specific code" ON)
|
||||
option(SUNSHINE_ENABLE_CUDA "Enable cuda specific code" ON)
|
||||
|
||||
if(${SUNSHINE_ENABLE_X11})
|
||||
find_package(X11)
|
||||
else()
|
||||
set(X11_FOUND OFF)
|
||||
endif()
|
||||
|
||||
set(CUDA_FOUND OFF)
|
||||
if(${SUNSHINE_ENABLE_CUDA})
|
||||
include(CheckLanguage)
|
||||
check_language(CUDA)
|
||||
|
||||
if(CMAKE_CUDA_COMPILER)
|
||||
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES 35)
|
||||
endif()
|
||||
|
||||
set(CUDA_FOUND ON)
|
||||
enable_language(CUDA)
|
||||
endif()
|
||||
endif()
|
||||
if(${SUNSHINE_ENABLE_DRM})
|
||||
find_package(LIBDRM)
|
||||
find_package(LIBCAP)
|
||||
else()
|
||||
set(LIBDRM_FOUND OFF)
|
||||
set(LIBCAP_FOUND OFF)
|
||||
endif()
|
||||
if(${SUNSHINE_ENABLE_WAYLAND})
|
||||
find_package(Wayland)
|
||||
else()
|
||||
set(WAYLAND_FOUND OFF)
|
||||
endif()
|
||||
|
||||
find_package(FFMPEG REQUIRED)
|
||||
|
||||
if(X11_FOUND)
|
||||
add_compile_definitions(SUNSHINE_BUILD_X11)
|
||||
include_directories(${X11_INCLUDE_DIR})
|
||||
list(APPEND PLATFORM_TARGET_FILES src/platform/linux/x11grab.cpp)
|
||||
endif()
|
||||
|
||||
if(CUDA_FOUND)
|
||||
include_directories(third-party/nvfbc)
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
src/platform/linux/cuda.cu
|
||||
src/platform/linux/cuda.cpp
|
||||
third-party/nvfbc/NvFBC.h)
|
||||
|
||||
add_compile_definitions(SUNSHINE_BUILD_CUDA)
|
||||
endif()
|
||||
|
||||
if(LIBDRM_FOUND AND LIBCAP_FOUND)
|
||||
add_compile_definitions(SUNSHINE_BUILD_DRM)
|
||||
include_directories(${LIBDRM_INCLUDE_DIRS} ${LIBCAP_INCLUDE_DIRS})
|
||||
list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES} ${LIBCAP_LIBRARIES})
|
||||
list(APPEND PLATFORM_TARGET_FILES src/platform/linux/kmsgrab.cpp)
|
||||
list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1)
|
||||
elseif(LIBDRM_FOUND)
|
||||
message(WARNING "Found libdrm, yet there is no libcap")
|
||||
elseif(LIBDRM_FOUND)
|
||||
message(WARNING "Found libcap, yet there is no libdrm")
|
||||
endif()
|
||||
|
||||
if(WAYLAND_FOUND)
|
||||
add_compile_definitions(SUNSHINE_BUILD_WAYLAND)
|
||||
macro(genWayland FILENAME)
|
||||
make_directory(${CMAKE_BINARY_DIR}/generated-src)
|
||||
|
||||
message("wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c")
|
||||
message("wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h")
|
||||
execute_process(
|
||||
COMMAND wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c
|
||||
COMMAND wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h
|
||||
|
||||
RESULT_VARIABLE EXIT_INT
|
||||
)
|
||||
|
||||
if(NOT ${EXIT_INT} EQUAL 0)
|
||||
message(FATAL_ERROR "wayland-scanner failed")
|
||||
endif()
|
||||
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c
|
||||
${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h
|
||||
)
|
||||
endmacro()
|
||||
|
||||
genWayland(xdg-output-unstable-v1)
|
||||
genWayland(wlr-export-dmabuf-unstable-v1)
|
||||
|
||||
include_directories(
|
||||
${WAYLAND_INCLUDE_DIRS}
|
||||
${CMAKE_BINARY_DIR}/generated-src
|
||||
)
|
||||
|
||||
list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES})
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
src/platform/linux/wlgrab.cpp
|
||||
src/platform/linux/wayland.cpp)
|
||||
endif()
|
||||
if(NOT ${X11_FOUND} AND NOT (${LIBDRM_FOUND} AND ${LIBCAP_FOUND}) AND NOT ${WAYLAND_FOUND} AND NOT ${})
|
||||
message(FATAL_ERROR "Couldn't find either x11, wayland, cuda or (libdrm and libcap)")
|
||||
endif()
|
||||
|
||||
list(APPEND PLATFORM_TARGET_FILES
|
||||
src/platform/linux/publish.cpp
|
||||
src/platform/linux/vaapi.h
|
||||
src/platform/linux/vaapi.cpp
|
||||
src/platform/linux/cuda.h
|
||||
src/platform/linux/graphics.h
|
||||
src/platform/linux/graphics.cpp
|
||||
src/platform/linux/misc.h
|
||||
src/platform/linux/misc.cpp
|
||||
src/platform/linux/audio.cpp
|
||||
src/platform/linux/input.cpp
|
||||
src/platform/linux/x11grab.h
|
||||
src/platform/linux/wayland.h
|
||||
third-party/glad/src/egl.c
|
||||
third-party/glad/src/gl.c
|
||||
third-party/glad/include/EGL/eglplatform.h
|
||||
third-party/glad/include/KHR/khrplatform.h
|
||||
third-party/glad/include/glad/gl.h
|
||||
third-party/glad/include/glad/egl.h)
|
||||
|
||||
list(APPEND PLATFORM_LIBRARIES
|
||||
dl
|
||||
evdev
|
||||
pulse
|
||||
pulse-simple
|
||||
)
|
||||
|
||||
set(PLATFORM_INCLUDE_DIRS
|
||||
${X11_INCLUDE_DIR}
|
||||
/usr/include/libevdev-1.0)
|
||||
|
||||
include_directories(
|
||||
/usr/include/libevdev-1.0
|
||||
third-party/nv-codec-headers/include
|
||||
third-party/glad/include)
|
||||
|
||||
if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH)
|
||||
set(SUNSHINE_EXECUTABLE_PATH "sunshine")
|
||||
endif()
|
||||
configure_file(sunshine.service.in sunshine.service @ONLY)
|
||||
endif()
|
||||
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
find_package(Boost COMPONENTS log filesystem REQUIRED)
|
||||
configure_file(version.h.in version.h @ONLY)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(SUNSHINE_TARGET_FILES
|
||||
moonlight-common-c/reedsolomon/rs.c
|
||||
moonlight-common-c/reedsolomon/rs.h
|
||||
moonlight-common-c/src/Input.h
|
||||
moonlight-common-c/src/Rtsp.h
|
||||
moonlight-common-c/src/RtspParser.c
|
||||
moonlight-common-c/src/Video.h
|
||||
sunshine/utility.h
|
||||
sunshine/uuid.h
|
||||
sunshine/config.h
|
||||
sunshine/config.cpp
|
||||
sunshine/main.cpp
|
||||
sunshine/main.h
|
||||
sunshine/crypto.cpp
|
||||
sunshine/crypto.h
|
||||
sunshine/nvhttp.cpp
|
||||
sunshine/nvhttp.h
|
||||
sunshine/stream.cpp
|
||||
sunshine/stream.h
|
||||
sunshine/video.cpp
|
||||
sunshine/video.h
|
||||
sunshine/thread_safe.h
|
||||
sunshine/input.cpp
|
||||
sunshine/input.h
|
||||
sunshine/audio.cpp
|
||||
sunshine/audio.h
|
||||
sunshine/platform/common.h
|
||||
sunshine/process.cpp
|
||||
sunshine/process.h
|
||||
sunshine/network.cpp
|
||||
sunshine/network.h
|
||||
sunshine/move_by_copy.h
|
||||
sunshine/task_pool.h
|
||||
sunshine/thread_pool.h
|
||||
third-party/moonlight-common-c/reedsolomon/rs.c
|
||||
third-party/moonlight-common-c/reedsolomon/rs.h
|
||||
third-party/moonlight-common-c/src/Input.h
|
||||
third-party/moonlight-common-c/src/Rtsp.h
|
||||
third-party/moonlight-common-c/src/RtspParser.c
|
||||
third-party/moonlight-common-c/src/Video.h
|
||||
src/upnp.cpp
|
||||
src/upnp.h
|
||||
src/cbs.cpp
|
||||
src/utility.h
|
||||
src/uuid.h
|
||||
src/config.h
|
||||
src/config.cpp
|
||||
src/main.cpp
|
||||
src/main.h
|
||||
src/crypto.cpp
|
||||
src/crypto.h
|
||||
src/nvhttp.cpp
|
||||
src/nvhttp.h
|
||||
src/httpcommon.cpp
|
||||
src/httpcommon.h
|
||||
src/confighttp.cpp
|
||||
src/confighttp.h
|
||||
src/rtsp.cpp
|
||||
src/rtsp.h
|
||||
src/stream.cpp
|
||||
src/stream.h
|
||||
src/video.cpp
|
||||
src/video.h
|
||||
src/input.cpp
|
||||
src/input.h
|
||||
src/audio.cpp
|
||||
src/audio.h
|
||||
src/platform/common.h
|
||||
src/process.cpp
|
||||
src/process.h
|
||||
src/network.cpp
|
||||
src/network.h
|
||||
src/move_by_copy.h
|
||||
src/task_pool.h
|
||||
src/thread_pool.h
|
||||
src/thread_safe.h
|
||||
src/sync.h
|
||||
src/round_robin.h
|
||||
${PLATFORM_TARGET_FILES})
|
||||
|
||||
set_source_files_properties(src/upnp.cpp PROPERTIES COMPILE_FLAGS -Wno-pedantic)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Simple-Web-Server
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/enet/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/reedsolomon
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/cbs/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/enet/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third-party/moonlight-common-c/reedsolomon
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
${PLATFORM_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
add_subdirectory(third-party/cbs)
|
||||
|
||||
string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE)
|
||||
if("${BUILD_TYPE}" STREQUAL "XDEBUG")
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -pedantic -ggdb3)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -ggdb3)
|
||||
if(WIN32)
|
||||
set_source_files_properties(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2)
|
||||
set_source_files_properties(src/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2)
|
||||
endif()
|
||||
else()
|
||||
add_definitions(-DNDEBUG)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O3)
|
||||
endif()
|
||||
|
||||
if(NOT SUNSHINE_ROOT)
|
||||
set(SUNSHINE_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
if(NOT SUNSHINE_ASSETS_DIR)
|
||||
set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_BINARY_DIR}/assets")
|
||||
endif()
|
||||
|
||||
if(SUNSHINE_STANDALONE)
|
||||
set(OPENSSL_LIBRARIES
|
||||
C:/msys64/mingw64/lib/libssl.a
|
||||
C:/msys64/mingw64/lib/libcrypto.a)
|
||||
if(NOT SUNSHINE_CONFIG_DIR)
|
||||
set(SUNSHINE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/config")
|
||||
endif()
|
||||
|
||||
if(UNIX AND CMAKE_INSTALL_PREFIX AND NOT ${SUNSHINE_CONFIGURE_APPIMAGE})
|
||||
set(SUNSHINE_ASSETS_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_ASSETS_DIR}")
|
||||
set(SUNSHINE_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/${SUNSHINE_CONFIG_DIR}")
|
||||
endif()
|
||||
|
||||
list(APPEND CBS_EXTERNAL_LIBRARIES
|
||||
cbs)
|
||||
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
libminiupnpc-static
|
||||
${CBS_EXTERNAL_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
stdc++fs
|
||||
enet
|
||||
opus
|
||||
${FFMPEG_LIBRARIES}
|
||||
@@ -182,10 +448,182 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${OPENSSL_LIBRARIES}
|
||||
${PLATFORM_LIBRARIES})
|
||||
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ROOT}/assets")
|
||||
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES})
|
||||
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 17)
|
||||
if(NOT WIN32)
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES Boost::log)
|
||||
endif()
|
||||
|
||||
target_compile_options(sunshine PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}")
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_CONFIG_DIR="${SUNSHINE_CONFIG_DIR}")
|
||||
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS})
|
||||
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 17
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
)
|
||||
|
||||
if(NOT DEFINED CMAKE_CUDA_STANDARD)
|
||||
set(CMAKE_CUDA_STANDARD 17)
|
||||
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_options(sunshine PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${APPLE_PLIST_FILE})
|
||||
endif()
|
||||
|
||||
foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS_CUDA "$<$<COMPILE_LANGUAGE:CUDA>:--compiler-options=${flag}>")
|
||||
endforeach()
|
||||
|
||||
target_compile_options(sunshine PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${SUNSHINE_COMPILE_OPTIONS}>;$<$<COMPILE_LANGUAGE:CUDA>:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>)
|
||||
|
||||
# CPACK / Packaging
|
||||
|
||||
# Common options
|
||||
set(CPACK_PACKAGE_NAME "Sunshine")
|
||||
set(CPACK_PACKAGE_VENDOR "LizardByte")
|
||||
set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/cpack_artifacts)
|
||||
set(CPACK_PACKAGE_CONTACT "https://app.lizardbyte.dev")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "https://github.com/LizardByte")
|
||||
set(CPACK_PACKAGE_DESCRIPTION ${CMAKE_PROJECT_DESCRIPTION})
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL ${CMAKE_PROJECT_HOMEPAGE_URL})
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png)
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_STRIP_FILES YES)
|
||||
|
||||
# Platform specific options
|
||||
if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.html
|
||||
install(TARGETS sunshine RUNTIME DESTINATION "." COMPONENT application)
|
||||
|
||||
# Adding tools
|
||||
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
|
||||
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
|
||||
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc)
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}" COMPONENT assets)
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT config)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}" COMPONENT config)
|
||||
|
||||
|
||||
# set(CPACK_NSIS_MUI_HEADERIMAGE "") # TODO: image should be 150x57 bmp
|
||||
|
||||
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}\\\\sunshine.ico")
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "${PROJECT__DIR}\\\\${PROJECT_EXE}")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") # The name of the directory that will be created in C:/Program files/
|
||||
string(APPEND CPACK_NSIS_DEFINES "\n RequestExecutionLevel admin") # TODO: Not sure if this is needed but it took me a while to figure out where to put this option so I'm leaving it here
|
||||
|
||||
# Sets permissions on the installed folder so that we can write in it
|
||||
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS
|
||||
"${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
|
||||
ExecWait 'icacls \\\"$INSTDIR\\\" /grant:r Users:\\\(OI\\\)\\\(CI\\\)\\\(F\\\)'
|
||||
")
|
||||
|
||||
# Adding an option for the start menu and PATH
|
||||
set(CPACK_NSIS_MODIFY_PATH "OFF") # TODO: it asks to add it to the PATH but is not working https://gitlab.kitware.com/cmake/cmake/-/issues/15635
|
||||
set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
||||
set(CPACK_NSIS_MUI_FINISHPAGE_RUN "${CMAKE_PROJECT_NAME}.exe")
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe") # This will be shown on the installed apps Windows settings
|
||||
set(CPACK_NSIS_CREATE_ICONS "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${CMAKE_PROJECT_NAME}.lnk' '\$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe'")
|
||||
|
||||
# Checking for previous installed versions
|
||||
# set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL "ON") # TODO: doesn't work on my machine when Sunshine is already installed
|
||||
|
||||
# Setting components groups and dependencies
|
||||
# sunshine binary
|
||||
set(CPACK_COMPONENT_APPLICATION_DISPLAY_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_COMPONENT_APPLICATION_DESCRIPTION "The main application.")
|
||||
set(CPACK_COMPONENT_APPLICATION_GROUP "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_COMPONENT_APPLICATION_REQUIRED true)
|
||||
set(CPACK_COMPONENT_APPLICATION_DEPENDS assets)
|
||||
|
||||
# assets
|
||||
set(CPACK_COMPONENT_ASSETS_DISPLAY_NAME "Assets")
|
||||
set(CPACK_COMPONENT_ASSETS_DESCRIPTION "Shaders, default box art, and web ui.")
|
||||
set(CPACK_COMPONENT_ASSETS_GROUP "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_COMPONENT_ASSETS_REQUIRED true)
|
||||
|
||||
# config
|
||||
set(CPACK_COMPONENT_CONFIG_DISPLAY_NAME "Config")
|
||||
set(CPACK_COMPONENT_CONFIG_DESCRIPTION "Default config and apps.json files.")
|
||||
set(CPACK_COMPONENT_CONFIG_GROUP "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_COMPONENT_CONFIG_REQUIRED true)
|
||||
|
||||
# audio tool
|
||||
set(CPACK_COMPONENT_AUDIO_DISPLAY_NAME "audio-info.exe")
|
||||
set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool that allows you to get information about sound devices.")
|
||||
set(CPACK_COMPONENT_AUDIO_GROUP "Tools")
|
||||
|
||||
# display tool
|
||||
set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info.exe")
|
||||
set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool that allows you to get information about graphics cards and displays.")
|
||||
set(CPACK_COMPONENT_DXGI_GROUP "Tools")
|
||||
|
||||
# service tool
|
||||
set(CPACK_COMPONENT_SUNSHINESVC_DISPLAY_NAME "sunshinesvc.exe")
|
||||
set(CPACK_COMPONENT_SUNSHINESVC_DESCRIPTION "CLI tool that allows you to enable/disable the Sunshine service.")
|
||||
set(CPACK_COMPONENT_SUNSHINESVC_GROUP "Tools")
|
||||
endif()
|
||||
if(APPLE)
|
||||
# TODO: bundle doesn't produce a valid .app use cpack -G DragNDrop
|
||||
set(CPACK_BUNDLE_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_BUNDLE_PLIST "${APPLE_PLIST_FILE}")
|
||||
set(CPACK_BUNDLE_ICON "${PROJECT_SOURCE_DIR}/sunshine.icns")
|
||||
# set(CPACK_BUNDLE_STARTUP_COMMAND "${INSTALL_RUNTIME_DIR}/sunshine")
|
||||
endif()
|
||||
if(APPLE AND SUNSHINE_MACOS_PACKAGE) # TODO
|
||||
set(prefix "${CMAKE_PROJECT_NAME}.app/Contents")
|
||||
set(INSTALL_RUNTIME_DIR "${prefix}/MacOS")
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}")
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}")
|
||||
|
||||
install(TARGETS sunshine
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime)
|
||||
elseif(UNIX)
|
||||
# Installation destination dir
|
||||
set(CPACK_SET_DESTDIR true)
|
||||
if(NOT CMAKE_INSTALL_PREFIX)
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local/sunshine")
|
||||
endif()
|
||||
|
||||
install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}")
|
||||
|
||||
if(APPLE)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}")
|
||||
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/misc/uninstall_pkg.sh" DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
else()
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/" DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/config/" DESTINATION "${SUNSHINE_CONFIG_DIR}")
|
||||
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/85-sunshine.rules" DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/sunshine.service" DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/user")
|
||||
|
||||
# Pre and post install
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||
"${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst"
|
||||
"${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst"
|
||||
"${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/conffiles")
|
||||
set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/preinst")
|
||||
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/misc/postinst")
|
||||
|
||||
# Dependencies
|
||||
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libavdevice >= 4.3, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
|
||||
101
DOCKER_README.md
Normal file
101
DOCKER_README.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Docker
|
||||
|
||||
## Using docker run
|
||||
Create and run the container (substitute your `<values>`):
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name=sunshine \
|
||||
--restart=unless-stopped
|
||||
-v <path to data>:/config \
|
||||
-e PUID=<uid> \
|
||||
-e PGID=<gid> \
|
||||
-e TZ=<timezone> \
|
||||
-p 47984-47990:47984-47990/tcp \
|
||||
-p 48010:48010 \
|
||||
-p 47998-48000:47998-48000/udp \
|
||||
lizardbyte/sunshine
|
||||
```
|
||||
|
||||
To update the container it must be removed and recreated:
|
||||
|
||||
```bash
|
||||
# Stop the container
|
||||
docker stop sunshine
|
||||
# Remove the container
|
||||
docker rm sunshine
|
||||
# Pull the latest update
|
||||
docker pull lizardbyte/sunshine
|
||||
# Run the container with the same parameters as before
|
||||
docker run -d ...
|
||||
```
|
||||
|
||||
## Using docker-compose
|
||||
|
||||
Create a `docker-compose.yml` file with the following contents (substitute your `<values>`):
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
sunshine:
|
||||
image: lizardbyte/sunshine
|
||||
container_name: sunshine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- <path to data>:/config
|
||||
environment:
|
||||
- PUID=<uid>
|
||||
- PGID=<gid>
|
||||
- TZ=<timezone>
|
||||
ports:
|
||||
- 47984-47990:47984-47990/tcp
|
||||
- 48010:48010
|
||||
- 47998-48000:47998-48000/udp
|
||||
```
|
||||
|
||||
Create and start the container (run the command from the same folder as your `docker-compose.yml` file):
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To update the container:
|
||||
```bash
|
||||
# Pull the latest update
|
||||
docker-compose pull
|
||||
# Update and restart the container
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Parameters
|
||||
You must substitute the `<values>` with your own settings.
|
||||
|
||||
Parameters are split into two halves separated by a colon. The left side represents the host and the right side the
|
||||
container.
|
||||
|
||||
**Example:** `-p external:internal` - This shows the port mapping from internal to external of the container.
|
||||
Therefore `-p 47990:47990` would expose port `47990` from inside the container to be accessible from the host's IP on
|
||||
port `47990` (e.g. `http://<host_ip>:47990`). The internal port must be `47990`, but the external port may be changed
|
||||
(e.g. `-p 8080:47990`). All the ports listed in the `docker run` and `docker-compose` examples are required.
|
||||
|
||||
|
||||
| Parameter | Function | Example Value | Required |
|
||||
| --------------------------- | -------------------- | ------------------- | -------- |
|
||||
| `-p <port>:47990` | Web UI Port | `47990` | True |
|
||||
| `-v <path to data>:/config` | Volume mapping | `/home/sunshine` | True |
|
||||
| `-e PUID=<uid>` | User ID | `1001` | False |
|
||||
| `-e PGID=<gid>` | Group ID | `1001` | False |
|
||||
| `-e TZ=<timezone>` | Lookup TZ value [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `America/New_York` | True |
|
||||
|
||||
### User / Group Identifiers:
|
||||
|
||||
When using data volumes (-v flags) permissions issues can arise between the host OS and the container. To avoid this
|
||||
issue you can specify the user PUID and group PGID. Ensure the data volume directory on the host is owned by the same
|
||||
user you specify.
|
||||
|
||||
In this instance `PUID=1001` and `PGID=1001`. To find yours use id user as below:
|
||||
|
||||
```bash
|
||||
$ id dockeruser
|
||||
uid=1001(dockeruser) gid=1001(dockergroup) groups=1001(dockergroup)
|
||||
```
|
||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
3
NOTICE
Normal file
3
NOTICE
Normal file
@@ -0,0 +1,3 @@
|
||||
©2018 Valve Corporation. Steam and the Steam logo are trademarks and/or
|
||||
registered trademarks of Valve Corporation in the U.S. and/or other countries. All
|
||||
rights reserved.
|
||||
67
README.rst
Normal file
67
README.rst
Normal file
@@ -0,0 +1,67 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/README.rst
|
||||
|
||||
Overview
|
||||
========
|
||||
LizardByte has the full documentation hosted on `Read the Docs <https://docs.lizardbyte.dev/projects/sunshine/>`_.
|
||||
|
||||
About
|
||||
-----
|
||||
Sunshine is a Game stream host for Moonlight.
|
||||
Sunshine is a self hosted, low latency, cloud gaming solution with support for AMD, Intel, and Nvidia gpus.
|
||||
It is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||
Connect to Sunshine from any Moonlight client, available for nearly any device imaginable.
|
||||
|
||||
These are the advantages of Sunshine over GeForce Experience.
|
||||
|
||||
- FOSS (Free and Open Source Software)
|
||||
- Multi-platform
|
||||
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
|
||||
- Pair over web ui
|
||||
- Supports AMD, Intel, and Nvidia GPUs for encoding
|
||||
- Supports software encoding
|
||||
- Supports streaming to multiple clients
|
||||
- Web UI for configuration
|
||||
|
||||
Integrations
|
||||
------------
|
||||
|
||||
.. image:: https://img.shields.io/github/workflow/status/lizardbyte/sunshine/CI/master?label=CI%20build&logo=github&style=for-the-badge
|
||||
:alt: GitHub Workflow Status (CI)
|
||||
:target: https://github.com/LizardByte/Sunshine/actions/workflows/CI.yml?query=branch%3Amaster
|
||||
|
||||
.. image:: https://img.shields.io/github/workflow/status/lizardbyte/sunshine/localize/nightly?label=localize%20build&logo=github&style=for-the-badge
|
||||
:alt: GitHub Workflow Status (localize)
|
||||
:target: https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Anightly
|
||||
|
||||
.. image:: https://img.shields.io/readthedocs/sunshinestream?label=Docs&style=for-the-badge&logo=readthedocs
|
||||
:alt: Read the Docs
|
||||
:target: http://sunshinestream.readthedocs.io/
|
||||
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=localized&style=for-the-badge&query=%24.progress..data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json&logo=crowdin
|
||||
:alt: CrowdIn
|
||||
:target: https://crowdin.com/project/sunshinestream
|
||||
|
||||
Support
|
||||
---------
|
||||
|
||||
Our support methods are listed in our `LizardByte Docs <https://docs.lizardbyte.dev/en/latest/about/support.html>`_.
|
||||
|
||||
Downloads
|
||||
---------
|
||||
|
||||
.. image:: https://img.shields.io/github/downloads/lizardbyte/sunshine/total?style=for-the-badge&logo=github
|
||||
:alt: GitHub Releases
|
||||
:target: https://github.com/LizardByte/Sunshine/releases/latest
|
||||
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=AUR&style=for-the-badge&query=$.results.0.NumVotes&url=https%3A%2F%2Fapp.lizardbyte.dev%2Funo%2Faur%2Fsunshine.json&logo=archlinux
|
||||
:alt: AUR votes
|
||||
:target: https://aur.archlinux.org/packages/sunshine
|
||||
|
||||
.. comment
|
||||
image:: https://img.shields.io/docker/pulls/lizardbyte/sunshine?style=for-the-badge&logo=docker
|
||||
:alt: Docker
|
||||
:target: https://hub.docker.com/r/lizardbyte/sunshine
|
||||
108
README.txt
108
README.txt
@@ -1,108 +0,0 @@
|
||||
######### Linux ##############
|
||||
|
||||
Requirements:
|
||||
Ubuntu 19.10: cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
|
||||
Compilation:
|
||||
* git clone <repository> --recurse-submodules
|
||||
* mkdir build && cd build
|
||||
* cmake ..
|
||||
* make
|
||||
|
||||
|
||||
Setup:
|
||||
* sunshine needs access to uinput to create mouse and gamepad events:
|
||||
* Add user to group 'input': "usermod -a -G input username
|
||||
* Create a file: "/etc/udev/rules.d/85-input.rules"
|
||||
* The contents of the file is as follows:
|
||||
SUBSYSTEM=="input", mode="660"
|
||||
* assets/sunshine.conf is an example configuration file. Modify it as you see fit and use it by running: "sunshine path/to/sunshine.conf"
|
||||
* assets/sunshine.service is used to start sunshine in the background:
|
||||
* cp sunshine.service $HOME/.config/systemd/user/
|
||||
* Modify $HOME/.config/systemd/user/sunshine.conf to point to the sunshine executable
|
||||
* systemctl --user start sunshine
|
||||
|
||||
* assets/apps.json is an example of a list of applications that are started just before running a stream:
|
||||
* See below for a detailed explanation
|
||||
|
||||
Trouleshooting:
|
||||
* If you get "Could not create Sunshine Gamepad: Permission Denied" Try the following steps before running sunshine:
|
||||
* sudo chown root:input /dev/uinput
|
||||
* sudo chmod 660 /dev/uinput
|
||||
* If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
|
||||
* pacmd list-sources | grep "name:"
|
||||
* Copy the name to the configuration option "audio_sink"
|
||||
* restart sunshine
|
||||
|
||||
|
||||
|
||||
######### Windows 10 ############
|
||||
|
||||
Requirements:
|
||||
MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost
|
||||
|
||||
Compilation:
|
||||
* git clone <repository> --recurse-submodules
|
||||
* mkdir build && cd build
|
||||
* cmake -G"Unix Makefiles" ..
|
||||
* make
|
||||
|
||||
Setup:
|
||||
* <optional> Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases]
|
||||
|
||||
|
||||
|
||||
|
||||
######### Common #############
|
||||
|
||||
Usage:
|
||||
* run "sunshine path/to/sunshine.conf"
|
||||
* In Moonlight: Add PC manually
|
||||
* When Moonlight request you insert the correct pin on sunshine:
|
||||
wget xxx.xxx.xxx.xxx:47989/pin/xxxx -- where the first few x's are substituted by the ip of Sunshine and the final 4 x'es are substituted by the pin
|
||||
or
|
||||
Type in the URL bar of your browser: xxx.xxx.xxx.xxx:47989/pin/xxxx -- where the first few x's are substituted by the ip of the final 4 x'es are subsituted by the pin
|
||||
* Click on one of the Applications listed
|
||||
* Have fun :)
|
||||
|
||||
|
||||
Note:
|
||||
* The Windows key is not passed through by Moonlight, therefore Sunshine maps Right-Alt key to the Windows key
|
||||
* If you set Video Bitrate to 0.5Mb/s:
|
||||
* Sunshine will use CRF or QP to controll the quality of the stream. (See example configuration file for more details)
|
||||
* This is less CPU intensive and it has lower average bandwith requirements compared to manually setting bitrate to acceptable quality
|
||||
* However, it has higher peak bitrates, forcing Sunshine to drop entire frames when streaming 1080P due to their size.
|
||||
* When this happens, the video portion of the stream appears to be frozen.
|
||||
* This is rare enough that using this for the desktop environment is tolerable (in my opinion), however for gaming not so much.
|
||||
|
||||
|
||||
Credits:
|
||||
* Simple-Web-Server [https://gitlab.com/eidheim/Simple-Web-Server]
|
||||
* Moonlight [https://github.com/moonlight-stream]
|
||||
* Looking-Glass [https://github.com/gnif/LookingGlass] (For showing me how to properly capture frames on Windows, saving me a lot of time :)
|
||||
|
||||
|
||||
|
||||
Application List:
|
||||
* You can use Environment variables in place of values
|
||||
* $(HOME) will be replaced by the value of $HOME
|
||||
* $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME)
|
||||
* env: Adds or overwrites Environment variables for the commands/applications run by Sunshine.
|
||||
* "Variable name":"Variable value"
|
||||
* apps: The list of applications
|
||||
* name: Self explanatory
|
||||
* output <optional>: The file where the output of the command is stored
|
||||
* If it is not specified, the output is ignored
|
||||
* prep-cmd: A list of commands to be run before/after the application
|
||||
* If any of the prep-commands fail, starting the application is aborted
|
||||
* do: Run before the application
|
||||
* If it fails, all 'undo' commands of the previously succeeded 'do' commands are run
|
||||
* undo <optional>: Run after the application has terminated
|
||||
* This should not fail considering it is supposed to undo the 'do' commands.
|
||||
* If it fails, Sunshine is terminated
|
||||
* cmd <optional>: The main application
|
||||
* If not specified, a processs is started that sleeps indefinitely
|
||||
|
||||
When an application is started, if there is an application already running, it will be terminated.
|
||||
When the application has been shutdown, the stream shuts down as well.
|
||||
In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application, instead it simply starts a stream.
|
||||
Submodule Simple-Web-Server deleted from f37a41d48b
Submodule ViGEmClient deleted from 52682b59c4
37
appveyor.yml
37
appveyor.yml
@@ -1,37 +0,0 @@
|
||||
image:
|
||||
- Ubuntu
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- BUILD_TYPE: Debug
|
||||
- BUILD_TYPE: Release
|
||||
|
||||
install:
|
||||
- sh: sudo add-apt-repository ppa:hnakamur/icu
|
||||
- sh: sudo add-apt-repository ppa:hnakamur/boost
|
||||
- sh: sudo apt update
|
||||
- sh: sudo apt install -y cmake libssl-dev libavdevice-dev libboost-thread1.67-dev libboost-filesystem1.67-dev libboost-log1.67-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
- sh: sudo update-alternatives --set gcc /usr/bin/gcc-8
|
||||
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
|
||||
|
||||
before_build:
|
||||
- git submodule update --init --recursive
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
||||
build_script:
|
||||
- cmd: set OLDPATH=%PATH%
|
||||
- cmd: set PATH=C:\msys64\mingw64\bin
|
||||
- sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DOPENSSL_ROOT_DIR=C:\OpenSSL-v111-Win64 -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ROOT=. -G "MinGW Makefiles" ..
|
||||
- sh: make -j$(nproc)
|
||||
- cmd: mingw32-make -j2
|
||||
- cmd: set PATH=%OLDPATH%
|
||||
|
||||
after_build:
|
||||
- cmd: 7z a Sunshine-Windows.zip ..\assets\
|
||||
- cmd: 7z a Sunshine-Windows.zip sunshine.exe
|
||||
- cmd: 7z a Sunshine-Windows.zip tools\dxgi-info.exe
|
||||
- cmd: 7z a Sunshine-Windows.zip tools\audio-info.exe
|
||||
- cmd: appveyor PushArtifact Sunshine-Windows.zip
|
||||
@@ -1,111 +0,0 @@
|
||||
# If no external IP address is given, the local IP address is used
|
||||
# external_ip = 123.456.789.12
|
||||
|
||||
# The private key must be 2048 bits
|
||||
# pkey = /dir/pkey.pem
|
||||
|
||||
# The certificate must be signed with a 2048 bit key
|
||||
# cert = /dir/cert.pem
|
||||
|
||||
# The name displayed by Moonlight
|
||||
# If not specified, the PC's hostname is used
|
||||
# sunshine_name = Sunshine
|
||||
|
||||
# The minimum log level printed to standard out
|
||||
#
|
||||
# none -> no logs are printed to standard out
|
||||
#
|
||||
# verbose = [0]
|
||||
# debug = [1]
|
||||
# info = [2]
|
||||
# warning = [3]
|
||||
# error = [4]
|
||||
# fatal = [5]
|
||||
# none = [6]
|
||||
#
|
||||
# min_log_level = info
|
||||
|
||||
# The origin of the remote endpoint address that is not denied for HTTP method /pin
|
||||
# Could be any of the following values:
|
||||
# pc|lan|wan
|
||||
# pc: Only localhost may access /pin
|
||||
# lan: Only those in LAN may access /pin
|
||||
# wan: Anyone may access /pin
|
||||
#
|
||||
# origin_pin_allowed = lan
|
||||
|
||||
# The file where current state of Sunshine is stored
|
||||
# file_state = sunshine_state.json
|
||||
|
||||
# How long to wait in milliseconds for data from moonlight before shutting down the stream
|
||||
ping_timeout = 2000
|
||||
|
||||
# The file where configuration for the different applications that Sunshine can run during a stream
|
||||
# file_apps = apps.json
|
||||
|
||||
# How much error correcting packets must be send for every video
|
||||
# This is just some random number, don't know the optimal value
|
||||
# The higher fec_percentage, the lower space for the actual data to send per frame there is
|
||||
#
|
||||
# The value must be greater than 0 and lower than or equal to 100
|
||||
fec_percentage = 10
|
||||
|
||||
|
||||
# The back/select button on the controller
|
||||
# On the Shield, the home and powerbutton are not passed to Moonlight
|
||||
# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
|
||||
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
|
||||
# back_button_timeout = 2000
|
||||
|
||||
|
||||
# The name of the audio sink used for Audio Loopback
|
||||
# If you do not specify this variable, pulseaudio will select the default monitor device.
|
||||
#
|
||||
# You can find the name of the audio sink using the following command:
|
||||
# !! Linux only !!
|
||||
# pacmd list-sources | grep "name:"
|
||||
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor
|
||||
#
|
||||
# !! Windows only !!
|
||||
# tools\audio-info.exe
|
||||
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
|
||||
|
||||
# !! Windows only !!
|
||||
# You can select the video card you want to stream:
|
||||
# The appropriate values can be found using the following command:
|
||||
# tools\dxgi-info.exe
|
||||
# adapter_name = Radeon RX 580 Series
|
||||
# output_name = \\.\DISPLAY1
|
||||
|
||||
|
||||
###############################################
|
||||
# FFmpeg software encoding parameters
|
||||
# Honestly, I have no idea what the optimal values would be.
|
||||
# Play around with this :)
|
||||
|
||||
# Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality
|
||||
# Higher value means more compression, but less quality
|
||||
# If crf == 0, then use QP directly instead
|
||||
crf = 0
|
||||
|
||||
# Quantitization Parameter
|
||||
# Higher value means more compression, but less quality
|
||||
# If crf != 0, then this parameter is ignored
|
||||
qp = 28
|
||||
|
||||
# Minimum number of threads used by ffmpeg to encode the video.
|
||||
# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
|
||||
# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
|
||||
# value that can reliably encode at your desired streaming settings on your hardware.
|
||||
min_threads = 2
|
||||
|
||||
# Allows the client to request HEVC Main or HEVC Main10 video streams.
|
||||
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance.
|
||||
# If set to 0 (default), Sunshine will not advertise support for HEVC
|
||||
# If set to 1, Sunshine will advertise support for HEVC Main profile
|
||||
# If set to 2, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
|
||||
# hevc_mode = 2
|
||||
|
||||
# See x264 --fullhelp for the different presets
|
||||
preset = superfast
|
||||
tune = zerolatency
|
||||
@@ -1,13 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sunshine Gamestream Server for Moonlight
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/%u
|
||||
Environment="DISPLAY=:0"
|
||||
Type=simple
|
||||
# wait for Xorg
|
||||
ExecStartPre=/bin/sh -c 'while ! pgrep Xorg; do sleep 2; done'
|
||||
ExecStart=/home/%u/Github/sunshine/cmake-build-release/sunshine
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
21
cmake/FindLIBCAP.cmake
Normal file
21
cmake/FindLIBCAP.cmake
Normal file
@@ -0,0 +1,21 @@
|
||||
# - Try to find Libcap
|
||||
# Once done this will define
|
||||
#
|
||||
# LIBCAP_FOUND - system has Libcap
|
||||
# LIBCAP_INCLUDE_DIRS - the Libcap include directory
|
||||
# LIBCAP_LIBRARIES - the libraries needed to use Libcap
|
||||
# LIBCAP_DEFINITIONS - Compiler switches required for using Libcap
|
||||
|
||||
# Use pkg-config to get the directories and then use these values
|
||||
# in the find_path() and find_library() calls
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_LIBCAP libcap)
|
||||
|
||||
set(LIBCAP_DEFINITIONS ${PC_LIBCAP_CFLAGS})
|
||||
|
||||
find_path(LIBCAP_INCLUDE_DIRS sys/capability.h PATHS ${PC_LIBCAP_INCLUDEDIR} ${PC_LIBCAP_INCLUDE_DIRS})
|
||||
find_library(LIBCAP_LIBRARIES NAMES libcap.so PATHS ${PC_LIBCAP_LIBDIR} ${PC_LIBCAP_LIBRARY_DIRS})
|
||||
mark_as_advanced(LIBCAP_INCLUDE_DIRS LIBCAP_LIBRARIES)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LIBCAP REQUIRED_VARS LIBCAP_LIBRARIES LIBCAP_INCLUDE_DIRS)
|
||||
21
cmake/FindLIBDRM.cmake
Normal file
21
cmake/FindLIBDRM.cmake
Normal file
@@ -0,0 +1,21 @@
|
||||
# - Try to find Libdrm
|
||||
# Once done this will define
|
||||
#
|
||||
# LIBDRM_FOUND - system has Libdrm
|
||||
# LIBDRM_INCLUDE_DIRS - the Libdrm include directory
|
||||
# LIBDRM_LIBRARIES - the libraries needed to use Libdrm
|
||||
# LIBDRM_DEFINITIONS - Compiler switches required for using Libdrm
|
||||
|
||||
# Use pkg-config to get the directories and then use these values
|
||||
# in the find_path() and find_library() calls
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_LIBDRM libdrm)
|
||||
|
||||
set(LIBDRM_DEFINITIONS ${PC_LIBDRM_CFLAGS})
|
||||
|
||||
find_path(LIBDRM_INCLUDE_DIRS drm.h PATHS ${PC_LIBDRM_INCLUDEDIR} ${PC_LIBDRM_INCLUDE_DIRS} PATH_SUFFIXES libdrm)
|
||||
find_library(LIBDRM_LIBRARIES NAMES libdrm.so PATHS ${PC_LIBDRM_LIBDIR} ${PC_LIBDRM_LIBRARY_DIRS})
|
||||
mark_as_advanced(LIBDRM_INCLUDE_DIRS LIBDRM_LIBRARIES)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LIBDRM REQUIRED_VARS LIBDRM_LIBRARIES LIBDRM_INCLUDE_DIRS)
|
||||
78
cmake/FindWayland.cmake
Normal file
78
cmake/FindWayland.cmake
Normal file
@@ -0,0 +1,78 @@
|
||||
# Try to find Wayland on a Unix system
|
||||
#
|
||||
# This will define:
|
||||
#
|
||||
# WAYLAND_FOUND - True if Wayland is found
|
||||
# WAYLAND_LIBRARIES - Link these to use Wayland
|
||||
# WAYLAND_INCLUDE_DIRS - Include directory for Wayland
|
||||
# WAYLAND_DEFINITIONS - Compiler flags for using Wayland
|
||||
#
|
||||
# In addition the following more fine grained variables will be defined:
|
||||
#
|
||||
# Wayland_Client_FOUND WAYLAND_CLIENT_INCLUDE_DIRS WAYLAND_CLIENT_LIBRARIES
|
||||
# Wayland_Server_FOUND WAYLAND_SERVER_INCLUDE_DIRS WAYLAND_SERVER_LIBRARIES
|
||||
# Wayland_EGL_FOUND WAYLAND_EGL_INCLUDE_DIRS WAYLAND_EGL_LIBRARIES
|
||||
# Wayland_Cursor_FOUND WAYLAND_CURSOR_INCLUDE_DIRS WAYLAND_CURSOR_LIBRARIES
|
||||
#
|
||||
# Copyright (c) 2013 Martin Gräßlin <mgraesslin@kde.org>
|
||||
# 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
IF (NOT WIN32)
|
||||
|
||||
# Use pkg-config to get the directories and then use these values
|
||||
# in the find_path() and find_library() calls
|
||||
find_package(PkgConfig)
|
||||
PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor)
|
||||
|
||||
set(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS})
|
||||
|
||||
find_path(WAYLAND_CLIENT_INCLUDE_DIRS NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
|
||||
find_library(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
|
||||
if(WAYLAND_CLIENT_INCLUDE_DIRS AND WAYLAND_CLIENT_LIBRARIES)
|
||||
set(Wayland_Client_FOUND TRUE)
|
||||
else()
|
||||
set(Wayland_Client_FOUND FALSE)
|
||||
endif()
|
||||
mark_as_advanced(WAYLAND_CLIENT_INCLUDE_DIRS WAYLAND_CLIENT_LIBRARIES)
|
||||
|
||||
find_path(WAYLAND_CURSOR_INCLUDE_DIRS NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
|
||||
find_library(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
|
||||
if(WAYLAND_CURSOR_INCLUDE_DIRS AND WAYLAND_CURSOR_LIBRARIES)
|
||||
set(Wayland_Cursor_FOUND TRUE)
|
||||
else()
|
||||
set(Wayland_Cursor_FOUND FALSE)
|
||||
endif()
|
||||
mark_as_advanced(WAYLAND_CURSOR_INCLUDE_DIRS WAYLAND_CURSOR_LIBRARIES)
|
||||
|
||||
find_path(WAYLAND_EGL_INCLUDE_DIRS NAMES wayland-egl.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
|
||||
find_library(WAYLAND_EGL_LIBRARIES NAMES wayland-egl HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
|
||||
if(WAYLAND_EGL_INCLUDE_DIRS AND WAYLAND_EGL_LIBRARIES)
|
||||
set(Wayland_EGL_FOUND TRUE)
|
||||
else()
|
||||
set(Wayland_EGL_FOUND FALSE)
|
||||
endif()
|
||||
mark_as_advanced(WAYLAND_EGL_INCLUDE_DIRS WAYLAND_EGL_LIBRARIES)
|
||||
|
||||
find_path(WAYLAND_SERVER_INCLUDE_DIRS NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
|
||||
find_library(WAYLAND_SERVER_LIBRARIES NAMES wayland-server HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
|
||||
if(WAYLAND_SERVER_INCLUDE_DIRS AND WAYLAND_SERVER_LIBRARIES)
|
||||
set(Wayland_Server_FOUND TRUE)
|
||||
else()
|
||||
set(Wayland_Server_FOUND FALSE)
|
||||
endif()
|
||||
mark_as_advanced(WAYLAND_SERVER_INCLUDE_DIRS WAYLAND_SERVER_LIBRARIES)
|
||||
|
||||
set(WAYLAND_INCLUDE_DIRS ${WAYLAND_CLIENT_INCLUDE_DIRS} ${WAYLAND_SERVER_INCLUDE_DIRS} ${WAYLAND_EGL_INCLUDE_DIRS} ${WAYLAND_CURSOR_INCLUDE_DIRS})
|
||||
set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES})
|
||||
mark_as_advanced(WAYLAND_INCLUDE_DIRS WAYLAND_LIBRARIES)
|
||||
|
||||
list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIRS)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(Wayland REQUIRED_VARS WAYLAND_LIBRARIES WAYLAND_INCLUDE_DIRS HANDLE_COMPONENTS)
|
||||
|
||||
ENDIF ()
|
||||
22
crowdin.yml
Normal file
22
crowdin.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
"base_path": "."
|
||||
"base_url": "https://api.crowdin.com" # optional (for Crowdin Enterprise only)
|
||||
"preserve_hierarchy": false # flatten tree on crowdin
|
||||
"pull_request_labels": [
|
||||
"crowdin",
|
||||
"l10n"
|
||||
]
|
||||
|
||||
"files": [
|
||||
{
|
||||
"source": "/locale/*.po",
|
||||
"translation": "/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%",
|
||||
"languages_mapping": {
|
||||
"two_letters_code": {
|
||||
# map non-two letter codes here, left side is crowdin designation, right side is babel designation
|
||||
"en-GB": "en_GB",
|
||||
"en-US": "en_US"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
1113
docs/source/about/advanced_usage.rst
Normal file
1113
docs/source/about/advanced_usage.rst
Normal file
File diff suppressed because it is too large
Load Diff
5
docs/source/about/docker.rst
Normal file
5
docs/source/about/docker.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/DOCKER_README.md
|
||||
|
||||
.. Todo:: This is a planned feature. Currently no Dockerfile or image exists for Sunshine.
|
||||
|
||||
.. mdinclude:: ../../../DOCKER_README.md
|
||||
160
docs/source/about/installation.rst
Normal file
160
docs/source/about/installation.rst
Normal file
@@ -0,0 +1,160 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/installation.rst
|
||||
|
||||
Installation
|
||||
============
|
||||
The recommended method for running Sunshine is to use the `binaries`_ bundled with the `latest release`_.
|
||||
|
||||
Binaries
|
||||
--------
|
||||
Binaries of Sunshine are created for each release. They are available for Linux, and Windows.
|
||||
Binaries can be found in the `latest release`_.
|
||||
|
||||
.. Tip:: Some third party packages also exist. See
|
||||
:ref:`Third Party Packages <about/third_party_packages:third party packages>`.
|
||||
|
||||
Docker
|
||||
------
|
||||
.. Todo:: Docker images of Sunshine are planned to be included in the future.
|
||||
They will be available on `Dockerhub.io`_ and `ghcr.io`_.
|
||||
|
||||
Linux
|
||||
-----
|
||||
Follow the instructions for your preferred package type below.
|
||||
|
||||
AppImage
|
||||
^^^^^^^^
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:appimage?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
According to AppImageLint the AppImage can run on the following distros.
|
||||
|
||||
- [✖] Debian oldstable (buster)
|
||||
- [✔] Debian stable (bullseye)
|
||||
- [✔] Debian testing (bookworm)
|
||||
- [✔] Debian unstable (sid)
|
||||
- [✔] Ubuntu jammy
|
||||
- [✔] Ubuntu impish
|
||||
- [✔] Ubuntu focal
|
||||
- [✖] Ubuntu bionic
|
||||
- [✖] Ubuntu xenial
|
||||
- [✖] Ubuntu trusty
|
||||
- [✖] CentOS 7
|
||||
|
||||
#. Download ``sunshine-appimage.zip`` and extract the contents to your home directory.
|
||||
|
||||
AUR Package
|
||||
^^^^^^^^^^^
|
||||
#. Open terminal and run the following code.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://aur.archlinux.org/sunshine-git.git
|
||||
cd sunshine-git
|
||||
makepkg -fi
|
||||
|
||||
Debian Package
|
||||
^^^^^^^^^^^^^^
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:deb?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
#. Download ``sunshine.deb`` and run the following code.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt install -f ./sunshine.deb
|
||||
|
||||
.. Tip:: You can double click the deb file to see details about the package and begin installation.
|
||||
|
||||
Flatpak Package
|
||||
^^^^^^^^^^^^^^^
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:flatpak?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
.. Todo:: This package needs to have CUDA added.
|
||||
|
||||
#. Install `Flatpak <https://flatpak.org/setup/>`_ as required.
|
||||
#. Download ``sunshine.flatpak`` and run the following code.
|
||||
|
||||
System level (recommended)
|
||||
.. code-block:: bash
|
||||
|
||||
flatpak install --system sunshine.flatpak
|
||||
|
||||
User level
|
||||
.. code-block:: bash
|
||||
|
||||
flatpak install --user sunshine.flatpak
|
||||
|
||||
RPM Package
|
||||
^^^^^^^^^^^
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/pkg:rpm?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
#. Add `rpmfusion` repositories by running the following code.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
|
||||
https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
|
||||
|
||||
#. Download ``sunshine.rpm`` and run the following code.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf install ./sunshine.rpm
|
||||
|
||||
.. Tip:: You can double click the rpm file to see details about the package and begin installation.
|
||||
|
||||
macOS
|
||||
-----
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:macos?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
pkg
|
||||
.. Warning:: The `pkg` does not include runtime dependencies and should be considered experimental.
|
||||
|
||||
#. Download the ``sunshine.pkg`` file and install it as normal.
|
||||
|
||||
Portfile
|
||||
#. Install `MacPorts <https://www.macports.org>`_
|
||||
#. Update the Macports sources.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo nano /opt/local/etc/macports/sources.conf
|
||||
|
||||
Add this line, replacing your username, below the line that starts with ``rsync``.
|
||||
|
||||
file://Users/<username>/ports
|
||||
|
||||
``Ctrl+x``, then ``Y`` to exit and save changes.
|
||||
|
||||
#. Download the ``Portfile`` to ``~/Downloads`` and run the following code.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir -p ~/ports/multimedia/sunshine
|
||||
mv ~/Downlaods/Portfile ~/ports/multimedia/sunshine
|
||||
cd ~/ports
|
||||
portindex
|
||||
sudo port install sunshine
|
||||
|
||||
#. The first time you start Sunshine, you will be asked to grant access to screen recording and your microphone.
|
||||
|
||||
Windows
|
||||
-------
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:10?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
.. image:: https://img.shields.io/github/issues/lizardbyte/sunshine/os:windows:11?logo=github&style=for-the-badge
|
||||
:alt: GitHub issues by-label
|
||||
|
||||
Installed option:
|
||||
#. Download and install ``sunshine-windows.exe``
|
||||
|
||||
Standalone option:
|
||||
#. Download and extract ``sunshine-windows.zip``
|
||||
|
||||
.. _latest release: https://github.com/LizardByte/Sunshine/releases/latest
|
||||
.. _Dockerhub.io: https://hub.docker.com/repository/docker/lizardbyte/sunshine
|
||||
.. _ghcr.io: https://github.com/orgs/LizardByte/packages?repo_name=sunshine
|
||||
1
docs/source/about/overview.rst
Normal file
1
docs/source/about/overview.rst
Normal file
@@ -0,0 +1 @@
|
||||
.. include:: ../../../README.rst
|
||||
47
docs/source/about/third_party_packages.rst
Normal file
47
docs/source/about/third_party_packages.rst
Normal file
@@ -0,0 +1,47 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/third_party_packages.rst
|
||||
|
||||
Third Party Packages
|
||||
====================
|
||||
|
||||
.. Danger:: These packages are not maintained by LizardByte. Use at your own risk.
|
||||
|
||||
Chocolatey
|
||||
----------
|
||||
|
||||
.. image:: https://img.shields.io/chocolatey/v/Sunshine?style=for-the-badge&logo=chocolatey
|
||||
:alt: Chocolatey Version
|
||||
:target: https://community.chocolatey.org/packages/sunshine
|
||||
|
||||
.. image:: https://img.shields.io/chocolatey/dt/sunshine?style=for-the-badge&logo=chocolatey
|
||||
:alt: Chocolatey
|
||||
|
||||
Scoop
|
||||
-----
|
||||
|
||||
.. image:: https://img.shields.io/scoop/v/sunshine?bucket=extras&style=for-the-badge
|
||||
:alt: Scoop Version (extras bucket)
|
||||
:target: https://scoop.sh/#/apps?s=0&d=1&o=true&q=sunshine
|
||||
|
||||
Winget
|
||||
------
|
||||
.. image:: https://img.shields.io/badge/dynamic/xml?color=orange&label=Winget&style=for-the-badge&prefix=v&query=%2F%2Ftr%5B%40id%3D%27winget%27%5D%2Ftd%5B3%5D%2Fspan%2Fa&url=https%3A%2F%2Frepology.org%2Fproject%2Fsunshine%2Fversions&logo=microsoft
|
||||
:alt: Winget Version
|
||||
:target: https://github.com/microsoft/winget-pkgs/tree/master/manifests/l/LizardByte/Sunshine
|
||||
|
||||
Legacy GitHub Repo
|
||||
------------------
|
||||
|
||||
.. Attention:: This repo is not maintained. Thank you to Loki for bringing this amazing project to life!
|
||||
|
||||
.. image:: https://img.shields.io/static/v1?label=repo&message=loki-47-6F-64/sunshine&color=blue&style=for-the-badge&logo=github
|
||||
:alt: GitHub Maintainer
|
||||
:target: https://github.com/loki-47-6F-64/sunshine/releases
|
||||
|
||||
.. image:: https://img.shields.io/github/last-commit/loki-47-6F-64/sunshine?style=for-the-badge&logo=github
|
||||
:alt: GitHub last commit
|
||||
|
||||
.. image:: https://img.shields.io/github/release-date/loki-47-6F-64/sunshine?style=for-the-badge&logo=github
|
||||
:alt: GitHub Release Date
|
||||
|
||||
.. image:: https://img.shields.io/github/downloads/loki-47-6F-64/sunshine/total?style=for-the-badge&logo=github
|
||||
:alt: GitHub Releases
|
||||
221
docs/source/about/usage.rst
Normal file
221
docs/source/about/usage.rst
Normal file
@@ -0,0 +1,221 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/about/usage.rst
|
||||
|
||||
Usage
|
||||
=====
|
||||
#. See the `setup`_ section for your specific OS.
|
||||
#. Run ``sunshine <directory of conf file>/sunshine.conf``.
|
||||
|
||||
.. Note:: You do not need to specify a config file. If no config file is entered the default location will be used.
|
||||
|
||||
.. Attention:: The configuration file specified will be created if it doesn't exist.
|
||||
|
||||
.. Tip:: If using the Linux AppImage, replace ``sunshine`` with ``./sunshine.AppImage``
|
||||
|
||||
#. Configure Sunshine in the web ui
|
||||
The web ui is available on `https://localhost:47990 <https://localhost:47990>`_ by default. You may replace
|
||||
`localhost` with your internal ip address.
|
||||
|
||||
.. Attention:: Ignore any warning given by your browser about "insecure website".
|
||||
|
||||
.. Caution:: If running for the first time, make sure to note the username and password Sunshine showed to you,
|
||||
since you cannot get back later!
|
||||
|
||||
Add games and applications.
|
||||
This can be configured in the web ui.
|
||||
|
||||
.. Note:: Additionally, apps can be configured manually. `src_assets/<os>/config/apps.json` is an example of a
|
||||
list of applications that are started just before running a stream. This is the directory within the GitHub
|
||||
repo.
|
||||
|
||||
.. Attention:: Application list is not fully supported on macOS
|
||||
|
||||
#. In Moonlight, you may need to add the PC manually.
|
||||
#. When Moonlight request you insert the correct pin on sunshine:
|
||||
|
||||
- Login to the web ui
|
||||
- Go to "PIN" in the Navbar
|
||||
- Type in your PIN and press Enter, you should get a Success Message
|
||||
- In Moonlight, select one of the Applications listed
|
||||
|
||||
Network
|
||||
-------
|
||||
Sunshine will be available on port 47990 by default.
|
||||
|
||||
.. Danger:: Do not expose port 47990, or the web ui, to the internet!
|
||||
|
||||
Arguments
|
||||
---------
|
||||
To get a list of available arguments run the following:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sunshine --help
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
Linux
|
||||
^^^^^
|
||||
The deb and rpm packages handle these steps automatically. The AppImage does not, third party packages may not as well.
|
||||
|
||||
Sunshine needs access to `uinput` to create mouse and gamepad events.
|
||||
|
||||
#. Add user to group `input`, if this is the first time installing.
|
||||
.. code-block:: bash
|
||||
|
||||
sudo usermod -a -G input $USER
|
||||
|
||||
#. Create `udev` rules.
|
||||
.. code-block:: bash
|
||||
|
||||
sudo nano /etc/udev/rules.d/85-sunshine.rules
|
||||
|
||||
Input the following contents.
|
||||
|
||||
.. code-block::
|
||||
|
||||
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
|
||||
|
||||
Save the file and exit:
|
||||
|
||||
#. ``CTRL+X`` to start exit.
|
||||
#. ``Y`` to save modifications.
|
||||
|
||||
#. Optionally, configure autostart service
|
||||
- filename: ``~/.config/systemd/user/sunshine.service``
|
||||
- contents:
|
||||
|
||||
.. code-block::
|
||||
|
||||
[Unit]
|
||||
Description=Sunshine Gamestream Server for Moonlight
|
||||
|
||||
[Service]
|
||||
ExecStart=<see table>
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
|
||||
.. table::
|
||||
:widths: auto
|
||||
|
||||
======== ============================================== ===============
|
||||
package ExecStart Auto Configured
|
||||
======== ============================================== ===============
|
||||
aur /usr/bin/sunshine ✔
|
||||
deb /usr/bin/sunshine ✔
|
||||
rpm /usr/bin/sunshine ✔
|
||||
AppImage ~/sunshine.AppImage ✖
|
||||
Flatpak flatpak run dev.lizardbyte.sunshine ✖
|
||||
======== ============================================== ===============
|
||||
|
||||
Start once
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl --user start sunshine
|
||||
|
||||
Start on boot
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl --user enable sunshine
|
||||
|
||||
#. Additional Setup for KMS
|
||||
.. Note:: ``cap_sys_admin`` may as well be root, except you don't need to be root to run it. It is necessary to
|
||||
allow Sunshine to use KMS.
|
||||
|
||||
Enable
|
||||
.. code-block:: bash
|
||||
|
||||
sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine))
|
||||
|
||||
Disable
|
||||
.. code-block:: bash
|
||||
|
||||
sudo setcap -r $(readlink -f $(which sunshine))
|
||||
|
||||
#. Reboot
|
||||
.. code-block:: bash
|
||||
|
||||
sudo reboot now
|
||||
|
||||
macOS
|
||||
^^^^^
|
||||
Sunshine can only access microphones on macOS due to system limitations. To stream system audio use
|
||||
`Soundflower <https://github.com/mattingalls/Soundflower>`_ or
|
||||
`BlackHole <https://github.com/ExistentialAudio/BlackHole>`_ and
|
||||
select their sink as audio device in `sunshine.conf`.
|
||||
|
||||
.. Note:: Command Keys are not forwarded by Moonlight. Right Option-Key is mapped to CMD-Key.
|
||||
|
||||
.. Caution:: Gamepads are not currently supported.
|
||||
|
||||
Configure autostart service
|
||||
|
||||
MacPorts
|
||||
.. code-block:: bash
|
||||
|
||||
sudo port load Sunshine
|
||||
|
||||
Windows
|
||||
^^^^^^^
|
||||
For gamepad support, install `ViGEmBus <https://github.com/ViGEm/ViGEmBus/releases/latest>`_
|
||||
|
||||
Shortcuts
|
||||
---------
|
||||
All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight
|
||||
|
||||
- ``CTRL + ALT + SHIFT + N`` - Hide/Unhide the cursor (This may be useful for Remote Desktop Mode for Moonlight)
|
||||
- ``CTRL + ALT + SHIFT + F1/F13`` - Switch to different monitor for Streaming
|
||||
|
||||
Application List
|
||||
----------------
|
||||
- You can use Environment variables in place of values
|
||||
- ``$(HOME)`` will be replaced by the value of ``$HOME``
|
||||
- ``$$`` will be replaced by ``$``, e.g. ``$$(HOME)`` will be replaced by ``$(HOME)``
|
||||
- ``env`` - Adds or overwrites Environment variables for the commands/applications run by Sunshine
|
||||
- ``"Variable name":"Variable value"``
|
||||
- ``apps`` - The list of applications
|
||||
- Example application:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name":"An App",
|
||||
"cmd":"command to open app",
|
||||
"prep-cmd":[
|
||||
{
|
||||
"do":"some-command",
|
||||
"undo":"undo-that-command"
|
||||
}
|
||||
],
|
||||
"detached":[
|
||||
"some-command",
|
||||
"another-command"
|
||||
]
|
||||
}
|
||||
|
||||
- ``name`` - The name of the application/game
|
||||
- ``output`` - The file where the output of the command is stored
|
||||
- ``detached`` - A list of commands to be run and forgotten about
|
||||
- ``prep-cmd`` - A list of commands to be run before/after the application
|
||||
|
||||
- If any of the prep-commands fail, starting the application is aborted
|
||||
- ``do`` - Run before the application
|
||||
|
||||
- If it fails, all ``undo`` commands of the previously succeeded ``do`` commands are run
|
||||
|
||||
- ``undo`` - Run after the application has terminated
|
||||
|
||||
- This should not fail considering it is supposed to undo the ``do`` commands
|
||||
- If it fails, Sunshine is terminated
|
||||
|
||||
- ``cmd`` - The main application
|
||||
|
||||
- If not specified, a process is started that sleeps indefinitely
|
||||
|
||||
Considerations
|
||||
--------------
|
||||
- When an application is started, if there is an application already running, it will be terminated.
|
||||
- When the application has been shutdown, the stream shuts down as well.
|
||||
- In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application,
|
||||
instead it simply starts a stream.
|
||||
35
docs/source/building/build.rst
Normal file
35
docs/source/building/build.rst
Normal file
@@ -0,0 +1,35 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/build.rst
|
||||
|
||||
Build
|
||||
=====
|
||||
Sunshine binaries are built using `CMake <https://cmake.org/>`_. Cross compilation is not
|
||||
supported. That means the binaries must be built on the target operating system and architecture.
|
||||
|
||||
Building Locally
|
||||
----------------
|
||||
|
||||
Clone
|
||||
^^^^^
|
||||
Ensure `git <https://git-scm.com/>`_ is installed and run the following:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/lizardbyte/sunshine.git --recurse-submodules
|
||||
cd sunshine && mkdir build && cd build
|
||||
|
||||
Compile
|
||||
^^^^^^^
|
||||
See the section specific to your OS.
|
||||
|
||||
- :ref:`Linux <building/linux:linux>`
|
||||
- :ref:`macOS <building/macos:macos>`
|
||||
- :ref:`Windows <building/windows:windows>`
|
||||
|
||||
Remote Build
|
||||
------------
|
||||
It may be beneficial to build remotely in some cases. This will enable easier building on different operating systems.
|
||||
|
||||
#. Fork the project
|
||||
#. Activate workflows
|
||||
#. Trigger the `CI` workflow manually
|
||||
#. Download the artifacts/binaries from the workflow run summary
|
||||
302
docs/source/building/linux.rst
Normal file
302
docs/source/building/linux.rst
Normal file
@@ -0,0 +1,302 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/linux.rst
|
||||
|
||||
Linux
|
||||
=====
|
||||
|
||||
Requirements
|
||||
------------
|
||||
.. Danger:: Installing these dependencies may break your distribution. It is recommended to build in a virtual machine
|
||||
or to use the `Dockerfile builds`_ located in the `./scripts` directory.
|
||||
|
||||
Debian Bullseye
|
||||
^^^^^^^^^^^^^^^
|
||||
End of Life: TBD
|
||||
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt update && sudo apt install \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \ # KMS
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \ # Wayland
|
||||
libx11-dev \ # X11
|
||||
libxcb-shm0-dev \ # X11
|
||||
libxcb-xfixes0-dev \ # X11
|
||||
libxcb1-dev \ # X11
|
||||
libxfixes-dev \ # X11
|
||||
libxrandr-dev \ # X11
|
||||
libxtst-dev \ # X11
|
||||
nvidia-cuda-dev \ # Cuda, NvFBC
|
||||
nvidia-cuda-toolkit \ # Cuda, NvFBC
|
||||
|
||||
Fedora 35
|
||||
^^^^^^^^^
|
||||
End of Life: TBD
|
||||
|
||||
Install Repositories
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf update && \
|
||||
sudo dnf group install "Development Tools" && \
|
||||
sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
|
||||
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf install \
|
||||
boost-devel \
|
||||
boost-static.x86_64 \
|
||||
cmake \
|
||||
ffmpeg-devel \
|
||||
gcc-c++ \
|
||||
libevdev-devel \
|
||||
libX11-devel \ # X11
|
||||
libxcb-devel \ # X11
|
||||
libXcursor-devel \ # X11
|
||||
libXfixes-devel \ # X11
|
||||
libXinerama-devel \ # X11
|
||||
libXi-devel \ # X11
|
||||
libXrandr-devel \ # X11
|
||||
libXtst-devel \ # X11
|
||||
mesa-libGL-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
pulseaudio-libs-devel \
|
||||
rpm-build \ # if you want to build an RPM binary package
|
||||
|
||||
Ubuntu 18.04
|
||||
^^^^^^^^^^^^
|
||||
End of Life: April 2028
|
||||
|
||||
Install Repositories
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt update && sudo apt install \
|
||||
software-properties-common \
|
||||
&& add-apt-repository ppa:savoury1/graphics && \
|
||||
add-apt-repository ppa:savoury1/multimedia && \
|
||||
add-apt-repository ppa:savoury1/ffmpeg4 && \
|
||||
add-apt-repository ppa:savoury1/boost-defaults-1.71 && \
|
||||
add-apt-repository ppa:ubuntu-toolchain-r/test && \
|
||||
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt install \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc-10 \
|
||||
git \
|
||||
g++-10 \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem1.71-dev \
|
||||
libboost-log1.71-dev \
|
||||
libboost-regex1.71-dev \
|
||||
libboost-thread1.71-dev \
|
||||
libcap-dev \ # KMS
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \ # Wayland
|
||||
libx11-dev \ # X11
|
||||
libxcb-shm0-dev \ # X11
|
||||
libxcb-xfixes0-dev \ # X11
|
||||
libxcb1-dev \ # X11
|
||||
libxfixes-dev \ # X11
|
||||
libxrandr-dev \ # X11
|
||||
libxtst-dev \ # X11
|
||||
wget \
|
||||
|
||||
Update gcc alias
|
||||
.. code-block:: bash
|
||||
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10
|
||||
|
||||
Install CuDA
|
||||
.. code-block:: bash
|
||||
|
||||
wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run
|
||||
|
||||
Install CMake
|
||||
.. code-block:: bash
|
||||
|
||||
wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh
|
||||
mkdir /opt/cmake
|
||||
sh /cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license
|
||||
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
|
||||
cmake --version
|
||||
|
||||
Ubuntu 20.04
|
||||
^^^^^^^^^^^^
|
||||
End of Life: April 2030
|
||||
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt update && sudo apt install \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
g++-10 \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \ # KMS
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \ # Wayland
|
||||
libx11-dev \ # X11
|
||||
libxcb-shm0-dev \ # X11
|
||||
libxcb-xfixes0-dev \ # X11
|
||||
libxcb1-dev \ # X11
|
||||
libxfixes-dev \ # X11
|
||||
libxrandr-dev \ # X11
|
||||
libxtst-dev \ # X11
|
||||
wget \
|
||||
|
||||
Update gcc alias
|
||||
.. code-block:: bash
|
||||
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10
|
||||
|
||||
Install CuDA
|
||||
.. code-block:: bash
|
||||
|
||||
wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O ./cuda.run && chmod a+x ./cuda.run
|
||||
./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm ./cuda.run
|
||||
|
||||
Ubuntu 21.10
|
||||
^^^^^^^^^^^^
|
||||
End of Life: July 2022
|
||||
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt update && sudo apt install \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \ # KMS
|
||||
libdrm-dev \ # KMS
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \ # Wayland
|
||||
libx11-dev \ # X11
|
||||
libxcb-shm0-dev \ # X11
|
||||
libxcb-xfixes0-dev \ # X11
|
||||
libxcb1-dev \ # X11
|
||||
libxfixes-dev \ # X11
|
||||
libxrandr-dev \ # X11
|
||||
libxtst-dev \ # X11
|
||||
nvidia-cuda-dev \ # Cuda, NvFBC
|
||||
nvidia-cuda-toolkit \ # Cuda, NvFBC
|
||||
|
||||
Ubuntu 22.04
|
||||
^^^^^^^^^^^^
|
||||
End of Life: April 2027
|
||||
|
||||
.. Todo:: Create Ubuntu 22.04 Dockerfile and complete this documentation.
|
||||
|
||||
Build
|
||||
-----
|
||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
||||
|
||||
Debian based OSes
|
||||
.. code-block:: bash
|
||||
|
||||
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..
|
||||
|
||||
Red Hat based Oses
|
||||
.. code-block:: bash
|
||||
|
||||
cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..
|
||||
|
||||
Finally
|
||||
.. code-block:: bash
|
||||
|
||||
make -j ${nproc}
|
||||
cpack -G DEB # optionally, create a deb package
|
||||
cpack -G RPM # optionally, create a rpm package
|
||||
|
||||
Dockerfile Builds
|
||||
-----------------
|
||||
You may wish to simply build sunshine from source, without bloating your OS with development files.
|
||||
There are scripts located in the ``./scripts`` directory that will create docker images that have the necessary
|
||||
packages. As a result, removing the development files after you're done is a single command away.
|
||||
These scripts use docker under the hood, as such, they can only be used to compile the Linux version
|
||||
|
||||
.. Todo:: Publish the Dockerfiles to Dockerhub and ghcr.
|
||||
|
||||
Requirements
|
||||
Install `Docker <https://docs.docker.com/engine/install/>`_
|
||||
|
||||
Instructions
|
||||
#. :ref:`Clone <building/build:clone>`. Sunshine.
|
||||
#. Select the desired Dockerfile from the ``./scripts`` directory.
|
||||
|
||||
Available Files:
|
||||
.. code-block:: text
|
||||
|
||||
Dockerfile-debian
|
||||
Dockerfile-fedora_33 # end of life
|
||||
Dockerfile-fedora_35
|
||||
Dockerfile-ubuntu_18_04
|
||||
Dockerfile-ubuntu_20_04
|
||||
Dockerfile-ubuntu_21_04 # end of life
|
||||
Dockerfile-ubuntu_21_10
|
||||
|
||||
#. Execute
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd scripts # move to the scripts directory
|
||||
./build-container.sh -f Dockerfile-<name> # create the container (replace the "<name>")
|
||||
./build-sunshine.sh -p -s .. # compile and build sunshine
|
||||
|
||||
#. Updating
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git pull # pull the latest changes from github
|
||||
./build-sunshine.sh -p -s .. # compile and build sunshine
|
||||
|
||||
#. Optionally, delete the container
|
||||
.. code-block:: bash
|
||||
|
||||
./build-container.sh -c delete
|
||||
|
||||
#. Install the resulting package
|
||||
|
||||
Debian
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt install -f sunshine-build/sunshine.deb
|
||||
|
||||
Red Hat
|
||||
.. code-block:: bash
|
||||
|
||||
sudo dnf install sunshine-build/sunshine.rpm
|
||||
43
docs/source/building/macos.rst
Normal file
43
docs/source/building/macos.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/macos.rst
|
||||
|
||||
macOS
|
||||
=====
|
||||
|
||||
Requirements
|
||||
------------
|
||||
macOS Big Sur and Xcode 12.5+
|
||||
|
||||
Use either `MacPorts <https://www.macports.org>`_ or `Homebrew <https://brew.sh>`_
|
||||
|
||||
MacPorts
|
||||
""""""""
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
sudo port install cmake boost ffmpeg libopus
|
||||
|
||||
Homebrew
|
||||
""""""""
|
||||
Install Requirements
|
||||
.. code-block:: bash
|
||||
|
||||
brew install boost cmake ffmpeg opus
|
||||
# if there are issues with an SSL header that is not found:
|
||||
cd /usr/local/include
|
||||
ln -s ../opt/openssl/include/openssl .
|
||||
|
||||
Build
|
||||
-----
|
||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cmake ..
|
||||
make -j ${nproc}
|
||||
|
||||
cpack -G DragNDrop # optionally, create a macOS dmg package
|
||||
|
||||
If cmake fails complaining to find Boost, try to set the path explicitly.
|
||||
|
||||
``cmake -DBOOST_ROOT=[boost path] ..``, e.g., ``cmake -DBOOST_ROOT=/opt/local/libexec/boost/1.76 ..``
|
||||
|
||||
27
docs/source/building/windows.rst
Normal file
27
docs/source/building/windows.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/building/windows.rst
|
||||
|
||||
Windows
|
||||
=======
|
||||
|
||||
Requirements
|
||||
------------
|
||||
First you need to install `MSYS2 <https://www.msys2.org>`_, then startup "MSYS2 MinGW 64-bit" and install the
|
||||
following packages using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git mingw-w64-x86_64-make cmake make gcc
|
||||
|
||||
Build
|
||||
-----
|
||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cmake -G"Unix Makefiles" ..
|
||||
cmake -G"MinGW Makefiles" .. # alternatively
|
||||
|
||||
mingw32-make
|
||||
|
||||
cpack -G NSIS # optionally, create a windows installer
|
||||
cpack -G ZIP # optionally, create a windows standalone package
|
||||
97
docs/source/conf.py
Normal file
97
docs/source/conf.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# standard imports
|
||||
from datetime import datetime
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__)) # the directory of this file
|
||||
source_dir = os.path.dirname(script_dir) # the source folder directory
|
||||
root_dir = os.path.dirname(source_dir) # the root folder directory
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
project = 'Sunshine'
|
||||
copyright = f'{datetime.now ().year}, {project}'
|
||||
author = 'ReenigneArcher'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
with open(os.path.join(root_dir, 'CMakeLists.txt'), 'r') as f:
|
||||
version = re.search(r"project\(Sunshine VERSION ((\d+)\.(\d+)\.(\d+))", str(f.read())).group(1)
|
||||
"""
|
||||
To use cmake method for obtaining version instead of regex,
|
||||
1. Within CMakeLists.txt add the following line without backticks:
|
||||
``configure_file(docs/source/conf.py.in "${CMAKE_CURRENT_SOURCE_DIR}/docs/source/conf.py" @ONLY)``
|
||||
2. Rename this file to ``conf.py.in``
|
||||
3. Uncomment the next line
|
||||
"""
|
||||
# version = '@PROJECT_VERSION@' # use this for cmake configure_file method
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'm2r2', # enable markdown files
|
||||
'sphinx.ext.autosectionlabel',
|
||||
'sphinx.ext.todo', # enable to-do sections
|
||||
'sphinx.ext.viewcode', # add links to view source code
|
||||
'sphinx_copybutton', # add a copy button to code blocks
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['toc.rst']
|
||||
|
||||
# Extensions to include.
|
||||
source_suffix = ['.rst', '.md']
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
# html_static_path = ['_static']
|
||||
|
||||
html_logo = os.path.join(root_dir, 'sunshine.png')
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
html_theme_options = {
|
||||
# 'analytics_id': 'G-XXXXXXXXXX', # Provided by Google in your dashboard
|
||||
# 'analytics_anonymize_ip': False,
|
||||
'logo_only': False,
|
||||
'display_version': False,
|
||||
'prev_next_buttons_location': 'bottom',
|
||||
'style_external_links': True,
|
||||
'vcs_pageview_mode': 'blob',
|
||||
'style_nav_header_background': '#151515',
|
||||
# Toc options
|
||||
'collapse_navigation': True,
|
||||
'sticky_navigation': True,
|
||||
'navigation_depth': 4,
|
||||
'includehidden': True,
|
||||
'titles_only': False,
|
||||
}
|
||||
|
||||
# extension config options
|
||||
autosectionlabel_prefix_document = True # Make sure the target is unique
|
||||
todo_include_todos = True
|
||||
39
docs/source/contributing/contributing.rst
Normal file
39
docs/source/contributing/contributing.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/contributing.rst
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
.. Tip:: If this is your first time contributing to an open source project, it is a good idea to read
|
||||
MDN's `Basic etiquette for open source projects`_ first. There are a few best practices to adopt that will help
|
||||
ensure that you and the other project contributors feel valued and safe, and stay productive.
|
||||
|
||||
#. Fork the repo on GitHub
|
||||
#. Create a new branch for the feature you are adding or the issue you are fixing
|
||||
|
||||
.. Tip:: Base the new branch off the `nightly` branch. It will make your life easier when you submit the PR!
|
||||
|
||||
#. Make changes, push commits, etc.
|
||||
#. Files should contain an empty line at the end.
|
||||
#. Document your code!
|
||||
#. Test your code!
|
||||
#. When ready create a PR to this repo on the `nightly` branch.
|
||||
|
||||
.. Hint:: If you accidentally make your PR against a different branch, a bot will comment letting you know it's on
|
||||
the wrong branch. Don't worry. You can edit the PR to change the target branch. There is no reason to close the
|
||||
PR!
|
||||
|
||||
.. Note:: Draft PRs are also welcome as you work through issues. The benefit of creating a draft PR is that an
|
||||
automated build can run in a github runner.
|
||||
|
||||
.. Attention:: Do not expect partially complete PRs to be merged. These topics will be considered before merging.
|
||||
|
||||
- Does the code follows the style guidelines of this project?
|
||||
|
||||
.. Tip:: Look at examples of existing code in the project!
|
||||
|
||||
- Is the code well commented?
|
||||
- Were documentation blocks updated for new or modified components?
|
||||
|
||||
.. Note:: Developers and maintainers will attempt to assist with challenging issues.
|
||||
|
||||
.. _Basic etiquette for open source projects: https://developer.mozilla.org/en-US/docs/MDN/Contribute/Open_source_etiquette
|
||||
84
docs/source/contributing/localization.rst
Normal file
84
docs/source/contributing/localization.rst
Normal file
@@ -0,0 +1,84 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/contributing/localization.rst
|
||||
|
||||
Localization
|
||||
============
|
||||
Sunshine is being localized into various languages. The default language is `en` (English) and is highlighted green.
|
||||
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=de&style=for-the-badge&query=%24.progress.0.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=green&label=en&style=for-the-badge&query=%24.progress.1.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=en-GB&style=for-the-badge&query=%24.progress.2.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=en-US&style=for-the-badge&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=es-ES&style=for-the-badge&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=fr&style=for-the-badge&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=it&style=for-the-badge&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
.. image:: https://img.shields.io/badge/dynamic/json?color=blue&label=ru&style=for-the-badge&query=%24.progress.7.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15178612-503956.json
|
||||
|
||||
Graph
|
||||
.. image:: https://badges.awesome-crowdin.com/translation-15178612-503956.png
|
||||
|
||||
CrowdIn
|
||||
-------
|
||||
The translations occur on
|
||||
`CrowdIn <https://crowdin.com/project/sunshinestream>`_. Feel free to contribute to localization there.
|
||||
Only elements of the API are planned to be translated.
|
||||
|
||||
.. Attention:: The rest API has not yet been implemented.
|
||||
|
||||
Translations Basics
|
||||
- The brand names `LizardByte` and `Sunshine` should never be translated.
|
||||
- Other brand names should never be translated.
|
||||
Examples:
|
||||
|
||||
- AMD
|
||||
- Nvidia
|
||||
|
||||
CrowdIn Integration
|
||||
How does it work?
|
||||
|
||||
When a change is made to sunshine source code, a workflow generates new translation templates
|
||||
that get pushed to CrowdIn automatically.
|
||||
|
||||
When translations are updated on CrowdIn, a push gets made to the `l10n_nightly` branch and a PR is made against the
|
||||
`nightly` branch. Once PR is merged, all updated translations are part of the project and will be included in the
|
||||
next release.
|
||||
|
||||
Extraction
|
||||
----------
|
||||
There should be minimal cases where strings need to be extracted from source code; however it may be necessary in some
|
||||
situations. For example if a system tray icon is added it should be localized as it is user interfacing.
|
||||
|
||||
- Wrap the string to be extracted in a function as shown.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <boost/locale.hpp>
|
||||
boost::locale::translate("Hello world!")
|
||||
|
||||
.. Tip:: More examples can be found in the documentation for
|
||||
`boost locale <https://www.boost.org/doc/libs/1_70_0/libs/locale/doc/html/messages_formatting.html>`_.
|
||||
|
||||
.. Warning:: This is for information only. Contributors should never include manually updated template files, or
|
||||
manually compiled language files in Pull Requests.
|
||||
|
||||
Strings are automatically extracted from the code to the `locale/sunshine.po` template file. The generated file is
|
||||
used by CrowdIn to generate language specific template files. The file is generated using the
|
||||
`.github/workflows/localize.yml` workflow and is run on any push event into the `nightly` branch. Jobs are only run if
|
||||
any of the following paths are modified.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- 'src/**'
|
||||
|
||||
When testing locally it may be desirable to manually extract, initialize, update, and compile strings. Python is
|
||||
required for this, along with the python dependencies in the `./scripts/requirements.txt` file. Additionally,
|
||||
`xgettext <https://www.gnu.org/software/gettext/>`_ must be installed.
|
||||
|
||||
Extract, initialize, and update
|
||||
.. code-block:: bash
|
||||
|
||||
python ./scripts/_locale.py --extract --init --update
|
||||
|
||||
Compile
|
||||
.. code-block:: bash
|
||||
|
||||
python ./scripts/_locale.py --compile
|
||||
42
docs/source/contributing/testing.rst
Normal file
42
docs/source/contributing/testing.rst
Normal file
@@ -0,0 +1,42 @@
|
||||
:github_url: https://github.com/RetroArcher/RetroArcher/tree/nightly/docs/source/contributing/testing.rst
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
Clang Format
|
||||
------------
|
||||
Source code is tested against the `.clang-format` file for linting errors. The workflow file responsible for clang
|
||||
format testing is `.github/workflows/clang.yml`.
|
||||
|
||||
Test clang-format locally.
|
||||
.. Todo:: This documentation needs to be improved.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
clang-format ...
|
||||
|
||||
Sphinx
|
||||
------
|
||||
Sunshine uses `Sphinx <https://www.sphinx-doc.org/en/master/>`_ for documentation building. Sphinx is included
|
||||
in the `./scripts/requirements.txt` file. Python is required to build sphinx docs. Installation and setup of python
|
||||
will not be covered here.
|
||||
|
||||
The config file for Sphinx is `docs/source/conf.py`. This is already included in the repo and should not be modified.
|
||||
|
||||
Test with Sphinx
|
||||
.. code-block:: bash
|
||||
|
||||
cd docs
|
||||
make html
|
||||
|
||||
Alternatively
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd docs
|
||||
sphinx-build -b html source build
|
||||
|
||||
Unit Testing
|
||||
------------
|
||||
.. Todo:: Sunshine does not currently have any unit tests. If you would like to help us improve please get in contact
|
||||
with us, or make a PR with suggested changes.
|
||||
5
docs/source/index.rst
Normal file
5
docs/source/index.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/index.rst
|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
.. include:: toc.rst
|
||||
36
docs/source/toc.rst
Normal file
36
docs/source/toc.rst
Normal file
@@ -0,0 +1,36 @@
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: About
|
||||
|
||||
about/overview
|
||||
about/installation
|
||||
about/docker
|
||||
about/third_party_packages
|
||||
about/usage
|
||||
about/advanced_usage
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Troubleshooting
|
||||
|
||||
troubleshooting/general
|
||||
troubleshooting/linux
|
||||
troubleshooting/macos
|
||||
troubleshooting/windows
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Build
|
||||
|
||||
building/build
|
||||
building/linux
|
||||
building/macos
|
||||
building/windows
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contributing
|
||||
|
||||
contributing/contributing
|
||||
contributing/localization
|
||||
contributing/testing
|
||||
13
docs/source/troubleshooting/general.rst
Normal file
13
docs/source/troubleshooting/general.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/general.rst
|
||||
|
||||
General
|
||||
=======
|
||||
If you forgot your credentials to the web UI, try this.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sunshine -creds <new username> <new password>
|
||||
|
||||
Can't access the web UI?
|
||||
|
||||
#. Check firefall rules.
|
||||
9
docs/source/troubleshooting/linux.rst
Normal file
9
docs/source/troubleshooting/linux.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/linux.rst
|
||||
|
||||
Linux
|
||||
=====
|
||||
If screencasting fails with Wayland, you may need to run the following to force screencasting with X11.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo setcap -r $(readlink -f $(which sunshine))
|
||||
29
docs/source/troubleshooting/macos.rst
Normal file
29
docs/source/troubleshooting/macos.rst
Normal file
@@ -0,0 +1,29 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/macos.rst
|
||||
|
||||
macOS
|
||||
=====
|
||||
If you get this error:
|
||||
|
||||
``Dynamic session lookup supported but failed: launchd did not provide a socket path, verify that
|
||||
org.freedesktop.dbus-session.plist is loaded!``
|
||||
|
||||
Try this.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist
|
||||
|
||||
Uninstall:
|
||||
|
||||
- pkg
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo chmod +x /opt/local/etc/sunshine/assets/uninstall_pkg.sh
|
||||
sudo /opt/local/etc/sunshine/assets/uninstall_pkg.sh
|
||||
|
||||
- Portfile
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo port uninstall Sunshine
|
||||
7
docs/source/troubleshooting/windows.rst
Normal file
7
docs/source/troubleshooting/windows.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
:github_url: https://github.com/LizardByte/Sunshine/tree/nightly/docs/source/troubleshooting/windows.rst
|
||||
|
||||
Windows
|
||||
=======
|
||||
No gamepad is detected.
|
||||
|
||||
#. Verify that you've installed `ViGEmBus <https://github.com/ViGEm/ViGEmBus/releases/latest>`_.
|
||||
Submodule moonlight-common-c deleted from cfeb0ffd90
55
packaging/linux/aur/PKGBUILD
Normal file
55
packaging/linux/aur/PKGBUILD
Normal file
@@ -0,0 +1,55 @@
|
||||
# Edit on github: https://github.com/LizardByte/Sunshine/tree/nightly/packaging/linux/aur/PKGBUILD
|
||||
# Reference: https://wiki.archlinux.org/title/PKGBUILD
|
||||
|
||||
pkgname=@SUNSHINE_AUR_PKG@
|
||||
pkgver=@PROJECT_VERSION@@SUNSHINE_SUB_VERSION@
|
||||
pkgrel=1
|
||||
pkgdesc="@PROJECT_DESCRIPTION@"
|
||||
arch=('x86_64' 'i686')
|
||||
url=@PROJECT_HOMEPAGE_URL@
|
||||
license=('GPL3')
|
||||
|
||||
depends=('avahi' 'boost-libs' 'ffmpeg4.4' 'libevdev' 'libpulse' 'libx11' 'libxcb' 'libxfixes' 'libxrandr' 'libxtst' 'openssl' 'opus' 'udev')
|
||||
makedepends=('boost' 'cmake' 'git' 'make')
|
||||
optdepends=('cuda' 'libcap' 'libdrm')
|
||||
|
||||
provides=(@SUNSHINE_AUR_PROVIDES@)
|
||||
conflicts=(@SUNSHINE_AUR_CONFLICTS@)
|
||||
|
||||
source=("$pkgname::git+@GITHUB_CLONE_URL@#commit=@GITHUB_COMMIT@")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
prepare() {
|
||||
cd "$pkgname"
|
||||
git submodule update --recursive --init
|
||||
}
|
||||
|
||||
build() {
|
||||
export CFLAGS="${CFLAGS/-Werror=format-security/}"
|
||||
export CXXFLAGS="${CXXFLAGS/-Werror=format-security/}"
|
||||
|
||||
cmake \
|
||||
-S "$pkgname" \
|
||||
-B build \
|
||||
-Wno-dev \
|
||||
-D SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
|
||||
-D CMAKE_INSTALL_PREFIX="/usr" \
|
||||
-D SUNSHINE_ASSETS_DIR="share/sunshine/assets" \
|
||||
-D SUNSHINE_CONFIG_DIR="share/sunshine/config" \
|
||||
-D LIBAVCODEC_INCLUDE_DIR=/usr/include/ffmpeg4.4 \
|
||||
-D LIBAVCODEC_LIBRARIES=/usr/lib/ffmpeg4.4/libavcodec.so \
|
||||
-D LIBAVDEVICE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \
|
||||
-D LIBAVDEVICE_LIBRARIES=/usr/lib/ffmpeg4.4/libavdevice.so \
|
||||
-D LIBAVFORMAT_INCLUDE_DIR=/usr/include/ffmpeg4.4 \
|
||||
-D LIBAVFORMAT_LIBRARIES=/usr/lib/ffmpeg4.4/libavformat.so \
|
||||
-D LIBAVUTIL_INCLUDE_DIR=/usr/include/ffmpeg4.4 \
|
||||
-D LIBAVUTIL_LIBRARIES=/usr/lib/ffmpeg4.4/libavutil.so \
|
||||
-D LIBSWSCALE_INCLUDE_DIR=/usr/include/ffmpeg4.4 \
|
||||
-D LIBSWSCALE_LIBRARIES=/usr/lib/ffmpeg4.4/libswscale.so
|
||||
|
||||
make -C build
|
||||
}
|
||||
|
||||
package() {
|
||||
make -C build install DESTDIR="$pkgdir"
|
||||
}
|
||||
216
packaging/linux/flatpak/dev.lizardbyte.sunshine.yml
Normal file
216
packaging/linux/flatpak/dev.lizardbyte.sunshine.yml
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
app-id: dev.lizardbyte.sunshine
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: "21.08"
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: sunshine
|
||||
separate-locales: false
|
||||
finish-args:
|
||||
- --device=all
|
||||
- --env=PULSE_PROP_media.category=Manager
|
||||
- --persist=.config/sunshine
|
||||
- --share=ipc
|
||||
- --share=network
|
||||
- --socket=pulseaudio
|
||||
- --socket=wayland
|
||||
- --socket=x11
|
||||
- --system-talk-name=org.freedesktop.Avahi
|
||||
- --talk-name=org.freedesktop.Flatpak
|
||||
|
||||
cleanup:
|
||||
- /include
|
||||
- /lib/cmake
|
||||
- /lib/pkgconfig
|
||||
- /lib/*.la
|
||||
- /lib/*.a
|
||||
- /share
|
||||
|
||||
modules:
|
||||
- name: cuda
|
||||
disabled: false
|
||||
buildsystem: simple
|
||||
only-arches:
|
||||
- x86_64
|
||||
- aarch64
|
||||
cleanup:
|
||||
- '*'
|
||||
build-commands:
|
||||
- chmod u+x ./cuda.run
|
||||
- ./cuda.run --silent --toolkit --toolkitpath=$FLATPAK_DEST/cuda --no-opengl-libs --no-man-page --no-drm --tmpdir=$FLATPAK_BUILDER_BUILDDIR # yamllint disable-line rule:line-length
|
||||
- rm -r $FLATPAK_DEST/cuda/nsight-systems-2021.3.2
|
||||
- rm ./cuda.run
|
||||
sources:
|
||||
- type: file
|
||||
only-arches:
|
||||
- x86_64
|
||||
url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run
|
||||
sha256: bbd87ca0e913f837454a796367473513cddef555082e4d86ed9a38659cc81f0a
|
||||
dest-filename: cuda.run
|
||||
- type: file
|
||||
only-arches:
|
||||
- aarch64
|
||||
url: https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux_sbsa.run # yamllint disable-line rule:line-length
|
||||
sha256: f2c4a52e06329606c8dfb7c5ea3f4cb4c0b28f9d3fdffeeb734fcc98daf580d8
|
||||
dest-filename: cuda.run
|
||||
|
||||
- name: boost
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log
|
||||
- ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" -j $FLATPAK_BUILDER_N_JOBS # yamllint disable-line rule:line-length
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2
|
||||
sha256: 475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39
|
||||
|
||||
- name: ffmpeg
|
||||
config-opts:
|
||||
- --enable-gpl
|
||||
- --disable-static
|
||||
- --enable-shared
|
||||
- --disable-doc
|
||||
- --disable-programs
|
||||
- --disable-decoders
|
||||
- --enable-libfontconfig
|
||||
- --enable-libfreetype
|
||||
- --enable-libopus
|
||||
- --enable-libvorbis
|
||||
- --enable-libvpx
|
||||
- --enable-libx264
|
||||
- --enable-libx265
|
||||
- --enable-nvenc
|
||||
- --enable-encoder=h264_v4l2m2m
|
||||
- --enable-encoder=hevc_v4l2m2m
|
||||
# - --enable-nonfree
|
||||
# - --enable-cuda-nvcc
|
||||
# - --enable-libnpp
|
||||
# - --extra-cflags=-I${FLATPAK_DEST}/cuda/include
|
||||
# - --extra-ldflags=-L${FLATPAK_DEST}/cuda/lib64
|
||||
# - --nvccflags="-gencode arch=compute_52,code=sm_52 -O2"
|
||||
cleanup:
|
||||
- /share/ffmpeg/examples
|
||||
sources:
|
||||
- type: archive
|
||||
url: http://archive.ubuntu.com/ubuntu/pool/universe/f/ffmpeg/ffmpeg_4.4.2.orig.tar.xz
|
||||
sha256: af419a7f88adbc56c758ab19b4c708afbcae15ef09606b82b855291f6a6faa93
|
||||
modules:
|
||||
- name: vmaf
|
||||
buildsystem: meson
|
||||
subdir: libvmaf
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/Netflix/vmaf/archive/refs/tags/v2.3.1.tar.gz
|
||||
sha256: 8d60b1ddab043ada25ff11ced821da6e0c37fd7730dd81c24f1fc12be7293ef2
|
||||
- name: x264
|
||||
config-opts:
|
||||
- --disable-cli
|
||||
- --enable-shared
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://code.videolan.org/videolan/x264/-/archive/stable/x264-stable.tar.bz2
|
||||
sha256: 8fedb184045722d8cc39353099373a5b7350171d0964d01fff8eced21b959b29
|
||||
- name: x265
|
||||
buildsystem: cmake-ninja
|
||||
builddir: true
|
||||
subdir: source
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
- -DENABLE_CLI=OFF
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://bitbucket.org/multicoreware/x265_git/downloads/x265_3.5.tar.gz
|
||||
sha256: e70a3335cacacbba0b3a20ec6fecd6783932288ebc8163ad74bcc9606477cae8
|
||||
- name: ffnvcodec
|
||||
no-autogen: true
|
||||
make-install-args:
|
||||
- PREFIX=${FLATPAK_DEST}
|
||||
cleanup:
|
||||
- '*'
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/FFmpeg/nv-codec-headers/archive/refs/tags/n11.1.5.1.tar.gz
|
||||
sha256: d095fbd56aa93772471a323be0ebe65504a0f43f06c76a30b6d25da77b06ae9c
|
||||
|
||||
- name: avahi
|
||||
cleanup:
|
||||
- /bin
|
||||
- /lib/avahi
|
||||
- /share/applications/*.desktop
|
||||
- /share/avahi
|
||||
config-opts:
|
||||
- --with-distro=none
|
||||
- --disable-gobject
|
||||
- --disable-introspection
|
||||
- --disable-qt3
|
||||
- --disable-qt4
|
||||
- --disable-qt5
|
||||
- --disable-gtk
|
||||
- --disable-core-docs
|
||||
- --disable-manpages
|
||||
- --disable-libdaemon
|
||||
- --disable-python
|
||||
- --disable-pygobject
|
||||
- --disable-mono
|
||||
- --disable-monodoc
|
||||
- --disable-autoipd
|
||||
- --disable-doxygen-doc
|
||||
- --disable-doxygen-dot
|
||||
- --disable-doxygen-xml
|
||||
- --disable-doxygen-html
|
||||
- --disable-manpages
|
||||
- --disable-xmltoman
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://avahi.org/download/avahi-0.8.tar.gz
|
||||
sha256: 060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda
|
||||
modules:
|
||||
- name: libevent
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz # yamllint disable-line rule:line-length
|
||||
sha256: 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb
|
||||
|
||||
- name: libevdev
|
||||
buildsystem: meson
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://www.freedesktop.org/software/libevdev/libevdev-1.12.1.tar.xz
|
||||
sha256: 1dbba41bc516d3ca7abc0da5b862efe3ea8a7018fa6e9b97ce9d39401b22426c
|
||||
modules:
|
||||
- name: libcheck
|
||||
buildsystem: cmake
|
||||
cleanup:
|
||||
- /bin
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://github.com/libcheck/check/archive/refs/tags/0.15.2.tar.gz
|
||||
sha256: 998d355294bb94072f40584272cf4424571c396c631620ce463f6ea97aa67d2e
|
||||
|
||||
- name: sunshine
|
||||
buildsystem: cmake
|
||||
no-make-install: false
|
||||
builddir: true
|
||||
build-options:
|
||||
cxxflags: -I${C_INCLUDE_PATH}/libevdev-1.0
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMAKE_INSTALL_PREFIX=/app
|
||||
- -DCMAKE_CUDA_COMPILER=/app/cuda/bin/nvcc
|
||||
- -DSUNSHINE_ASSETS_DIR=assets
|
||||
- -DSUNSHINE_CONFIG_DIR=config
|
||||
- -DSUNSHINE_EXECUTABLE_PATH=/app/bin/sunshine
|
||||
- -DSUNSHINE_ENABLE_WAYLAND=ON
|
||||
- -DSUNSHINE_ENABLE_X11=ON
|
||||
- -DSUNSHINE_ENABLE_DRM=ON
|
||||
- -DSUNSHINE_ENABLE_CUDA=ON
|
||||
sources:
|
||||
- type: git
|
||||
url: '@GITHUB_CLONE_URL@'
|
||||
branch: '@GITHUB_BRANCH@'
|
||||
commit: '@GITHUB_COMMIT@'
|
||||
12
packaging/linux/sunshine.desktop
Normal file
12
packaging/linux/sunshine.desktop
Normal file
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=@PROJECT_NAME@
|
||||
Exec=sunshine
|
||||
Version=1.0
|
||||
Comment=@PROJECT_DESCRIPTION@
|
||||
Icon=sunshine
|
||||
Categories=Utility;
|
||||
Terminal=true
|
||||
X-AppImage-Name=sunshine
|
||||
X-AppImage-Version=@PROJECT_VERSION@
|
||||
X-AppImage-Arch=x86_64
|
||||
84
packaging/macos/Portfile
Normal file
84
packaging/macos/Portfile
Normal file
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4
|
||||
|
||||
# initial PR into macports: https://github.com/macports/macports-ports/pull/15143
|
||||
|
||||
PortSystem 1.0
|
||||
PortGroup cmake 1.1
|
||||
PortGroup github 1.0
|
||||
PortGroup boost 1.0
|
||||
|
||||
name @PROJECT_NAME@
|
||||
version @PROJECT_VERSION@
|
||||
revision 0
|
||||
categories multimedia emulators games
|
||||
platforms darwin
|
||||
license GPL-3
|
||||
maintainers @LizardByte
|
||||
description @PROJECT_DESCRIPTION@
|
||||
|
||||
# long_description will not be split into multiple lines as it's configured by CMakeLists
|
||||
long_description @PROJECT_LONG_DESCRIPTION@
|
||||
homepage @PROJECT_HOMEPAGE_URL@
|
||||
master_sites https://github.com/lizardbyte/sunshine/releases
|
||||
|
||||
compiler.cxx_standard 2017
|
||||
fetch.type git
|
||||
|
||||
git.url @GITHUB_CLONE_URL@
|
||||
git.branch @GITHUB_COMMIT@
|
||||
|
||||
post-fetch {
|
||||
system -W ${worksrcpath} "${git.cmd} submodule update --init --recursive"
|
||||
}
|
||||
|
||||
depends_lib port:avahi \
|
||||
port:ffmpeg \
|
||||
port:libopus
|
||||
|
||||
boost.version 1.76
|
||||
|
||||
configure.args -DCMAKE_INSTALL_PREFIX=${prefix} \
|
||||
-DSUNSHINE_ASSETS_DIR=etc/sunshine/assets \
|
||||
-DSUNSHINE_CONFIG_DIR=etc/sunshine/config
|
||||
|
||||
startupitem.create yes
|
||||
startupitem.executable "${prefix}/bin/{$name}"
|
||||
startupitem.location LaunchDaemons
|
||||
startupitem.name ${name}
|
||||
startupitem.netchange yes
|
||||
|
||||
platform darwin {
|
||||
if { ${os.major} < 20 } {
|
||||
# See: https://github.com/LizardByte/Sunshine/discussions/117#discussioncomment-2513494
|
||||
notes-append "Port is limited to software encoding, when used with macOS releases prior to Big Sur."
|
||||
}
|
||||
}
|
||||
|
||||
# destroot not required as cmake install directive handles moving files
|
||||
|
||||
# # Rename files in `destroot`
|
||||
# post-destroot {
|
||||
# file rename ${destroot}${prefix}/etc/${name}/config/sunshine.conf ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample
|
||||
# file rename ${destroot}${prefix}/etc/${name}/config/apps.json ${destroot}${prefix}/etc/${name}/config/apps.json.sample
|
||||
# }
|
||||
|
||||
# # Don't overwrite existing preference files
|
||||
# post-activate {
|
||||
# if {![file exists ${prefix}/etc/${name}/config/sunshine.conf]} {
|
||||
# file copy ${destroot}${prefix}/etc/${name}/config/sunshine.conf.sample \
|
||||
# ${prefix}/etc/${name}/config/sunshine.conf
|
||||
# }
|
||||
# if {![file exists ${prefix}/etc/${name}/config/apps.json]} {
|
||||
# file copy ${destroot}${prefix}/etc/${name}/config/apps.json.sample \
|
||||
# ${prefix}/etc/${name}/config/apps.json
|
||||
# }
|
||||
# }
|
||||
|
||||
# disabled not overwriting config files... these are the default config files required by Sunshine
|
||||
# this did not work with pkg created by macports
|
||||
# we should always install the default files and user should start sunshine like "sunshine <path to user config file>"
|
||||
# if the file doesn't exist sunshine will copy the default config to that location
|
||||
notes-append "Run @PROJECT_NAME@ by executing 'sunshine <path to user config>', e.g. 'sunshine ~/sunshine.conf' "
|
||||
notes-append "The config file will be created if it doesn't exist."
|
||||
notes-append "It is recommended to set a location for the apps file in the config."
|
||||
notes-append "See our documentation at 'https://docs.lizardbyte.dev/projects/sunshine/en/v@PROJECT_VERSION@/' for further info."
|
||||
Submodule pre-compiled deleted from 95c5077b66
40
scripts/Dockerfile-debian
Normal file
40
scripts/Dockerfile-debian
Normal file
@@ -0,0 +1,40 @@
|
||||
FROM debian:bullseye AS sunshine-debian
|
||||
|
||||
# Debian Bullseye end of life is TBD
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN echo deb http://deb.debian.org/debian/ bullseye main contrib non-free | tee /etc/apt/sources.list.d/non-free.list
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nvidia-cuda-dev \
|
||||
nvidia-cuda-toolkit \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-deb"]
|
||||
32
scripts/Dockerfile-fedora_33
Normal file
32
scripts/Dockerfile-fedora_33
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM fedora:33 AS sunshine-fedora_33
|
||||
|
||||
# Fedora 33 end of life is November 2021
|
||||
# This file remains for reference only
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN dnf -y update && \
|
||||
dnf -y group install "Development Tools" && \
|
||||
dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \
|
||||
dnf -y install \
|
||||
boost-devel \
|
||||
boost-static.x86_64 \
|
||||
cmake \
|
||||
ffmpeg-devel \
|
||||
libevdev-devel \
|
||||
libxcb-devel \
|
||||
libX11-devel \
|
||||
libXfixes-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
pulseaudio-libs-devel \
|
||||
libcap-devel \
|
||||
libdrm-devel \
|
||||
rpm-build \
|
||||
&& dnf clean all \
|
||||
&& rm -rf /var/cache/yum
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-rpm"]
|
||||
36
scripts/Dockerfile-fedora_35
Normal file
36
scripts/Dockerfile-fedora_35
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM fedora:35 AS sunshine-fedora_35
|
||||
|
||||
# Fedora 35 end of life is TBD
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN dnf -y update && \
|
||||
dnf -y group install "Development Tools" && \
|
||||
dnf -y install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm && \
|
||||
dnf -y install \
|
||||
boost-devel \
|
||||
boost-static.x86_64 \
|
||||
cmake \
|
||||
ffmpeg-devel \
|
||||
gcc-c++ \
|
||||
libevdev-devel \
|
||||
libX11-devel \
|
||||
libxcb-devel \
|
||||
libXcursor-devel \
|
||||
libXfixes-devel \
|
||||
libXinerama-devel \
|
||||
libXi-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
mesa-libGL-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
pulseaudio-libs-devel \
|
||||
libcap-devel \
|
||||
libdrm-devel \
|
||||
rpm-build \
|
||||
&& dnf clean all \
|
||||
&& rm -rf /var/cache/yum
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-rpm"]
|
||||
63
scripts/Dockerfile-ubuntu_18_04
Normal file
63
scripts/Dockerfile-ubuntu_18_04
Normal file
@@ -0,0 +1,63 @@
|
||||
FROM ubuntu:18.04 AS sunshine-ubuntu_18_04
|
||||
|
||||
# Ubuntu 18.04 end of life is April 2028
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
software-properties-common \
|
||||
&& add-apt-repository ppa:savoury1/graphics && \
|
||||
add-apt-repository ppa:savoury1/multimedia && \
|
||||
add-apt-repository ppa:savoury1/ffmpeg4 && \
|
||||
add-apt-repository ppa:savoury1/boost-defaults-1.71 && \
|
||||
add-apt-repository ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update -y && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc-10 \
|
||||
git \
|
||||
g++-10 \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem1.71-dev \
|
||||
libboost-log1.71-dev \
|
||||
libboost-regex1.71-dev \
|
||||
libboost-thread1.71-dev \
|
||||
libcap-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Update gcc alias
|
||||
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10
|
||||
|
||||
# Install CuDA
|
||||
RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run
|
||||
RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run
|
||||
|
||||
# Install cmake
|
||||
ADD https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh /cmake-3.22.2-linux-x86_64.sh
|
||||
RUN mkdir /opt/cmake
|
||||
RUN sh /cmake-3.22.2-linux-x86_64.sh --prefix=/opt/cmake --skip-license
|
||||
RUN ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
|
||||
RUN cmake --version
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-deb"]
|
||||
46
scripts/Dockerfile-ubuntu_20_04
Normal file
46
scripts/Dockerfile-ubuntu_20_04
Normal file
@@ -0,0 +1,46 @@
|
||||
FROM ubuntu:20.04 AS sunshine-ubuntu_20_04
|
||||
|
||||
# Ubuntu 20.04 end of life is April 2030
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
g++-10 \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Update gcc alias
|
||||
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10
|
||||
|
||||
# Install CuDA
|
||||
RUN wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda_11.4.2_470.57.02_linux.run --progress=bar:force:noscroll -q --show-progress -O /root/cuda.run && chmod a+x /root/cuda.run
|
||||
RUN /root/cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm && rm /root/cuda.run
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-deb"]
|
||||
40
scripts/Dockerfile-ubuntu_21_04
Normal file
40
scripts/Dockerfile-ubuntu_21_04
Normal file
@@ -0,0 +1,40 @@
|
||||
FROM ubuntu:21.04 AS sunshine-ubuntu_21_04
|
||||
|
||||
# Ubuntu 21.04 end of life is January 2022
|
||||
# This file remains for reference only
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
libboost-thread-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libcap-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nvidia-cuda-dev \
|
||||
nvidia-cuda-toolkit \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-deb"]
|
||||
39
scripts/Dockerfile-ubuntu_21_10
Normal file
39
scripts/Dockerfile-ubuntu_21_10
Normal file
@@ -0,0 +1,39 @@
|
||||
FROM ubuntu:21.10 AS sunshine-ubuntu_21_10
|
||||
|
||||
# Ubuntu 21.10 end of life is July 2022
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TZ="Europe/London"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-log-dev \
|
||||
libboost-thread-dev \
|
||||
libcap-dev \
|
||||
libdrm-dev \
|
||||
libevdev-dev \
|
||||
libpulse-dev \
|
||||
libopus-dev \
|
||||
libssl-dev \
|
||||
libwayland-dev \
|
||||
libx11-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxcb1-dev \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nvidia-cuda-dev \
|
||||
nvidia-cuda-toolkit \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Entrypoint
|
||||
COPY build-private.sh /root/build.sh
|
||||
ENTRYPOINT ["/root/build.sh", "-deb"]
|
||||
170
scripts/_locale.py
Normal file
170
scripts/_locale.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
..
|
||||
_locale.py
|
||||
|
||||
Functions related to building, initializing, updating, and compiling localization translations.
|
||||
|
||||
Borrowed from RetroArcher.
|
||||
"""
|
||||
# standard imports
|
||||
import argparse
|
||||
import datetime
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
project_name = 'Sunshine'
|
||||
project_owner = 'LizardByte'
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
root_dir = os.path.dirname(script_dir)
|
||||
locale_dir = os.path.join(root_dir, 'locale')
|
||||
project_dir = os.path.join(root_dir, 'src')
|
||||
|
||||
year = datetime.datetime.now().year
|
||||
|
||||
# retroarcher target locales
|
||||
target_locales = [
|
||||
'de', # Deutsch
|
||||
'en', # English
|
||||
'en_GB', # English (United Kingdom)
|
||||
'en_US', # English (United States)
|
||||
'es', # español
|
||||
'fr', # français
|
||||
'it', # italiano
|
||||
'ru', # русский
|
||||
]
|
||||
|
||||
|
||||
def x_extract():
|
||||
"""Executes `xgettext extraction` in subprocess."""
|
||||
|
||||
pot_filepath = os.path.join(locale_dir, f'{project_name.lower()}.po')
|
||||
|
||||
commands = [
|
||||
'xgettext',
|
||||
'--keyword=translate:1,1t',
|
||||
'--keyword=translate:1c,2,2t',
|
||||
'--keyword=translate:1,2,3t',
|
||||
'--keyword=translate:1c,2,3,4t',
|
||||
'--keyword=gettext:1',
|
||||
'--keyword=pgettext:1c,2',
|
||||
'--keyword=ngettext:1,2',
|
||||
'--keyword=npgettext:1c,2,3',
|
||||
f'--default-domain={project_name.lower()}',
|
||||
f'--output={pot_filepath}',
|
||||
'--language=C++',
|
||||
'--boost',
|
||||
'--from-code=utf-8',
|
||||
'-F',
|
||||
f'--msgid-bugs-address=github.com/{project_owner.lower()}/{project_name.lower()}',
|
||||
f'--copyright-holder={project_owner}',
|
||||
f'--package-name={project_name}',
|
||||
'--package-version=v0'
|
||||
]
|
||||
|
||||
extensions = ['cpp', 'h', 'm', 'mm']
|
||||
|
||||
# find input files
|
||||
for root, dirs, files in os.walk(project_dir, topdown=True):
|
||||
for name in files:
|
||||
filename = os.path.join(root, name)
|
||||
extension = filename.rsplit('.', 1)[-1]
|
||||
if extension in extensions: # append input files
|
||||
commands.append(filename)
|
||||
|
||||
print(commands)
|
||||
subprocess.check_output(args=commands, cwd=root_dir)
|
||||
|
||||
try:
|
||||
# fix header
|
||||
body = ""
|
||||
with open(file=pot_filepath, mode='r') as file:
|
||||
for line in file.readlines():
|
||||
if line != '"Language: \\n"\n': # do not include this line
|
||||
if line == '# SOME DESCRIPTIVE TITLE.\n':
|
||||
body += f'# Translations template for {project_name}.\n'
|
||||
elif line.startswith('#') and 'YEAR' in line:
|
||||
body += line.replace('YEAR', str(year))
|
||||
elif line.startswith('#') and 'PACKAGE' in line:
|
||||
body += line.replace('PACKAGE', project_name)
|
||||
else:
|
||||
body += line
|
||||
|
||||
# rewrite pot file with updated header
|
||||
with open(file=pot_filepath, mode='w+') as file:
|
||||
file.write(body)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def babel_init(locale_code: str):
|
||||
"""Executes `pybabel init` in subprocess.
|
||||
|
||||
:param locale_code: str - locale code
|
||||
"""
|
||||
commands = [
|
||||
'pybabel',
|
||||
'init',
|
||||
'-i', os.path.join(locale_dir, f'{project_name.lower()}.po'),
|
||||
'-d', locale_dir,
|
||||
'-D', project_name.lower(),
|
||||
'-l', locale_code
|
||||
]
|
||||
|
||||
print(commands)
|
||||
subprocess.check_output(args=commands, cwd=root_dir)
|
||||
|
||||
|
||||
def babel_update():
|
||||
"""Executes `pybabel update` in subprocess."""
|
||||
commands = [
|
||||
'pybabel',
|
||||
'update',
|
||||
'-i', os.path.join(locale_dir, f'{project_name.lower()}.po'),
|
||||
'-d', locale_dir,
|
||||
'-D', project_name.lower(),
|
||||
'--update-header-comment'
|
||||
]
|
||||
|
||||
print(commands)
|
||||
subprocess.check_output(args=commands, cwd=root_dir)
|
||||
|
||||
|
||||
def babel_compile():
|
||||
"""Executes `pybabel compile` in subprocess."""
|
||||
commands = [
|
||||
'pybabel',
|
||||
'compile',
|
||||
'-d', locale_dir,
|
||||
'-D', project_name.lower()
|
||||
]
|
||||
|
||||
print(commands)
|
||||
subprocess.check_output(args=commands, cwd=root_dir)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Script helps update locale translations. Translations must be done manually.')
|
||||
|
||||
parser.add_argument('--extract', action='store_true', help='Extract messages from c++ files.')
|
||||
parser.add_argument('--init', action='store_true', help='Initialize any new locales specified in target locales.')
|
||||
parser.add_argument('--update', action='store_true', help='Update existing locales.')
|
||||
parser.add_argument('--compile', action='store_true', help='Compile translated locales.')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.extract:
|
||||
x_extract()
|
||||
|
||||
if args.init:
|
||||
for locale_id in target_locales:
|
||||
if not os.path.isdir(os.path.join(locale_dir, locale_id)):
|
||||
babel_init(locale_code=locale_id)
|
||||
|
||||
if args.update:
|
||||
babel_update()
|
||||
|
||||
if args.compile:
|
||||
babel_compile()
|
||||
179
scripts/build-container.sh
Executable file
179
scripts/build-container.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/bin/bash -e
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo " -c: command --> default [build]"
|
||||
echo " | delete --> Delete the container, Dockerfile isn't mandatory"
|
||||
echo " | build --> Build the container, Dockerfile is mandatory"
|
||||
echo " | compile --> Builds the container, then compiles it. Dockerfile is mandatory"
|
||||
echo ""
|
||||
echo " -s: path: The path to the source for compilation"
|
||||
echo " -n: name: Docker container name --> default [sunshine]"
|
||||
echo " --> all: Build/Compile/Delete all available docker containers"
|
||||
echo " -f: Dockerfile: The name of the docker file"
|
||||
}
|
||||
|
||||
# Attempt to turn relative paths into absolute paths
|
||||
absolute_path() {
|
||||
RELATIVE_PATH=$1
|
||||
if which realpath >/dev/null 2>/dev/null
|
||||
then
|
||||
RELATIVE_PATH=$(realpath $RELATIVE_PATH)
|
||||
else
|
||||
echo "Warning: realpath is not installed on your system, ensure [$1] is absolute"
|
||||
fi
|
||||
|
||||
RETURN=$RELATIVE_PATH
|
||||
}
|
||||
|
||||
CONTAINER_NAME=sunshine
|
||||
COMMAND=BUILD
|
||||
|
||||
build_container() {
|
||||
CONTAINER_NAME=$1
|
||||
DOCKER_FILE=$2
|
||||
|
||||
if [ ! -f "$DOCKER_FILE" ]
|
||||
then
|
||||
echo "Error: $DOCKER_FILE doesn't exist"
|
||||
exit 7
|
||||
fi
|
||||
|
||||
echo "docker build . -t $CONTAINER_NAME -f $DOCKER_FILE"
|
||||
docker build . -t "$CONTAINER_NAME" -f "$DOCKER_FILE"
|
||||
}
|
||||
|
||||
delete() {
|
||||
CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$CONTAINER_NAME_UPPER" = "ALL" ]
|
||||
then
|
||||
shopt -s nullglob
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
|
||||
if docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null
|
||||
then
|
||||
echo "docker rmi $CURRENT_CONTAINER"
|
||||
docker rmi "$CURRENT_CONTAINER"
|
||||
fi
|
||||
done
|
||||
shopt -u nullglob #revert nullglob back to it's normal default state
|
||||
else
|
||||
if docker inspect "$CONTAINER_NAME" > /dev/null 2> /dev/null
|
||||
then
|
||||
echo "docker rmi $CONTAINER_NAME"
|
||||
docker rmi $CONTAINER_NAME
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
build() {
|
||||
CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$CONTAINER_NAME_UPPER" = "ALL" ]
|
||||
then
|
||||
shopt -s nullglob
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
build_container "$CURRENT_CONTAINER" "$file"
|
||||
done
|
||||
shopt -u nullglob #revert nullglob back to it's normal default state
|
||||
else
|
||||
if [[ -z "$DOCKER_FILE" ]]
|
||||
then
|
||||
echo "Error: if container name isn't equal to 'all', you need to specify the Dockerfile"
|
||||
exit 6
|
||||
fi
|
||||
|
||||
build_container "$CONTAINER_NAME" "$DOCKER_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
abort() {
|
||||
echo "$1"
|
||||
exit 10
|
||||
}
|
||||
|
||||
compile() {
|
||||
CONTAINER_NAME_UPPER=$(echo "$CONTAINER_NAME" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$CONTAINER_NAME_UPPER" = "ALL" ]
|
||||
then
|
||||
shopt -s nullglob
|
||||
|
||||
# If any docker container doesn't exist, we cannot compile all of them
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
|
||||
# If container doesn't exist --> abort.
|
||||
docker inspect "$CURRENT_CONTAINER" > /dev/null 2> /dev/null || abort "Error: container image [$CURRENT_CONTAINER] doesn't exist"
|
||||
done
|
||||
|
||||
for file in $(find . -maxdepth 1 -iname "Dockerfile-*" -type f)
|
||||
do
|
||||
CURRENT_CONTAINER="sunshine-$(echo $file | cut -c 14-)"
|
||||
|
||||
echo "$PWD/build-sunshine.sh -p -n $CURRENT_CONTAINER $SUNSHINE_SOURCES"
|
||||
"$PWD/build-sunshine.sh" -p -n "$CURRENT_CONTAINER" $SUNSHINE_SOURCES
|
||||
done
|
||||
shopt -u nullglob #revert nullglob back to it's normal default state
|
||||
else
|
||||
# If container exists
|
||||
if docker inspect "$CONTAINER_NAME" > /dev/null 2> /dev/null
|
||||
then
|
||||
echo "$PWD/build-sunshine.sh -p -n $CONTAINER_NAME $SUNSHINE_SOURCES"
|
||||
"$PWD/build-sunshine.sh" -p -n "$CONTAINER_NAME" $SUNSHINE_SOURCES
|
||||
else
|
||||
echo "Error: container image [$CONTAINER_NAME] doesn't exist"
|
||||
exit 9
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while getopts ":c:hn:f:s:" arg; do
|
||||
case ${arg} in
|
||||
s)
|
||||
SUNSHINE_SOURCES="-s $OPTARG"
|
||||
;;
|
||||
c)
|
||||
COMMAND=$(echo $OPTARG | tr '[:lower:]' '[:upper:]')
|
||||
;;
|
||||
n)
|
||||
echo "Container name: $OPTARG"
|
||||
CONTAINER_NAME="$OPTARG"
|
||||
;;
|
||||
f)
|
||||
echo "Using Dockerfile [$OPTARG]"
|
||||
DOCKER_FILE="$OPTARG"
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "$0 set to $(echo $COMMAND | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [ "$COMMAND" = "BUILD" ]
|
||||
then
|
||||
echo "Start building..."
|
||||
delete
|
||||
build
|
||||
echo "Done."
|
||||
elif [ "$COMMAND" = "COMPILE" ]
|
||||
then
|
||||
echo "Start compiling..."
|
||||
compile
|
||||
echo "Done."
|
||||
elif [ "$COMMAND" = "DELETE" ]
|
||||
then
|
||||
echo "Start deleting..."
|
||||
delete
|
||||
echo "Done."
|
||||
else
|
||||
echo "Unknown command [$(echo $COMMAND | tr '[:upper:]' '[:lower:]')]"
|
||||
exit 4
|
||||
fi
|
||||
48
scripts/build-private.sh
Executable file
48
scripts/build-private.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash -e
|
||||
set -e
|
||||
|
||||
CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}"
|
||||
SUNSHINE_EXECUTABLE_PATH="${SUNSHINE_EXECUTABLE_PATH:-/usr/bin/sunshine}"
|
||||
SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR:-/etc/sunshine}"
|
||||
|
||||
|
||||
SUNSHINE_ROOT="${SUNSHINE_ROOT:-/root/sunshine}"
|
||||
SUNSHINE_TAG="${SUNSHINE_TAG:-master}"
|
||||
SUNSHINE_GIT_URL="${SUNSHINE_GIT_URL:-https://github.com/lizardbyte/sunshine.git}"
|
||||
|
||||
|
||||
SUNSHINE_ENABLE_WAYLAND=${SUNSHINE_ENABLE_WAYLAND:-ON}
|
||||
SUNSHINE_ENABLE_X11=${SUNSHINE_ENABLE_X11:-ON}
|
||||
SUNSHINE_ENABLE_DRM=${SUNSHINE_ENABLE_DRM:-ON}
|
||||
SUNSHINE_ENABLE_CUDA=${SUNSHINE_ENABLE_CUDA:-ON}
|
||||
|
||||
# For debugging, it would be usefull to have the sources on the host.
|
||||
if [[ ! -d "$SUNSHINE_ROOT" ]]
|
||||
then
|
||||
git clone --depth 1 --branch "$SUNSHINE_TAG" "$SUNSHINE_GIT_URL" --recurse-submodules "$SUNSHINE_ROOT"
|
||||
fi
|
||||
|
||||
if [[ ! -d /root/sunshine-build ]]
|
||||
then
|
||||
mkdir -p /root/sunshine-build
|
||||
fi
|
||||
cd /root/sunshine-build
|
||||
|
||||
cmake "-DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE" "-DSUNSHINE_EXECUTABLE_PATH=$SUNSHINE_EXECUTABLE_PATH" "-DSUNSHINE_ASSETS_DIR=$SUNSHINE_ASSETS_DIR" "-DSUNSHINE_ENABLE_WAYLAND=$SUNSHINE_ENABLE_WAYLAND" "-DSUNSHINE_ENABLE_X11=$SUNSHINE_ENABLE_X11" "-DSUNSHINE_ENABLE_DRM=$SUNSHINE_ENABLE_DRM" "-DSUNSHINE_ENABLE_CUDA=$SUNSHINE_ENABLE_CUDA" "$SUNSHINE_ROOT"
|
||||
|
||||
make -j ${nproc}
|
||||
|
||||
# Get preferred package format
|
||||
if [ "$1" == "-rpm" ]
|
||||
then
|
||||
echo "Packaging in .rpm format."
|
||||
./gen-rpm -d
|
||||
elif [ "$1" == "-deb" ]
|
||||
then
|
||||
echo "Packaging in .deb format."
|
||||
./gen-deb
|
||||
else
|
||||
echo "Preferred packaging not specified."
|
||||
echo "Use -deb or -rpm to specify preferred package format."
|
||||
exit 1
|
||||
fi
|
||||
132
scripts/build-sunshine.sh
Executable file
132
scripts/build-sunshine.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/bin/bash -e
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0"
|
||||
echo " -d: Generate a debug build"
|
||||
echo " -p: Generate a linux package"
|
||||
echo " -e: Extension of package... i.e. 'deb', 'rpm' --> default [deb]"
|
||||
echo " -u: The input device is not a TTY"
|
||||
echo " -n name: Docker container name --> default [sunshine]"
|
||||
echo " -s path/to/sources/sunshine: Use local sources instead of a git repository"
|
||||
echo " -c path/to/cmake/binary/dir: Store cmake output on host OS"
|
||||
}
|
||||
|
||||
# Attempt to turn relative paths into absolute paths
|
||||
absolute_path() {
|
||||
RELATIVE_PATH=$1
|
||||
if which realpath >/dev/null 2>/dev/null
|
||||
then
|
||||
RELATIVE_PATH=$(realpath $RELATIVE_PATH)
|
||||
else
|
||||
echo "Warning: realpath is not installed on your system, ensure [$1] is absolute"
|
||||
fi
|
||||
|
||||
RETURN=$RELATIVE_PATH
|
||||
}
|
||||
|
||||
CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Release"
|
||||
SUNSHINE_PACKAGE_BUILD=OFF
|
||||
SUNSHINE_PACKAGE_EXTENSION=deb
|
||||
SUNSHINE_GIT_URL=https://github.com/lizardbyte/sunshine.git
|
||||
CONTAINER_NAME=sunshine
|
||||
|
||||
# Docker will fail if ctrl+c is passed through and the input is not a tty
|
||||
DOCKER_INTERACTIVE=-ti
|
||||
|
||||
while getopts ":dpuhc:e:s:n:" arg; do
|
||||
case ${arg} in
|
||||
u)
|
||||
echo "Input device is not a TTY"
|
||||
USERNAME="$USER"
|
||||
unset DOCKER_INTERACTIVE
|
||||
;;
|
||||
d)
|
||||
echo "Creating debug build"
|
||||
CMAKE_BUILD_TYPE="-e CMAKE_BUILD_TYPE=Debug"
|
||||
;;
|
||||
p)
|
||||
echo "Creating package build"
|
||||
SUNSHINE_PACKAGE_BUILD=ON
|
||||
SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=/etc/sunshine"
|
||||
SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine"
|
||||
;;
|
||||
e)
|
||||
echo "Defining package extension: $OPTARG"
|
||||
if [ "$OPTARG" == "deb" ]
|
||||
then
|
||||
SUNSHINE_PACKAGE_EXTENSION=$OPTARG
|
||||
echo "Package extension: deb"
|
||||
elif [ "$OPTARG" == "rpm" ]
|
||||
then
|
||||
SUNSHINE_PACKAGE_EXTENSION=$OPTARG
|
||||
echo "Package extension: rpm"
|
||||
else
|
||||
echo "Package extension not supported: $OPTARG"
|
||||
echo "Falling back to default package extension: $SUNSHINE_PACKAGE_EXTENSION"
|
||||
fi
|
||||
;;
|
||||
s)
|
||||
absolute_path "$OPTARG"
|
||||
OPTARG="$RETURN"
|
||||
echo "Using sources from $OPTARG"
|
||||
SUNSHINE_ROOT="-v $OPTARG:/root/sunshine"
|
||||
;;
|
||||
c)
|
||||
[ "$USERNAME" == "" ] && USERNAME=$(logname)
|
||||
|
||||
absolute_path "$OPTARG"
|
||||
OPTARG="$RETURN"
|
||||
|
||||
echo "Using $OPTARG as cmake binary dir"
|
||||
if [[ ! -d $OPTARG ]]
|
||||
then
|
||||
echo "cmake binary dir doesn't exist, a new one will be created."
|
||||
mkdir -p "$OPTARG"
|
||||
[ "$USERNAME" == "$USER"] || chown $USERNAME:$USERNAME "$OPTARG"
|
||||
fi
|
||||
|
||||
CMAKE_ROOT="-v $OPTARG:/root/sunshine-build"
|
||||
;;
|
||||
n)
|
||||
echo "Container name: $OPTARG"
|
||||
CONTAINER_NAME=$OPTARG
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$USERNAME" = "" ] && USERNAME=$(logname)
|
||||
|
||||
BUILD_DIR="$PWD/$CONTAINER_NAME-build"
|
||||
[ "$SUNSHINE_ASSETS_DIR" = "" ] && SUNSHINE_ASSETS_DIR="-e SUNSHINE_ASSETS_DIR=$BUILD_DIR/assets"
|
||||
[ "$SUNSHINE_EXECUTABLE_PATH" = "" ] && SUNSHINE_EXECUTABLE_PATH="-e SUNSHINE_EXECUTABLE_PATH=$BUILD_DIR/sunshine"
|
||||
|
||||
echo "docker run $DOCKER_INTERACTIVE --privileged $SUNSHINE_ROOT $CMAKE_ROOT $SUNSHINE_ASSETS_DIR $SUNSHINE_EXECUTABLE_PATH $CMAKE_BUILD_TYPE --name $CONTAINER_NAME $CONTAINER_NAME"
|
||||
docker run $DOCKER_INTERACTIVE --privileged $SUNSHINE_ROOT $CMAKE_ROOT $SUNSHINE_ASSETS_DIR $SUNSHINE_EXECUTABLE_PATH $CMAKE_BUILD_TYPE --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
exit_code=$?
|
||||
|
||||
if [ $exit_code -eq 0 ]
|
||||
then
|
||||
mkdir -p $BUILD_DIR
|
||||
case $SUNSHINE_PACKAGE_BUILD in
|
||||
ON)
|
||||
echo "Downloading package to: $BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION"
|
||||
docker cp $CONTAINER_NAME:/root/sunshine-build/package-$SUNSHINE_PACKAGE_EXTENSION/sunshine.$SUNSHINE_PACKAGE_EXTENSION "$BUILD_DIR/$CONTAINER_NAME.$SUNSHINE_PACKAGE_EXTENSION"
|
||||
;;
|
||||
*)
|
||||
echo "Downloading binary and assets to: $BUILD_DIR"
|
||||
docker cp $CONTAINER_NAME:/root/sunshine/assets "$BUILD_DIR"
|
||||
docker cp $CONTAINER_NAME:/root/sunshine-build/sunshine "$BUILD_DIR"
|
||||
;;
|
||||
esac
|
||||
echo "chown --recursive $USERNAME:$USERNAME $BUILD_DIR"
|
||||
chown --recursive $USERNAME:$USERNAME "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
echo "Removing docker container $CONTAINER_NAME"
|
||||
docker rm $CONTAINER_NAME
|
||||
5
scripts/requirements.txt
Normal file
5
scripts/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Babel==2.10.3
|
||||
m2r2==0.3.2
|
||||
Sphinx==5.1.1
|
||||
sphinx-copybutton==0.5.0
|
||||
sphinx-rtd-theme==1.0.0
|
||||
270
src/audio.cpp
Normal file
270
src/audio.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
#include <thread>
|
||||
|
||||
#include <opus/opus_multistream.h>
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "config.h"
|
||||
#include "main.h"
|
||||
#include "thread_safe.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace audio {
|
||||
using namespace std::literals;
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
|
||||
|
||||
struct audio_ctx_t {
|
||||
// We want to change the sink for the first stream only
|
||||
std::unique_ptr<std::atomic_bool> sink_flag;
|
||||
|
||||
std::unique_ptr<platf::audio_control_t> control;
|
||||
|
||||
bool restore_sink;
|
||||
platf::sink_t sink;
|
||||
};
|
||||
|
||||
static int start_audio_control(audio_ctx_t &ctx);
|
||||
static void stop_audio_control(audio_ctx_t &);
|
||||
|
||||
int map_stream(int channels, bool quality);
|
||||
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
|
||||
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
|
||||
{
|
||||
SAMPLE_RATE,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
platf::speaker::map_stereo,
|
||||
},
|
||||
{
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
4,
|
||||
2,
|
||||
platf::speaker::map_surround51,
|
||||
},
|
||||
{
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
6,
|
||||
0,
|
||||
platf::speaker::map_surround51,
|
||||
},
|
||||
{
|
||||
SAMPLE_RATE,
|
||||
8,
|
||||
5,
|
||||
3,
|
||||
platf::speaker::map_surround71,
|
||||
},
|
||||
{
|
||||
SAMPLE_RATE,
|
||||
8,
|
||||
8,
|
||||
0,
|
||||
platf::speaker::map_surround71,
|
||||
},
|
||||
};
|
||||
|
||||
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
|
||||
|
||||
void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
|
||||
auto packets = mail::man->queue<packet_t>(mail::audio_packets);
|
||||
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
|
||||
|
||||
opus_t opus { opus_multistream_encoder_create(
|
||||
stream->sampleRate,
|
||||
stream->channelCount,
|
||||
stream->streams,
|
||||
stream->coupledStreams,
|
||||
stream->mapping,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
nullptr) };
|
||||
|
||||
// For some reason, audio is crackling when the encoder is set to constant bitstream.
|
||||
// We simulate a constant bitstream with OPUS_SET_BITRATE(OPUS_BITRATE_MAX) -->
|
||||
// which tries to occupy as much space as possible in the packet
|
||||
opus_multistream_encoder_ctl(opus.get(), OPUS_SET_BITRATE(OPUS_BITRATE_MAX));
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
while(auto sample = samples->pop()) {
|
||||
buffer_t packet { 1400 }; // 1KB
|
||||
|
||||
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
|
||||
if(bytes < 0) {
|
||||
BOOST_LOG(error) << "Couldn't encode audio: "sv << opus_strerror(bytes);
|
||||
packets->stop();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Even with OPUS_SET_BITRATE(OPUS_BITRATE_MAX), silent packets are smaller than the rest
|
||||
// Drop silent packets to ensure Moonlight won't complain
|
||||
// A packet size of 128 seems a reasonable enough threshold
|
||||
if(bytes < 128) {
|
||||
BOOST_LOG(verbose) << "Dropped silent packet"sv;
|
||||
continue;
|
||||
}
|
||||
|
||||
packet.fake_resize(bytes);
|
||||
packets->raise(channel_data, std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void capture(safe::mail_t mail, config_t config, void *channel_data) {
|
||||
auto shutdown_event = mail->event<bool>(mail::shutdown);
|
||||
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
|
||||
|
||||
auto ref = control_shared.ref();
|
||||
if(!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &control = ref->control;
|
||||
if(!control) {
|
||||
shutdown_event->view();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Order of priorty:
|
||||
// 1. Config
|
||||
// 2. Virtual if available
|
||||
// 3. Host
|
||||
std::string *sink = &ref->sink.host;
|
||||
if(!config::audio.sink.empty()) {
|
||||
sink = &config::audio.sink;
|
||||
}
|
||||
else if(ref->sink.null) {
|
||||
auto &null = *ref->sink.null;
|
||||
switch(stream->channelCount) {
|
||||
case 2:
|
||||
sink = &null.stereo;
|
||||
break;
|
||||
case 6:
|
||||
sink = &null.surround51;
|
||||
break;
|
||||
case 8:
|
||||
sink = &null.surround71;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only the first to start a session may change the default sink
|
||||
if(!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
|
||||
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
|
||||
|
||||
// If the client requests audio on the host, don't change the default sink
|
||||
if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto samples = std::make_shared<sample_queue_t::element_type>(30);
|
||||
std::thread thread { encodeThread, samples, config, channel_data };
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
samples->stop();
|
||||
thread.join();
|
||||
|
||||
shutdown_event->view();
|
||||
});
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
int samples_per_frame = frame_size * stream->channelCount;
|
||||
|
||||
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
|
||||
if(!mic) {
|
||||
BOOST_LOG(error) << "Couldn't create audio input"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
while(!shutdown_event->peek()) {
|
||||
std::vector<std::int16_t> sample_buffer;
|
||||
sample_buffer.resize(samples_per_frame);
|
||||
|
||||
auto status = mic->sample(sample_buffer);
|
||||
switch(status) {
|
||||
case platf::capture_e::ok:
|
||||
break;
|
||||
case platf::capture_e::timeout:
|
||||
continue;
|
||||
case platf::capture_e::reinit:
|
||||
mic.reset();
|
||||
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
|
||||
if(!mic) {
|
||||
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
samples->raise(std::move(sample_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
int map_stream(int channels, bool quality) {
|
||||
int shift = quality ? 1 : 0;
|
||||
switch(channels) {
|
||||
case 2:
|
||||
return STEREO;
|
||||
case 6:
|
||||
return SURROUND51 + shift;
|
||||
case 8:
|
||||
return SURROUND71 + shift;
|
||||
}
|
||||
return STEREO;
|
||||
}
|
||||
|
||||
int start_audio_control(audio_ctx_t &ctx) {
|
||||
auto fg = util::fail_guard([]() {
|
||||
BOOST_LOG(warning) << "There will be no audio"sv;
|
||||
});
|
||||
|
||||
ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
|
||||
|
||||
// The default sink has not been replaced yet.
|
||||
ctx.restore_sink = false;
|
||||
|
||||
if(!(ctx.control = platf::audio_control())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto sink = ctx.control->sink_info();
|
||||
if(!sink) {
|
||||
// Let the calling code know it failed
|
||||
ctx.control.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx.sink = std::move(*sink);
|
||||
|
||||
fg.disable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stop_audio_control(audio_ctx_t &ctx) {
|
||||
// restore audio-sink if applicable
|
||||
if(!ctx.restore_sink) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
|
||||
if(!sink.empty()) {
|
||||
// Best effort, it's allowed to fail
|
||||
ctx.control->set_sink(sink);
|
||||
}
|
||||
}
|
||||
} // namespace audio
|
||||
45
src/audio.h
Normal file
45
src/audio.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef SUNSHINE_AUDIO_H
|
||||
#define SUNSHINE_AUDIO_H
|
||||
|
||||
#include "thread_safe.h"
|
||||
#include "utility.h"
|
||||
namespace audio {
|
||||
enum stream_config_e : int {
|
||||
STEREO,
|
||||
SURROUND51,
|
||||
HIGH_SURROUND51,
|
||||
SURROUND71,
|
||||
HIGH_SURROUND71,
|
||||
MAX_STREAM_CONFIG
|
||||
};
|
||||
|
||||
struct opus_stream_config_t {
|
||||
std::int32_t sampleRate;
|
||||
int channelCount;
|
||||
int streams;
|
||||
int coupledStreams;
|
||||
const std::uint8_t *mapping;
|
||||
};
|
||||
|
||||
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
|
||||
|
||||
struct config_t {
|
||||
enum flags_e : int {
|
||||
HIGH_QUALITY,
|
||||
HOST_AUDIO,
|
||||
MAX_FLAGS
|
||||
};
|
||||
|
||||
int packetDuration;
|
||||
int channels;
|
||||
int mask;
|
||||
|
||||
std::bitset<MAX_FLAGS> flags;
|
||||
};
|
||||
|
||||
using buffer_t = util::buffer_t<std::uint8_t>;
|
||||
using packet_t = std::pair<void *, buffer_t>;
|
||||
void capture(safe::mail_t mail, config_t config, void *channel_data);
|
||||
} // namespace audio
|
||||
|
||||
#endif
|
||||
300
src/cbs.cpp
Normal file
300
src/cbs.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
extern "C" {
|
||||
#include <cbs/cbs_h264.h>
|
||||
#include <cbs/cbs_h265.h>
|
||||
#include <cbs/video_levels.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
#include "cbs.h"
|
||||
#include "main.h"
|
||||
#include "utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
namespace cbs {
|
||||
void close(CodedBitstreamContext *c) {
|
||||
ff_cbs_close(&c);
|
||||
}
|
||||
|
||||
using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
|
||||
|
||||
class frag_t : public CodedBitstreamFragment {
|
||||
public:
|
||||
frag_t(frag_t &&o) {
|
||||
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
|
||||
|
||||
o.data = nullptr;
|
||||
o.units = nullptr;
|
||||
};
|
||||
|
||||
frag_t() {
|
||||
std::fill_n((std::uint8_t *)this, sizeof(*this), 0);
|
||||
}
|
||||
|
||||
frag_t &operator=(frag_t &&o) {
|
||||
std::copy((std::uint8_t *)&o, (std::uint8_t *)(&o + 1), (std::uint8_t *)this);
|
||||
|
||||
o.data = nullptr;
|
||||
o.units = nullptr;
|
||||
|
||||
return *this;
|
||||
};
|
||||
|
||||
|
||||
~frag_t() {
|
||||
if(data || units) {
|
||||
ff_cbs_fragment_free(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
util::buffer_t<std::uint8_t> write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::frag_t frag;
|
||||
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
|
||||
if(err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
|
||||
if(err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Could not write fragment data: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// frag.data_size * 8 - frag.data_bit_padding == bits in fragment
|
||||
util::buffer_t<std::uint8_t> data { frag.data_size };
|
||||
std::copy_n(frag.data, frag.data_size, std::begin(data));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
|
||||
cbs::ctx_t cbs_ctx;
|
||||
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
|
||||
|
||||
return write(cbs_ctx, nal, uh, codec_id);
|
||||
}
|
||||
|
||||
util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
|
||||
H264RawSPS sps {};
|
||||
|
||||
/* b_per_p == ctx->max_b_frames for h264 */
|
||||
/* desired_b_depth == avoption("b_depth") == 1 */
|
||||
/* max_b_depth == std::min(av_log2(ctx->b_per_p) + 1, desired_b_depth) ==> 1 */
|
||||
auto max_b_depth = 1;
|
||||
auto dpb_frame = ctx->gop_size == 1 ? 0 : 1 + max_b_depth;
|
||||
auto mb_width = (FFALIGN(ctx->width, 16) / 16) * 16;
|
||||
auto mb_height = (FFALIGN(ctx->height, 16) / 16) * 16;
|
||||
|
||||
|
||||
sps.nal_unit_header.nal_ref_idc = 3;
|
||||
sps.nal_unit_header.nal_unit_type = H264_NAL_SPS;
|
||||
|
||||
sps.profile_idc = FF_PROFILE_H264_HIGH & 0xFF;
|
||||
|
||||
sps.constraint_set1_flag = 1;
|
||||
|
||||
if(ctx->level != FF_LEVEL_UNKNOWN) {
|
||||
sps.level_idc = ctx->level;
|
||||
}
|
||||
else {
|
||||
auto framerate = ctx->framerate;
|
||||
|
||||
auto level = ff_h264_guess_level(
|
||||
sps.profile_idc,
|
||||
ctx->bit_rate,
|
||||
framerate.num / framerate.den,
|
||||
mb_width,
|
||||
mb_height,
|
||||
dpb_frame);
|
||||
|
||||
if(!level) {
|
||||
BOOST_LOG(error) << "Could not guess h264 level"sv;
|
||||
|
||||
return {};
|
||||
}
|
||||
sps.level_idc = level->level_idc;
|
||||
}
|
||||
|
||||
sps.seq_parameter_set_id = 0;
|
||||
sps.chroma_format_idc = 1;
|
||||
|
||||
sps.log2_max_frame_num_minus4 = 3; //4;
|
||||
sps.pic_order_cnt_type = 0;
|
||||
sps.log2_max_pic_order_cnt_lsb_minus4 = 0; //4;
|
||||
|
||||
sps.max_num_ref_frames = dpb_frame;
|
||||
|
||||
sps.pic_width_in_mbs_minus1 = mb_width / 16 - 1;
|
||||
sps.pic_height_in_map_units_minus1 = mb_height / 16 - 1;
|
||||
|
||||
sps.frame_mbs_only_flag = 1;
|
||||
sps.direct_8x8_inference_flag = 1;
|
||||
|
||||
if(ctx->width != mb_width || ctx->height != mb_height) {
|
||||
sps.frame_cropping_flag = 1;
|
||||
sps.frame_crop_left_offset = 0;
|
||||
sps.frame_crop_top_offset = 0;
|
||||
sps.frame_crop_right_offset = (mb_width - ctx->width) / 2;
|
||||
sps.frame_crop_bottom_offset = (mb_height - ctx->height) / 2;
|
||||
}
|
||||
|
||||
sps.vui_parameters_present_flag = 1;
|
||||
|
||||
auto &vui = sps.vui;
|
||||
|
||||
vui.video_format = 5;
|
||||
vui.colour_description_present_flag = 1;
|
||||
vui.video_signal_type_present_flag = 1;
|
||||
vui.video_full_range_flag = ctx->color_range == AVCOL_RANGE_JPEG;
|
||||
vui.colour_primaries = ctx->color_primaries;
|
||||
vui.transfer_characteristics = ctx->color_trc;
|
||||
vui.matrix_coefficients = ctx->colorspace;
|
||||
|
||||
vui.low_delay_hrd_flag = 1 - vui.fixed_frame_rate_flag;
|
||||
|
||||
vui.bitstream_restriction_flag = 1;
|
||||
vui.motion_vectors_over_pic_boundaries_flag = 1;
|
||||
vui.log2_max_mv_length_horizontal = 15;
|
||||
vui.log2_max_mv_length_vertical = 15;
|
||||
vui.max_num_reorder_frames = max_b_depth;
|
||||
vui.max_dec_frame_buffering = max_b_depth + 1;
|
||||
|
||||
return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264);
|
||||
}
|
||||
|
||||
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
|
||||
cbs::ctx_t ctx;
|
||||
if(ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
cbs::frag_t frag;
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if(err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
auto vps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_vps;
|
||||
auto sps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps;
|
||||
|
||||
H265RawSPS sps { *sps_p };
|
||||
H265RawVPS vps { *vps_p };
|
||||
|
||||
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
|
||||
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
|
||||
|
||||
auto &vui = sps.vui;
|
||||
std::memset(&vui, 0, sizeof(vui));
|
||||
|
||||
sps.vui_parameters_present_flag = 1;
|
||||
|
||||
// skip sample aspect ratio
|
||||
|
||||
vui.video_format = 5;
|
||||
vui.colour_description_present_flag = 1;
|
||||
vui.video_signal_type_present_flag = 1;
|
||||
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
|
||||
vui.colour_primaries = avctx->color_primaries;
|
||||
vui.transfer_characteristics = avctx->color_trc;
|
||||
vui.matrix_coefficients = avctx->colorspace;
|
||||
|
||||
|
||||
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
|
||||
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
|
||||
vui.vui_time_scale = vps.vps_time_scale;
|
||||
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
|
||||
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
|
||||
vui.vui_hrd_parameters_present_flag = 0;
|
||||
|
||||
vui.bitstream_restriction_flag = 1;
|
||||
vui.motion_vectors_over_pic_boundaries_flag = 1;
|
||||
vui.restricted_ref_pic_lists_flag = 1;
|
||||
vui.max_bytes_per_pic_denom = 0;
|
||||
vui.max_bits_per_min_cu_denom = 0;
|
||||
vui.log2_max_mv_length_horizontal = 15;
|
||||
vui.log2_max_mv_length_vertical = 15;
|
||||
|
||||
cbs::ctx_t write_ctx;
|
||||
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
|
||||
|
||||
|
||||
return hevc_t {
|
||||
nal_t {
|
||||
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *)&vps.nal_unit_header, AV_CODEC_ID_H265),
|
||||
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *)&vps_p->nal_unit_header, AV_CODEC_ID_H265),
|
||||
},
|
||||
|
||||
nal_t {
|
||||
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H265),
|
||||
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *)&sps_p->nal_unit_header, AV_CODEC_ID_H265),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet) {
|
||||
cbs::ctx_t ctx;
|
||||
if(ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
cbs::frag_t frag;
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet);
|
||||
if(err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto h264 = (H264RawNALUnitHeader *)((CodedBitstreamH264Context *)ctx->priv_data)->active_sps;
|
||||
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264);
|
||||
}
|
||||
|
||||
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
|
||||
return h264_t {
|
||||
make_sps_h264(ctx),
|
||||
read_sps_h264(packet),
|
||||
};
|
||||
}
|
||||
|
||||
bool validate_sps(const AVPacket *packet, int codec_id) {
|
||||
cbs::ctx_t ctx;
|
||||
if(ff_cbs_init(&ctx, (AVCodecID)codec_id, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cbs::frag_t frag;
|
||||
|
||||
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
|
||||
if(err < 0) {
|
||||
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
|
||||
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(codec_id == AV_CODEC_ID_H264) {
|
||||
auto h264 = (CodedBitstreamH264Context *)ctx->priv_data;
|
||||
|
||||
if(!h264->active_sps->vui_parameters_present_flag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps->vui_parameters_present_flag;
|
||||
}
|
||||
} // namespace cbs
|
||||
34
src/cbs.h
Normal file
34
src/cbs.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef SUNSHINE_CBS_H
|
||||
#define SUNSHINE_CBS_H
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
struct AVPacket;
|
||||
struct AVCodecContext;
|
||||
|
||||
namespace cbs {
|
||||
|
||||
struct nal_t {
|
||||
util::buffer_t<std::uint8_t> _new;
|
||||
util::buffer_t<std::uint8_t> old;
|
||||
};
|
||||
|
||||
struct hevc_t {
|
||||
nal_t vps;
|
||||
nal_t sps;
|
||||
};
|
||||
|
||||
struct h264_t {
|
||||
nal_t sps;
|
||||
};
|
||||
|
||||
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
|
||||
|
||||
/**
|
||||
* Check if SPS->VUI is present
|
||||
*/
|
||||
bool validate_sps(const AVPacket *packet, int codec_id);
|
||||
} // namespace cbs
|
||||
|
||||
#endif
|
||||
927
src/config.cpp
Normal file
927
src/config.cpp
Normal file
@@ -0,0 +1,927 @@
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "main.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::literals;
|
||||
|
||||
#define CA_DIR "credentials"
|
||||
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
||||
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
|
||||
|
||||
#define APPS_JSON_PATH platf::appdata().string() + "/apps.json"
|
||||
namespace config {
|
||||
|
||||
namespace nv {
|
||||
enum preset_e : int {
|
||||
_default = 0,
|
||||
slow,
|
||||
medium,
|
||||
fast,
|
||||
hp,
|
||||
hq,
|
||||
bd,
|
||||
ll_default,
|
||||
llhq,
|
||||
llhp,
|
||||
lossless_default, // lossless presets must be the last ones
|
||||
lossless_hp,
|
||||
};
|
||||
|
||||
enum rc_e : int {
|
||||
constqp = 0x0, /**< Constant QP mode */
|
||||
vbr = 0x1, /**< Variable bitrate mode */
|
||||
cbr = 0x2, /**< Constant bitrate mode */
|
||||
cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */
|
||||
cbr_hq = 0x10, /**< CBR, high quality (slower) */
|
||||
vbr_hq = 0x20 /**< VBR, high quality (slower) */
|
||||
};
|
||||
|
||||
enum coder_e : int {
|
||||
_auto = 0,
|
||||
cabac,
|
||||
cavlc
|
||||
};
|
||||
|
||||
std::optional<preset_e> preset_from_view(const std::string_view &preset) {
|
||||
#define _CONVERT_(x) \
|
||||
if(preset == #x##sv) return x
|
||||
_CONVERT_(slow);
|
||||
_CONVERT_(medium);
|
||||
_CONVERT_(fast);
|
||||
_CONVERT_(hp);
|
||||
_CONVERT_(bd);
|
||||
_CONVERT_(ll_default);
|
||||
_CONVERT_(llhq);
|
||||
_CONVERT_(llhp);
|
||||
_CONVERT_(lossless_default);
|
||||
_CONVERT_(lossless_hp);
|
||||
if(preset == "default"sv) return _default;
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
|
||||
#define _CONVERT_(x) \
|
||||
if(rc == #x##sv) return x
|
||||
_CONVERT_(constqp);
|
||||
_CONVERT_(vbr);
|
||||
_CONVERT_(cbr);
|
||||
_CONVERT_(cbr_hq);
|
||||
_CONVERT_(vbr_hq);
|
||||
_CONVERT_(cbr_ld_hq);
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int coder_from_view(const std::string_view &coder) {
|
||||
if(coder == "auto"sv) return _auto;
|
||||
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
|
||||
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace nv
|
||||
|
||||
namespace amd {
|
||||
enum quality_e : int {
|
||||
_default = 0,
|
||||
speed,
|
||||
balanced,
|
||||
};
|
||||
|
||||
enum class rc_hevc_e : int {
|
||||
constqp, /**< Constant QP mode */
|
||||
vbr_latency, /**< Latency Constrained Variable Bitrate */
|
||||
vbr_peak, /**< Peak Contrained Variable Bitrate */
|
||||
cbr, /**< Constant bitrate mode */
|
||||
};
|
||||
|
||||
enum class rc_h264_e : int {
|
||||
constqp, /**< Constant QP mode */
|
||||
cbr, /**< Constant bitrate mode */
|
||||
vbr_peak, /**< Peak Contrained Variable Bitrate */
|
||||
vbr_latency, /**< Latency Constrained Variable Bitrate */
|
||||
};
|
||||
|
||||
enum coder_e : int {
|
||||
_auto = 0,
|
||||
cabac,
|
||||
cavlc
|
||||
};
|
||||
|
||||
std::optional<quality_e> quality_from_view(const std::string_view &quality) {
|
||||
#define _CONVERT_(x) \
|
||||
if(quality == #x##sv) return x
|
||||
_CONVERT_(speed);
|
||||
_CONVERT_(balanced);
|
||||
if(quality == "default"sv) return _default;
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> rc_h264_from_view(const std::string_view &rc) {
|
||||
#define _CONVERT_(x) \
|
||||
if(rc == #x##sv) return (int)rc_h264_e::x
|
||||
_CONVERT_(constqp);
|
||||
_CONVERT_(vbr_latency);
|
||||
_CONVERT_(vbr_peak);
|
||||
_CONVERT_(cbr);
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> rc_hevc_from_view(const std::string_view &rc) {
|
||||
#define _CONVERT_(x) \
|
||||
if(rc == #x##sv) return (int)rc_hevc_e::x
|
||||
_CONVERT_(constqp);
|
||||
_CONVERT_(vbr_latency);
|
||||
_CONVERT_(vbr_peak);
|
||||
_CONVERT_(cbr);
|
||||
#undef _CONVERT_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int coder_from_view(const std::string_view &coder) {
|
||||
if(coder == "auto"sv) return _auto;
|
||||
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
|
||||
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace amd
|
||||
|
||||
namespace vt {
|
||||
|
||||
enum coder_e : int {
|
||||
_auto = 0,
|
||||
cabac,
|
||||
cavlc
|
||||
};
|
||||
|
||||
int coder_from_view(const std::string_view &coder) {
|
||||
if(coder == "auto"sv) return _auto;
|
||||
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
|
||||
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int allow_software_from_view(const std::string_view &software) {
|
||||
if(software == "allowed"sv || software == "forced") return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int force_software_from_view(const std::string_view &software) {
|
||||
if(software == "forced") return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rt_from_view(const std::string_view &rt) {
|
||||
if(rt == "disabled" || rt == "off" || rt == "0") return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace vt
|
||||
|
||||
video_t video {
|
||||
28, // qp
|
||||
|
||||
0, // hevc_mode
|
||||
|
||||
1, // min_threads
|
||||
{
|
||||
"superfast"s, // preset
|
||||
"zerolatency"s, // tune
|
||||
}, // software
|
||||
|
||||
{
|
||||
nv::llhq,
|
||||
std::nullopt,
|
||||
-1 }, // nv
|
||||
|
||||
{
|
||||
amd::balanced,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
-1 }, // amd
|
||||
|
||||
{
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
-1 }, // vt
|
||||
|
||||
{}, // encoder
|
||||
{}, // adapter_name
|
||||
{}, // output_name
|
||||
true // dwmflush
|
||||
};
|
||||
|
||||
audio_t audio {};
|
||||
|
||||
stream_t stream {
|
||||
10s, // ping_timeout
|
||||
|
||||
APPS_JSON_PATH,
|
||||
|
||||
20, // fecPercentage
|
||||
1 // channels
|
||||
};
|
||||
|
||||
nvhttp_t nvhttp {
|
||||
"pc", // origin_pin
|
||||
"lan", // origin web manager
|
||||
|
||||
PRIVATE_KEY_FILE,
|
||||
CERTIFICATE_FILE,
|
||||
|
||||
boost::asio::ip::host_name(), // sunshine_name,
|
||||
"sunshine_state.json"s, // file_state
|
||||
{}, // external_ip
|
||||
{
|
||||
"352x240"s,
|
||||
"480x360"s,
|
||||
"858x480"s,
|
||||
"1280x720"s,
|
||||
"1920x1080"s,
|
||||
"2560x1080"s,
|
||||
"3440x1440"s
|
||||
"1920x1200"s,
|
||||
"3860x2160"s,
|
||||
"3840x1600"s,
|
||||
}, // supported resolutions
|
||||
|
||||
{ 10, 30, 60, 90, 120 }, // supported fps
|
||||
};
|
||||
|
||||
input_t input {
|
||||
{
|
||||
{ 0x10, 0xA0 },
|
||||
{ 0x11, 0xA2 },
|
||||
{ 0x12, 0xA4 },
|
||||
},
|
||||
2s, // back_button_timeout
|
||||
500ms, // key_repeat_delay
|
||||
std::chrono::duration<double> { 1 / 24.9 }, // key_repeat_period
|
||||
|
||||
{
|
||||
platf::supported_gamepads().front().data(),
|
||||
platf::supported_gamepads().front().size(),
|
||||
}, // Default gamepad
|
||||
};
|
||||
|
||||
sunshine_t sunshine {
|
||||
2, // min_log_level
|
||||
0, // flags
|
||||
{}, // User file
|
||||
{}, // Username
|
||||
{}, // Password
|
||||
{}, // Password Salt
|
||||
platf::appdata().string() + "/sunshine.conf", // config file
|
||||
{}, // cmd args
|
||||
47989,
|
||||
};
|
||||
|
||||
bool endline(char ch) {
|
||||
return ch == '\r' || ch == '\n';
|
||||
}
|
||||
|
||||
bool space_tab(char ch) {
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
||||
bool whitespace(char ch) {
|
||||
return space_tab(ch) || endline(ch);
|
||||
}
|
||||
|
||||
std::string to_string(const char *begin, const char *end) {
|
||||
std::string result;
|
||||
|
||||
KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
|
||||
auto comment = std::find(pos, end, '#');
|
||||
auto endl = std::find_if(comment, end, endline);
|
||||
|
||||
result.append(pos, comment);
|
||||
|
||||
pos = endl;
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class It>
|
||||
It skip_list(It skipper, It end) {
|
||||
int stack = 1;
|
||||
while(skipper != end && stack) {
|
||||
if(*skipper == '[') {
|
||||
++stack;
|
||||
}
|
||||
if(*skipper == ']') {
|
||||
--stack;
|
||||
}
|
||||
|
||||
++skipper;
|
||||
}
|
||||
|
||||
return skipper;
|
||||
}
|
||||
|
||||
std::pair<
|
||||
std::string_view::const_iterator,
|
||||
std::optional<std::pair<std::string, std::string>>>
|
||||
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
begin = std::find_if_not(begin, end, whitespace);
|
||||
auto endl = std::find_if(begin, end, endline);
|
||||
auto endc = std::find(begin, endl, '#');
|
||||
endc = std::find_if(std::make_reverse_iterator(endc), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
||||
|
||||
auto eq = std::find(begin, endc, '=');
|
||||
if(eq == endc || eq == begin) {
|
||||
return std::make_pair(endl, std::nullopt);
|
||||
}
|
||||
|
||||
auto end_name = std::find_if_not(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), space_tab).base();
|
||||
auto begin_val = std::find_if_not(eq + 1, endc, space_tab);
|
||||
|
||||
if(begin_val == endl) {
|
||||
return std::make_pair(endl, std::nullopt);
|
||||
}
|
||||
|
||||
// Lists might contain newlines
|
||||
if(*begin_val == '[') {
|
||||
endl = skip_list(begin_val + 1, end);
|
||||
if(endl == end) {
|
||||
std::cout << "Warning: Config option ["sv << to_string(begin, end_name) << "] Missing ']'"sv;
|
||||
|
||||
return std::make_pair(endl, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(
|
||||
endl,
|
||||
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl)));
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
|
||||
std::unordered_map<std::string, std::string> vars;
|
||||
|
||||
auto pos = std::begin(file_content);
|
||||
auto end = std::end(file_content);
|
||||
|
||||
while(pos < end) {
|
||||
// auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; });
|
||||
TUPLE_2D(endl, var, parse_option(pos, end));
|
||||
|
||||
pos = endl;
|
||||
if(pos != end) {
|
||||
pos += (*pos == '\r') ? 2 : 1;
|
||||
}
|
||||
|
||||
if(!var) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vars.emplace(std::move(*var));
|
||||
}
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
auto it = vars.find(name);
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
input = std::move(it->second);
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
|
||||
std::string temp;
|
||||
string_f(vars, name, temp);
|
||||
|
||||
for(auto &allowed_val : allowed_vals) {
|
||||
if(temp == allowed_val) {
|
||||
input = std::move(temp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
|
||||
// appdata needs to be retrieved once only
|
||||
static auto appdata = platf::appdata();
|
||||
|
||||
std::string temp;
|
||||
string_f(vars, name, temp);
|
||||
|
||||
if(!temp.empty()) {
|
||||
input = temp;
|
||||
}
|
||||
|
||||
if(input.is_relative()) {
|
||||
input = appdata / input;
|
||||
}
|
||||
|
||||
auto dir = input;
|
||||
dir.remove_filename();
|
||||
|
||||
// Ensure the directories exists
|
||||
if(!fs::exists(dir)) {
|
||||
fs::create_directories(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
fs::path temp = input;
|
||||
|
||||
path_f(vars, name, temp);
|
||||
|
||||
input = temp.string();
|
||||
}
|
||||
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view val = it->second;
|
||||
|
||||
// If value is something like: "756" instead of 756
|
||||
if(val.size() >= 2 && val[0] == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
}
|
||||
|
||||
// If that integer is in hexadecimal
|
||||
if(val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view val = it->second;
|
||||
|
||||
// If value is something like: "756" instead of 756
|
||||
if(val.size() >= 2 && val[0] == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
}
|
||||
|
||||
// If that integer is in hexadecimal
|
||||
if(val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
input = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
input = util::from_view(val);
|
||||
}
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if(!tmp.empty()) {
|
||||
input = f(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
template<class F>
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
if(!tmp.empty()) {
|
||||
input = f(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
|
||||
int temp = input;
|
||||
|
||||
int_f(vars, name, temp);
|
||||
|
||||
TUPLE_2D_REF(lower, upper, range);
|
||||
if(temp >= lower && temp <= upper) {
|
||||
input = temp;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_bool(std::string &boolean) {
|
||||
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); });
|
||||
|
||||
return boolean == "true"sv ||
|
||||
boolean == "yes"sv ||
|
||||
boolean == "enable"sv ||
|
||||
boolean == "enabled"sv ||
|
||||
boolean == "on"sv ||
|
||||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
|
||||
}
|
||||
|
||||
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
|
||||
if(tmp.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
input = to_bool(tmp);
|
||||
}
|
||||
|
||||
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
|
||||
std::string tmp;
|
||||
string_f(vars, name, tmp);
|
||||
|
||||
if(tmp.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *c_str_p;
|
||||
auto val = std::strtod(tmp.c_str(), &c_str_p);
|
||||
|
||||
if(c_str_p == tmp.c_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
input = val;
|
||||
}
|
||||
|
||||
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
|
||||
double temp = input;
|
||||
|
||||
double_f(vars, name, temp);
|
||||
|
||||
TUPLE_2D_REF(lower, upper, range);
|
||||
if(temp >= lower && temp <= upper) {
|
||||
input = temp;
|
||||
}
|
||||
}
|
||||
|
||||
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
|
||||
std::string string;
|
||||
string_f(vars, name, string);
|
||||
|
||||
if(string.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.clear();
|
||||
|
||||
auto begin = std::cbegin(string);
|
||||
if(*begin == '[') {
|
||||
++begin;
|
||||
}
|
||||
|
||||
begin = std::find_if_not(begin, std::cend(string), whitespace);
|
||||
if(begin == std::cend(string)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pos = begin;
|
||||
while(pos < std::cend(string)) {
|
||||
if(*pos == '[') {
|
||||
pos = skip_list(pos + 1, std::cend(string)) + 1;
|
||||
}
|
||||
else if(*pos == ']') {
|
||||
break;
|
||||
}
|
||||
else if(*pos == ',') {
|
||||
input.emplace_back(begin, pos);
|
||||
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
|
||||
}
|
||||
else {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
if(pos != begin) {
|
||||
input.emplace_back(begin, pos);
|
||||
}
|
||||
}
|
||||
|
||||
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
|
||||
std::vector<std::string> list;
|
||||
list_string_f(vars, name, list);
|
||||
|
||||
for(auto &el : list) {
|
||||
std::string_view val = el;
|
||||
|
||||
// If value is something like: "756" instead of 756
|
||||
if(val.size() >= 2 && val[0] == '"') {
|
||||
val = val.substr(1, val.size() - 2);
|
||||
}
|
||||
|
||||
int tmp;
|
||||
|
||||
// If the integer is a hexadecimal
|
||||
if(val.size() >= 2 && val.substr(0, 2) == "0x"sv) {
|
||||
tmp = util::from_hex<int>(val.substr(2));
|
||||
}
|
||||
else {
|
||||
tmp = util::from_view(val);
|
||||
}
|
||||
input.emplace_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
|
||||
std::vector<int> list;
|
||||
list_int_f(vars, name, list);
|
||||
|
||||
// The list needs to be a multiple of 2
|
||||
if(list.size() % 2) {
|
||||
std::cout << "Warning: expected "sv << name << " to have a multiple of two elements --> not "sv << list.size() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
while(x < list.size()) {
|
||||
auto key = list[x++];
|
||||
auto val = list[x++];
|
||||
|
||||
input.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
int apply_flags(const char *line) {
|
||||
int ret = 0;
|
||||
while(*line != '\0') {
|
||||
switch(*line) {
|
||||
case '0':
|
||||
config::sunshine.flags[config::flag::PIN_STDIN].flip();
|
||||
break;
|
||||
case '1':
|
||||
config::sunshine.flags[config::flag::FRESH_STATE].flip();
|
||||
break;
|
||||
case '2':
|
||||
config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE].flip();
|
||||
break;
|
||||
case 'p':
|
||||
config::sunshine.flags[config::flag::UPNP].flip();
|
||||
break;
|
||||
default:
|
||||
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
++line;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void apply_config(std::unordered_map<std::string, std::string> &&vars) {
|
||||
if(!fs::exists(stream.file_apps.c_str())) {
|
||||
fs::copy_file(SUNSHINE_CONFIG_DIR "/apps.json", stream.file_apps);
|
||||
}
|
||||
|
||||
for(auto &[name, val] : vars) {
|
||||
std::cout << "["sv << name << "] -- ["sv << val << ']' << std::endl;
|
||||
}
|
||||
|
||||
int_f(vars, "qp", video.qp);
|
||||
int_f(vars, "min_threads", video.min_threads);
|
||||
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
|
||||
string_f(vars, "sw_preset", video.sw.preset);
|
||||
string_f(vars, "sw_tune", video.sw.tune);
|
||||
int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view);
|
||||
int_f(vars, "nv_rc", video.nv.rc, nv::rc_from_view);
|
||||
int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view);
|
||||
|
||||
int_f(vars, "amd_quality", video.amd.quality, amd::quality_from_view);
|
||||
|
||||
std::string rc;
|
||||
string_f(vars, "amd_rc", rc);
|
||||
int_f(vars, "amd_coder", video.amd.coder, amd::coder_from_view);
|
||||
if(!rc.empty()) {
|
||||
video.amd.rc_h264 = amd::rc_h264_from_view(rc);
|
||||
video.amd.rc_hevc = amd::rc_hevc_from_view(rc);
|
||||
}
|
||||
|
||||
int_f(vars, "vt_coder", video.vt.coder, vt::coder_from_view);
|
||||
int_f(vars, "vt_software", video.vt.allow_sw, vt::allow_software_from_view);
|
||||
int_f(vars, "vt_software", video.vt.require_sw, vt::force_software_from_view);
|
||||
int_f(vars, "vt_realtime", video.vt.realtime, vt::rt_from_view);
|
||||
|
||||
string_f(vars, "encoder", video.encoder);
|
||||
string_f(vars, "adapter_name", video.adapter_name);
|
||||
string_f(vars, "output_name", video.output_name);
|
||||
bool_f(vars, "dwmflush", video.dwmflush);
|
||||
|
||||
path_f(vars, "pkey", nvhttp.pkey);
|
||||
path_f(vars, "cert", nvhttp.cert);
|
||||
string_f(vars, "sunshine_name", nvhttp.sunshine_name);
|
||||
|
||||
path_f(vars, "file_state", nvhttp.file_state);
|
||||
|
||||
// Must be run after "file_state"
|
||||
config::sunshine.credentials_file = config::nvhttp.file_state;
|
||||
path_f(vars, "credentials_file", config::sunshine.credentials_file);
|
||||
|
||||
string_f(vars, "external_ip", nvhttp.external_ip);
|
||||
list_string_f(vars, "resolutions"s, nvhttp.resolutions);
|
||||
list_int_f(vars, "fps"s, nvhttp.fps);
|
||||
|
||||
string_f(vars, "audio_sink", audio.sink);
|
||||
string_f(vars, "virtual_sink", audio.virtual_sink);
|
||||
|
||||
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv });
|
||||
string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv });
|
||||
|
||||
int to = -1;
|
||||
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
|
||||
if(to != -1) {
|
||||
stream.ping_timeout = std::chrono::milliseconds(to);
|
||||
}
|
||||
|
||||
int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits<int>::max() });
|
||||
|
||||
path_f(vars, "file_apps", stream.file_apps);
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 255 });
|
||||
|
||||
map_int_int_f(vars, "keybindings"s, input.keybindings);
|
||||
|
||||
// This config option will only be used by the UI
|
||||
// When editing in the config file itself, use "keybindings"
|
||||
bool map_rightalt_to_win = false;
|
||||
bool_f(vars, "key_rightalt_to_key_win", map_rightalt_to_win);
|
||||
|
||||
if(map_rightalt_to_win) {
|
||||
input.keybindings.emplace(0xA5, 0x5B);
|
||||
}
|
||||
|
||||
to = std::numeric_limits<int>::min();
|
||||
int_f(vars, "back_button_timeout", to);
|
||||
|
||||
if(to > std::numeric_limits<int>::min()) {
|
||||
input.back_button_timeout = std::chrono::milliseconds { to };
|
||||
}
|
||||
|
||||
double repeat_frequency { 0 };
|
||||
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
|
||||
|
||||
if(repeat_frequency > 0) {
|
||||
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
|
||||
}
|
||||
|
||||
to = -1;
|
||||
int_f(vars, "key_repeat_delay", to);
|
||||
if(to >= 0) {
|
||||
input.key_repeat_delay = std::chrono::milliseconds { to };
|
||||
}
|
||||
|
||||
string_restricted_f(vars, "gamepad"s, input.gamepad, platf::supported_gamepads());
|
||||
|
||||
int port = sunshine.port;
|
||||
int_f(vars, "port"s, port);
|
||||
sunshine.port = (std::uint16_t)port;
|
||||
|
||||
bool upnp = false;
|
||||
bool_f(vars, "upnp"s, upnp);
|
||||
|
||||
if(upnp) {
|
||||
config::sunshine.flags[config::flag::UPNP].flip();
|
||||
}
|
||||
|
||||
std::string log_level_string;
|
||||
string_f(vars, "min_log_level", log_level_string);
|
||||
|
||||
if(!log_level_string.empty()) {
|
||||
if(log_level_string == "verbose"sv) {
|
||||
sunshine.min_log_level = 0;
|
||||
}
|
||||
else if(log_level_string == "debug"sv) {
|
||||
sunshine.min_log_level = 1;
|
||||
}
|
||||
else if(log_level_string == "info"sv) {
|
||||
sunshine.min_log_level = 2;
|
||||
}
|
||||
else if(log_level_string == "warning"sv) {
|
||||
sunshine.min_log_level = 3;
|
||||
}
|
||||
else if(log_level_string == "error"sv) {
|
||||
sunshine.min_log_level = 4;
|
||||
}
|
||||
else if(log_level_string == "fatal"sv) {
|
||||
sunshine.min_log_level = 5;
|
||||
}
|
||||
else if(log_level_string == "none"sv) {
|
||||
sunshine.min_log_level = 6;
|
||||
}
|
||||
else {
|
||||
// accept digit directly
|
||||
auto val = log_level_string[0];
|
||||
if(val >= '0' && val < '7') {
|
||||
sunshine.min_log_level = val - '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto it = vars.find("flags"s);
|
||||
if(it != std::end(vars)) {
|
||||
apply_flags(it->second.c_str());
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
if(sunshine.min_log_level <= 3) {
|
||||
for(auto &[var, _] : vars) {
|
||||
std::cout << "Warning: Unrecognized configurable option ["sv << var << ']' << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int parse(int argc, char *argv[]) {
|
||||
std::unordered_map<std::string, std::string> cmd_vars;
|
||||
|
||||
for(auto x = 1; x < argc; ++x) {
|
||||
auto line = argv[x];
|
||||
|
||||
if(line == "--help"sv) {
|
||||
print_help(*argv);
|
||||
return 1;
|
||||
}
|
||||
else if(*line == '-') {
|
||||
if(*(line + 1) == '-') {
|
||||
sunshine.cmd.name = line + 2;
|
||||
sunshine.cmd.argc = argc - x - 1;
|
||||
sunshine.cmd.argv = argv + x + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
if(apply_flags(line + 1)) {
|
||||
print_help(*argv);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto line_end = line + strlen(line);
|
||||
|
||||
auto pos = std::find(line, line_end, '=');
|
||||
if(pos == line_end) {
|
||||
sunshine.config_file = line;
|
||||
}
|
||||
else {
|
||||
TUPLE_EL(var, 1, parse_option(line, line_end));
|
||||
if(!var) {
|
||||
print_help(*argv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
TUPLE_EL_REF(name, 0, *var);
|
||||
|
||||
auto it = cmd_vars.find(name);
|
||||
if(it != std::end(cmd_vars)) {
|
||||
cmd_vars.erase(it);
|
||||
}
|
||||
|
||||
cmd_vars.emplace(std::move(*var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!fs::exists(sunshine.config_file)) {
|
||||
fs::copy_file(SUNSHINE_CONFIG_DIR "/sunshine.conf", sunshine.config_file);
|
||||
}
|
||||
|
||||
auto vars = parse_config(read_file(sunshine.config_file.c_str()));
|
||||
|
||||
for(auto &[name, value] : cmd_vars) {
|
||||
vars.insert_or_assign(std::move(name), std::move(value));
|
||||
}
|
||||
|
||||
apply_config(std::move(vars));
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace config
|
||||
135
src/config.h
Normal file
135
src/config.h
Normal file
@@ -0,0 +1,135 @@
|
||||
#ifndef SUNSHINE_CONFIG_H
|
||||
#define SUNSHINE_CONFIG_H
|
||||
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace config {
|
||||
struct video_t {
|
||||
// ffmpeg params
|
||||
int qp; // higher == more compression and less quality
|
||||
|
||||
int hevc_mode;
|
||||
|
||||
int min_threads; // Minimum number of threads/slices for CPU encoding
|
||||
struct {
|
||||
std::string preset;
|
||||
std::string tune;
|
||||
} sw;
|
||||
|
||||
struct {
|
||||
std::optional<int> preset;
|
||||
std::optional<int> rc;
|
||||
int coder;
|
||||
} nv;
|
||||
|
||||
struct {
|
||||
std::optional<int> quality;
|
||||
std::optional<int> rc_h264;
|
||||
std::optional<int> rc_hevc;
|
||||
int coder;
|
||||
} amd;
|
||||
|
||||
struct {
|
||||
int allow_sw;
|
||||
int require_sw;
|
||||
int realtime;
|
||||
int coder;
|
||||
} vt;
|
||||
|
||||
std::string encoder;
|
||||
std::string adapter_name;
|
||||
std::string output_name;
|
||||
bool dwmflush;
|
||||
};
|
||||
|
||||
struct audio_t {
|
||||
std::string sink;
|
||||
std::string virtual_sink;
|
||||
};
|
||||
|
||||
struct stream_t {
|
||||
std::chrono::milliseconds ping_timeout;
|
||||
|
||||
std::string file_apps;
|
||||
|
||||
int fec_percentage;
|
||||
|
||||
// max unique instances of video and audio streams
|
||||
int channels;
|
||||
};
|
||||
|
||||
struct nvhttp_t {
|
||||
// Could be any of the following values:
|
||||
// pc|lan|wan
|
||||
std::string origin_pin_allowed;
|
||||
std::string origin_web_ui_allowed;
|
||||
|
||||
std::string pkey; // must be 2048 bits
|
||||
std::string cert; // must be signed with a key of 2048 bits
|
||||
|
||||
std::string sunshine_name;
|
||||
|
||||
std::string file_state;
|
||||
|
||||
std::string external_ip;
|
||||
std::vector<std::string> resolutions;
|
||||
std::vector<int> fps;
|
||||
};
|
||||
|
||||
struct input_t {
|
||||
std::unordered_map<int, int> keybindings;
|
||||
|
||||
std::chrono::milliseconds back_button_timeout;
|
||||
std::chrono::milliseconds key_repeat_delay;
|
||||
std::chrono::duration<double> key_repeat_period;
|
||||
|
||||
std::string gamepad;
|
||||
};
|
||||
|
||||
namespace flag {
|
||||
enum flag_e : std::size_t {
|
||||
PIN_STDIN = 0, // Read PIN from stdin instead of http
|
||||
FRESH_STATE, // Do not load or save state
|
||||
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
|
||||
UPNP, // Try Universal Plug 'n Play
|
||||
CONST_PIN, // Use "universal" pin
|
||||
FLAG_SIZE
|
||||
};
|
||||
}
|
||||
|
||||
struct sunshine_t {
|
||||
int min_log_level;
|
||||
std::bitset<flag::FLAG_SIZE> flags;
|
||||
std::string credentials_file;
|
||||
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string salt;
|
||||
|
||||
std::string config_file;
|
||||
|
||||
struct cmd_t {
|
||||
std::string name;
|
||||
int argc;
|
||||
char **argv;
|
||||
} cmd;
|
||||
|
||||
std::uint16_t port;
|
||||
};
|
||||
|
||||
extern video_t video;
|
||||
extern audio_t audio;
|
||||
extern stream_t stream;
|
||||
extern nvhttp_t nvhttp;
|
||||
extern input_t input;
|
||||
extern sunshine_t sunshine;
|
||||
|
||||
int parse(int argc, char *argv[]);
|
||||
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
|
||||
} // namespace config
|
||||
#endif
|
||||
666
src/confighttp.cpp
Normal file
666
src/confighttp.cpp
Normal file
@@ -0,0 +1,666 @@
|
||||
//
|
||||
// Created by TheElixZammuto on 2021-05-09.
|
||||
// TODO: Authentication, better handling of routes common to nvhttp, cleanup
|
||||
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <Simple-Web-Server/crypto.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "confighttp.h"
|
||||
#include "crypto.h"
|
||||
#include "httpcommon.h"
|
||||
#include "main.h"
|
||||
#include "network.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace confighttp {
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
|
||||
|
||||
using args_t = SimpleWeb::CaseInsensitiveMultimap;
|
||||
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
|
||||
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
|
||||
|
||||
enum class op_e {
|
||||
ADD,
|
||||
REMOVE
|
||||
};
|
||||
|
||||
void print_req(const req_https_t &request) {
|
||||
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
|
||||
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
|
||||
|
||||
for(auto &[name, val] : request->header) {
|
||||
BOOST_LOG(debug) << name << " -- " << val;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
|
||||
for(auto &[name, val] : request->parse_query_string()) {
|
||||
BOOST_LOG(debug) << name << " -- " << val;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
}
|
||||
|
||||
void send_unauthorized(resp_https_t response, req_https_t request) {
|
||||
auto address = request->remote_endpoint_address();
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
|
||||
};
|
||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
||||
}
|
||||
|
||||
void send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||
auto address = request->remote_endpoint_address();
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "Location", path }
|
||||
};
|
||||
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
|
||||
}
|
||||
|
||||
bool authenticate(resp_https_t response, req_https_t request) {
|
||||
auto address = request->remote_endpoint_address();
|
||||
auto ip_type = net::from_address(address);
|
||||
|
||||
if(ip_type > http::origin_web_ui_allowed) {
|
||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- denied"sv;
|
||||
response->write(SimpleWeb::StatusCode::client_error_forbidden);
|
||||
return false;
|
||||
}
|
||||
|
||||
//If credentials are shown, redirect the user to a /welcome page
|
||||
if(config::sunshine.username.empty()) {
|
||||
send_redirect(response, request, "/welcome");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
send_unauthorized(response, request);
|
||||
});
|
||||
|
||||
auto auth = request->header.find("authorization");
|
||||
if(auth == request->header.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &rawAuth = auth->second;
|
||||
auto authData = SimpleWeb::Crypto::Base64::decode(rawAuth.substr("Basic "sv.length()));
|
||||
|
||||
int index = authData.find(':');
|
||||
if(index >= authData.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto username = authData.substr(0, index);
|
||||
auto password = authData.substr(index + 1);
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
|
||||
if(username != config::sunshine.username || hash != config::sunshine.password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
void not_found(resp_https_t response, req_https_t request) {
|
||||
pt::ptree tree;
|
||||
tree.put("root.<xmlattr>.status_code", 404);
|
||||
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
|
||||
*response << "HTTP/1.1 404 NOT FOUND\r\n"
|
||||
<< data.str();
|
||||
}
|
||||
|
||||
void getIndexPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "index.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getPinPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "pin.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getAppsPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "apps.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getClientsPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "clients.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getConfigPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "config.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getPasswordPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "password.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getWelcomePage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
if(!config::sunshine.username.empty()) {
|
||||
send_redirect(response, request, "/");
|
||||
return;
|
||||
}
|
||||
std::string header = read_file(WEB_DIR "header-no-nav.html");
|
||||
std::string content = read_file(WEB_DIR "welcome.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getTroubleshootingPage(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "troubleshooting.html");
|
||||
response->write(header + content);
|
||||
}
|
||||
|
||||
void getFaviconImage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "images/favicon.ico", std::ios::binary);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "image/x-icon");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
void getSunshineLogoImage(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "image/png");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
void getFontAwesomeCss(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::string content = read_file(WEB_DIR "fonts/fontawesome-free-web/css/all.min.css");
|
||||
response->write(content);
|
||||
}
|
||||
|
||||
void getFontAwesomeBrands(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "fonts/fontawesome-free-web/webfonts/fa-brands-400.ttf", std::ios::binary);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "font/ttf");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
void getFontAwesomeSolid(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::ifstream in(WEB_DIR "fonts/fontawesome-free-web/webfonts/fa-solid-900.ttf", std::ios::binary);
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "font/ttf");
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
void getBootstrapCss(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::string content = read_file(WEB_DIR "third_party/bootstrap.min.css");
|
||||
response->write(content);
|
||||
}
|
||||
|
||||
void getBootstrapJs(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::string content = read_file(WEB_DIR "third_party/bootstrap.bundle.min.js");
|
||||
response->write(content);
|
||||
}
|
||||
|
||||
void getVueJs(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
|
||||
std::string content = read_file(WEB_DIR "third_party/vue.js");
|
||||
response->write(content);
|
||||
}
|
||||
|
||||
void getApps(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string content = read_file(config::stream.file_apps.c_str());
|
||||
response->write(content);
|
||||
}
|
||||
|
||||
void saveApp(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
pt::ptree inputTree, fileTree;
|
||||
|
||||
BOOST_LOG(fatal) << config::stream.file_apps;
|
||||
try {
|
||||
//TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
|
||||
if(inputTree.get_child("prep-cmd").empty()) {
|
||||
inputTree.erase("prep-cmd");
|
||||
}
|
||||
|
||||
if(inputTree.get_child("detached").empty()) {
|
||||
inputTree.erase("detached");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = inputTree.get<int>("index");
|
||||
|
||||
inputTree.erase("index");
|
||||
|
||||
if(index == -1) {
|
||||
apps_node.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
//Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for(const auto &kv : apps_node) {
|
||||
if(i == index) {
|
||||
newApps.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
|
||||
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid Input JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
outputTree.put("status", "true");
|
||||
proc::refresh(config::stream.file_apps);
|
||||
}
|
||||
|
||||
void deleteApp(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
pt::ptree fileTree;
|
||||
try {
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = stoi(request->path_match[1]);
|
||||
|
||||
if(index < 0) {
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid Index");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
//Unfortuantely Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for(const auto &kv : apps_node) {
|
||||
if(i++ != index) {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid File JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
outputTree.put("status", "true");
|
||||
proc::refresh(config::stream.file_apps);
|
||||
}
|
||||
|
||||
void getConfig(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
outputTree.put("status", "true");
|
||||
outputTree.put("platform", SUNSHINE_PLATFORM);
|
||||
|
||||
auto vars = config::parse_config(read_file(config::sunshine.config_file.c_str()));
|
||||
|
||||
for(auto &[name, value] : vars) {
|
||||
outputTree.put(std::move(name), std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
void saveConfig(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
//TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
for(const auto &kv : inputTree) {
|
||||
std::string value = inputTree.get<std::string>(kv.first);
|
||||
if(value.length() == 0 || value.compare("null") == 0) continue;
|
||||
|
||||
configStream << kv.first << " = " << value << std::endl;
|
||||
}
|
||||
write_file(config::sunshine.config_file.c_str(), configStream.str());
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void savePassword(resp_https_t response, req_https_t request) {
|
||||
if(!config::sunshine.username.empty() && !authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree inputTree, outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
try {
|
||||
//TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
auto username = inputTree.count("currentUsername") > 0 ? inputTree.get<std::string>("currentUsername") : "";
|
||||
auto newUsername = inputTree.get<std::string>("newUsername");
|
||||
auto password = inputTree.count("currentPassword") > 0 ? inputTree.get<std::string>("currentPassword") : "";
|
||||
auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get<std::string>("newPassword") : "";
|
||||
auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get<std::string>("confirmNewPassword") : "";
|
||||
if(newUsername.length() == 0) newUsername = username;
|
||||
if(newUsername.length() == 0) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Invalid Username");
|
||||
}
|
||||
else {
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
if(config::sunshine.username.empty() || (username == config::sunshine.username && hash == config::sunshine.password)) {
|
||||
if(newPassword.empty() || newPassword != confirmPassword) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Password Mismatch");
|
||||
}
|
||||
else {
|
||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||
http::reload_user_creds(config::sunshine.credentials_file);
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Invalid Current Credentials");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void savePin(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree inputTree, outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
try {
|
||||
//TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
std::string pin = inputTree.get<std::string>("pin");
|
||||
outputTree.put("status", nvhttp::pin(pin));
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePin: "sv << e.what();
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void unpairAll(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
nvhttp::erase_all_clients();
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
|
||||
void closeApp(resp_https_t response, req_https_t request) {
|
||||
if(!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
proc::proc.terminate();
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
|
||||
void start() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
|
||||
auto port_https = map_port(PORT_HTTPS);
|
||||
|
||||
auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls);
|
||||
ctx->use_certificate_chain_file(config::nvhttp.cert);
|
||||
ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem);
|
||||
https_server_t server { ctx, 0 };
|
||||
server.default_resource = not_found;
|
||||
server.resource["^/$"]["GET"] = getIndexPage;
|
||||
server.resource["^/pin$"]["GET"] = getPinPage;
|
||||
server.resource["^/apps$"]["GET"] = getAppsPage;
|
||||
server.resource["^/clients$"]["GET"] = getClientsPage;
|
||||
server.resource["^/config$"]["GET"] = getConfigPage;
|
||||
server.resource["^/password$"]["GET"] = getPasswordPage;
|
||||
server.resource["^/welcome$"]["GET"] = getWelcomePage;
|
||||
server.resource["^/troubleshooting$"]["GET"] = getTroubleshootingPage;
|
||||
server.resource["^/api/pin"]["POST"] = savePin;
|
||||
server.resource["^/api/apps$"]["GET"] = getApps;
|
||||
server.resource["^/api/apps$"]["POST"] = saveApp;
|
||||
server.resource["^/api/config$"]["GET"] = getConfig;
|
||||
server.resource["^/api/config$"]["POST"] = saveConfig;
|
||||
server.resource["^/api/password$"]["POST"] = savePassword;
|
||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||
server.resource["^/api/clients/unpair$"]["POST"] = unpairAll;
|
||||
server.resource["^/api/apps/close"]["POST"] = closeApp;
|
||||
server.resource["^/images/favicon.ico$"]["GET"] = getFaviconImage;
|
||||
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
||||
server.resource["^/third_party/bootstrap.min.css$"]["GET"] = getBootstrapCss;
|
||||
server.resource["^/third_party/bootstrap.bundle.min.js$"]["GET"] = getBootstrapJs;
|
||||
server.resource["^/fontawesome/css/all.min.css$"]["GET"] = getFontAwesomeCss;
|
||||
server.resource["^/fontawesome/webfonts/fa-brands-400.ttf$"]["GET"] = getFontAwesomeBrands;
|
||||
server.resource["^/fontawesome/webfonts/fa-solid-900.ttf$"]["GET"] = getFontAwesomeSolid;
|
||||
server.resource["^/third_party/vue.js$"]["GET"] = getVueJs;
|
||||
server.config.reuse_address = true;
|
||||
server.config.address = "0.0.0.0"s;
|
||||
server.config.port = port_https;
|
||||
|
||||
try {
|
||||
server.bind();
|
||||
BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port_https << "]";
|
||||
}
|
||||
catch(boost::system::system_error &err) {
|
||||
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_https << "]: "sv << err.what();
|
||||
|
||||
shutdown_event->raise(true);
|
||||
return;
|
||||
}
|
||||
auto accept_and_run = [&](auto *server) {
|
||||
try {
|
||||
server->accept_and_run();
|
||||
}
|
||||
catch(boost::system::system_error &err) {
|
||||
// It's possible the exception gets thrown after calling server->stop() from a different thread
|
||||
if(shutdown_event->peek()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG(fatal) << "Couldn't start Configuration HTTPS server to port ["sv << port_https << "]: "sv << err.what();
|
||||
shutdown_event->raise(true);
|
||||
return;
|
||||
}
|
||||
};
|
||||
std::thread tcp { accept_and_run, &server };
|
||||
|
||||
// Wait for any event
|
||||
shutdown_event->view();
|
||||
|
||||
server.stop();
|
||||
|
||||
tcp.join();
|
||||
}
|
||||
} // namespace confighttp
|
||||
21
src/confighttp.h
Normal file
21
src/confighttp.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by loki on 6/3/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_CONFIGHTTP_H
|
||||
#define SUNSHINE_CONFIGHTTP_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "thread_safe.h"
|
||||
|
||||
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
|
||||
|
||||
|
||||
namespace confighttp {
|
||||
constexpr auto PORT_HTTPS = 1;
|
||||
void start();
|
||||
} // namespace confighttp
|
||||
|
||||
#endif //SUNSHINE_CONFIGHTTP_H
|
||||
495
src/crypto.cpp
Normal file
495
src/crypto.cpp
Normal file
@@ -0,0 +1,495 @@
|
||||
//
|
||||
// Created by loki on 5/31/19.
|
||||
//
|
||||
|
||||
#include "crypto.h"
|
||||
#include <openssl/pem.h>
|
||||
|
||||
namespace crypto {
|
||||
using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
|
||||
//using rsa_t = util::safe_ptr<RSA, RSA_free>;
|
||||
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
|
||||
|
||||
cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
|
||||
void cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store { X509_STORE_new() };
|
||||
|
||||
X509_STORE_add_cert(x509_store.get(), cert.get());
|
||||
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
|
||||
}
|
||||
|
||||
static int openssl_verify_cb(int ok, X509_STORE_CTX *ctx) {
|
||||
int err_code = X509_STORE_CTX_get_error(ctx);
|
||||
|
||||
switch(err_code) {
|
||||
//FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get mmonlight-embedded to work on the raspberry pi
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
return 1;
|
||||
|
||||
// Expired or not-yet-valid certificates are fine. Sometimes Moonlight is running on embedded devices
|
||||
// that don't have accurate clocks (or haven't yet synchronized by the time Moonlight first runs).
|
||||
// This behavior also matches what GeForce Experience does.
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
|
||||
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
|
||||
* Moonlight to be able to use Sunshine
|
||||
*
|
||||
* To circumvent this, x509_store_t instance will be created for each instance of the certificates.
|
||||
*/
|
||||
const char *cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
int err_code = 0;
|
||||
for(auto &[_, x509_store] : _certs) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
X509_STORE_CTX_cleanup(_cert_ctx.get());
|
||||
});
|
||||
|
||||
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), cert, nullptr);
|
||||
X509_STORE_CTX_set_verify_cb(_cert_ctx.get(), openssl_verify_cb);
|
||||
|
||||
// We don't care to validate the entire chain for the purposes of client auth.
|
||||
// Some versions of clients forked from Moonlight Embedded produce client certs
|
||||
// that OpenSSL doesn't detect as self-signed due to some X509v3 extensions.
|
||||
X509_STORE_CTX_set_flags(_cert_ctx.get(), X509_V_FLAG_PARTIAL_CHAIN);
|
||||
|
||||
auto err = X509_verify_cert(_cert_ctx.get());
|
||||
|
||||
if(err == 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
|
||||
|
||||
if(err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
|
||||
return X509_verify_cert_error_string(err_code);
|
||||
}
|
||||
}
|
||||
|
||||
return X509_verify_cert_error_string(err_code);
|
||||
}
|
||||
|
||||
namespace cipher {
|
||||
|
||||
static int init_decrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
if(!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_encrypt_gcm(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv->size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_encrypt_cbc(cipher_ctx_t &ctx, aes_t *key, aes_t *iv, bool padding) {
|
||||
ctx.reset(EVP_CIPHER_CTX_new());
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, key->data(), iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcm_t::decrypt(const std::string_view &tagged_cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv) {
|
||||
if(!decrypt_ctx && init_decrypt_gcm(decrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if(EVP_DecryptInit_ex(decrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cipher = tagged_cipher.substr(tag_size);
|
||||
auto tag = tagged_cipher.substr(0, tag_size);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
|
||||
int size;
|
||||
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_CIPHER_CTX_ctrl(decrypt_ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = size;
|
||||
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(size + len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gcm_t::encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv) {
|
||||
if(!encrypt_ctx && init_encrypt_gcm(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto tag = tagged_cipher;
|
||||
auto cipher = tag + tag_size;
|
||||
|
||||
int len;
|
||||
int size = round_to_pkcs7_padded(plaintext.size());
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// GCM encryption won't ever fill ciphertext here but we have to call it anyway
|
||||
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_CIPHER_CTX_ctrl(encrypt_ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_size, tag) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len + size;
|
||||
}
|
||||
|
||||
int ecb_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(decrypt_ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if(EVP_DecryptInit_ex(decrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(decrypt_ctx.get(), padding);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
auto size = (int)plaintext.size();
|
||||
// Decrypt into the caller's buffer, leaving room for the auth tag to be prepended
|
||||
if(EVP_DecryptUpdate(decrypt_ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_DecryptFinal_ex(decrypt_ctx.get(), plaintext.data(), &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ecb_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(encrypt_ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if(EVP_EncryptInit_ex(encrypt_ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(encrypt_ctx.get(), padding);
|
||||
|
||||
int len;
|
||||
|
||||
cipher.resize((plaintext.size() + 15) / 16 * 16);
|
||||
auto size = (int)cipher.size();
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbc_t::encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv) {
|
||||
if(!encrypt_ctx && init_encrypt_cbc(encrypt_ctx, &key, iv, padding)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calling with cipher == nullptr results in a parameter change
|
||||
// without requiring a reallocation of the internal cipher ctx.
|
||||
if(EVP_EncryptInit_ex(encrypt_ctx.get(), nullptr, nullptr, nullptr, iv->data()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int len;
|
||||
|
||||
int size = plaintext.size(); //round_to_pkcs7_padded(plaintext.size());
|
||||
|
||||
// Encrypt into the caller's buffer
|
||||
if(EVP_EncryptUpdate(encrypt_ctx.get(), cipher, &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(EVP_EncryptFinal_ex(encrypt_ctx.get(), cipher + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return size + len;
|
||||
}
|
||||
|
||||
ecb_t::ecb_t(const aes_t &key, bool padding)
|
||||
: cipher_t { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_new(), key, padding } {}
|
||||
|
||||
cbc_t::cbc_t(const aes_t &key, bool padding)
|
||||
: cipher_t { nullptr, nullptr, key, padding } {}
|
||||
|
||||
gcm_t::gcm_t(const crypto::aes_t &key, bool padding)
|
||||
: cipher_t { nullptr, nullptr, key, padding } {}
|
||||
|
||||
} // namespace cipher
|
||||
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key;
|
||||
|
||||
std::string salt_pin;
|
||||
salt_pin.reserve(salt.size() + pin.size());
|
||||
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
|
||||
|
||||
auto hsh = hash(salt_pin);
|
||||
|
||||
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext) {
|
||||
sha256_t hsh;
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, plaintext.data(), plaintext.size());
|
||||
SHA256_Final(hsh.data(), &sha256);
|
||||
|
||||
return hsh;
|
||||
}
|
||||
|
||||
x509_t x509(const std::string_view &x) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), x.data(), x.size());
|
||||
|
||||
x509_t p;
|
||||
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
pkey_t pkey(const std::string_view &k) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), k.data(), k.size());
|
||||
|
||||
pkey_t p = nullptr;
|
||||
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string pem(x509_t &x509) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
|
||||
PEM_write_bio_X509(bio.get(), x509.get());
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
}
|
||||
|
||||
std::string pem(pkey_t &pkey) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
|
||||
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
}
|
||||
|
||||
std::string_view signature(const x509_t &x) {
|
||||
// X509_ALGOR *_ = nullptr;
|
||||
|
||||
const ASN1_BIT_STRING *asn1 = nullptr;
|
||||
X509_get0_signature(&asn1, nullptr, x.get());
|
||||
|
||||
return { (const char *)asn1->data, (std::size_t)asn1->length };
|
||||
}
|
||||
|
||||
std::string rand(std::size_t bytes) {
|
||||
std::string r;
|
||||
r.resize(bytes);
|
||||
|
||||
RAND_bytes((uint8_t *)r.data(), r.size());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t slen = digest_size;
|
||||
|
||||
std::vector<uint8_t> digest;
|
||||
digest.resize(slen);
|
||||
|
||||
if(EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
|
||||
x509_t x509 { X509_new() };
|
||||
pkey_t pkey { EVP_PKEY_new() };
|
||||
|
||||
big_num_t big_num { BN_new() };
|
||||
BN_set_word(big_num.get(), RSA_F4);
|
||||
|
||||
auto rsa = RSA_new();
|
||||
RSA_generate_key_ex(rsa, key_bits, big_num.get(), nullptr);
|
||||
EVP_PKEY_assign_RSA(pkey.get(), rsa);
|
||||
|
||||
X509_set_version(x509.get(), 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 0);
|
||||
|
||||
constexpr auto year = 60 * 60 * 24 * 365;
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
|
||||
#else
|
||||
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) };
|
||||
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) };
|
||||
|
||||
X509_gmtime_adj(not_before.get(), 0);
|
||||
X509_gmtime_adj(not_after.get(), 20 * year);
|
||||
|
||||
X509_set1_notBefore(x509.get(), not_before.get());
|
||||
X509_set1_notAfter(x509.get(), not_after.get());
|
||||
#endif
|
||||
|
||||
X509_set_pubkey(x509.get(), pkey.get());
|
||||
|
||||
auto name = X509_get_subject_name(x509.get());
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||
(const std::uint8_t *)cn.data(), cn.size(),
|
||||
-1, 0);
|
||||
|
||||
X509_set_issuer_name(x509.get(), name);
|
||||
X509_sign(x509.get(), pkey.get(), EVP_sha256());
|
||||
|
||||
return { pem(x509), pem(pkey) };
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *)signature.data(), signature.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
return verify(x509, data, signature, EVP_sha256());
|
||||
}
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
}
|
||||
|
||||
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
|
||||
auto value = rand(bytes);
|
||||
|
||||
for(std::size_t i = 0; i != value.size(); ++i) {
|
||||
value[i] = alphabet[value[i] % alphabet.length()];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace crypto
|
||||
135
src/crypto.h
Normal file
135
src/crypto.h
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// Created by loki on 6/1/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_CRYPTO_H
|
||||
#define SUNSHINE_CRYPTO_H
|
||||
|
||||
#include <array>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
namespace crypto {
|
||||
struct creds_t {
|
||||
std::string x509;
|
||||
std::string pkey;
|
||||
};
|
||||
constexpr std::size_t digest_size = 256;
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *);
|
||||
|
||||
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
|
||||
|
||||
using aes_t = std::array<std::uint8_t, 16>;
|
||||
using x509_t = util::safe_ptr<X509, X509_free>;
|
||||
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
|
||||
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
|
||||
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
|
||||
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
|
||||
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
|
||||
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext);
|
||||
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
|
||||
|
||||
x509_t x509(const std::string_view &x);
|
||||
pkey_t pkey(const std::string_view &k);
|
||||
std::string pem(x509_t &x509);
|
||||
std::string pem(pkey_t &pkey);
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
|
||||
|
||||
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
|
||||
|
||||
std::string_view signature(const x509_t &x);
|
||||
|
||||
std::string rand(std::size_t bytes);
|
||||
std::string rand_alphabet(std::size_t bytes,
|
||||
const std::string_view &alphabet = std::string_view { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-" });
|
||||
|
||||
class cert_chain_t {
|
||||
public:
|
||||
KITTY_DECL_CONSTR(cert_chain_t)
|
||||
|
||||
void add(x509_t &&cert);
|
||||
|
||||
const char *verify(x509_t::element_type *cert);
|
||||
|
||||
private:
|
||||
std::vector<std::pair<x509_t, x509_store_t>> _certs;
|
||||
x509_store_ctx_t _cert_ctx;
|
||||
};
|
||||
|
||||
namespace cipher {
|
||||
constexpr std::size_t tag_size = 16;
|
||||
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
|
||||
return ((size + 15) / 16) * 16;
|
||||
}
|
||||
|
||||
class cipher_t {
|
||||
public:
|
||||
cipher_ctx_t decrypt_ctx;
|
||||
cipher_ctx_t encrypt_ctx;
|
||||
|
||||
aes_t key;
|
||||
|
||||
bool padding;
|
||||
};
|
||||
|
||||
class ecb_t : public cipher_t {
|
||||
public:
|
||||
ecb_t() = default;
|
||||
ecb_t(ecb_t &&) noexcept = default;
|
||||
ecb_t &operator=(ecb_t &&) noexcept = default;
|
||||
|
||||
ecb_t(const aes_t &key, bool padding = true);
|
||||
|
||||
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
|
||||
};
|
||||
|
||||
class gcm_t : public cipher_t {
|
||||
public:
|
||||
gcm_t() = default;
|
||||
gcm_t(gcm_t &&) noexcept = default;
|
||||
gcm_t &operator=(gcm_t &&) noexcept = default;
|
||||
|
||||
gcm_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size()) + crypto::cipher::tag_size
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
|
||||
|
||||
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
|
||||
};
|
||||
|
||||
class cbc_t : public cipher_t {
|
||||
public:
|
||||
cbc_t() = default;
|
||||
cbc_t(cbc_t &&) noexcept = default;
|
||||
cbc_t &operator=(cbc_t &&) noexcept = default;
|
||||
|
||||
cbc_t(const crypto::aes_t &key, bool padding = true);
|
||||
|
||||
/**
|
||||
* length of cipher must be at least: round_to_pkcs7_padded(plaintext.size())
|
||||
*
|
||||
* return -1 on error
|
||||
* return bytes written on success
|
||||
*/
|
||||
int encrypt(const std::string_view &plaintext, std::uint8_t *cipher, aes_t *iv);
|
||||
};
|
||||
} // namespace cipher
|
||||
} // namespace crypto
|
||||
|
||||
#endif //SUNSHINE_CRYPTO_H
|
||||
183
src/httpcommon.cpp
Normal file
183
src/httpcommon.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
|
||||
|
||||
#include "process.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
#include <Simple-Web-Server/server_http.hpp>
|
||||
#include <Simple-Web-Server/server_https.hpp>
|
||||
#include <boost/asio/ssl/context_base.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "crypto.h"
|
||||
#include "httpcommon.h"
|
||||
#include "main.h"
|
||||
#include "network.h"
|
||||
#include "nvhttp.h"
|
||||
#include "platform/common.h"
|
||||
#include "rtsp.h"
|
||||
#include "utility.h"
|
||||
#include "uuid.h"
|
||||
|
||||
namespace http {
|
||||
using namespace std::literals;
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
int reload_user_creds(const std::string &file);
|
||||
bool user_creds_exist(const std::string &file);
|
||||
|
||||
std::string unique_id;
|
||||
net::net_e origin_pin_allowed;
|
||||
net::net_e origin_web_ui_allowed;
|
||||
|
||||
int init() {
|
||||
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
|
||||
origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed);
|
||||
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
|
||||
|
||||
if(clean_slate) {
|
||||
unique_id = util::uuid_t::generate().string();
|
||||
auto dir = std::filesystem::temp_directory_path() / "Sushine"sv;
|
||||
config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string();
|
||||
config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string();
|
||||
}
|
||||
|
||||
if(!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) {
|
||||
if(create_creds(config::nvhttp.pkey, config::nvhttp.cert)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(user_creds_exist(config::sunshine.credentials_file)) {
|
||||
if(reload_user_creds(config::sunshine.credentials_file)) return -1;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
|
||||
pt::ptree outputTree;
|
||||
|
||||
if(fs::exists(file)) {
|
||||
try {
|
||||
pt::read_json(file, outputTree);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
auto salt = crypto::rand_alphabet(16);
|
||||
outputTree.put("username", username);
|
||||
outputTree.put("salt", salt);
|
||||
outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string());
|
||||
try {
|
||||
pt::write_json(file, outputTree);
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(error) << "generating user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "New credentials have been created"sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool user_creds_exist(const std::string &file) {
|
||||
if(!fs::exists(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
pt::read_json(file, inputTree);
|
||||
return inputTree.find("username") != inputTree.not_found() &&
|
||||
inputTree.find("password") != inputTree.not_found() &&
|
||||
inputTree.find("salt") != inputTree.not_found();
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(error) << "validating user credentials: "sv << e.what();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int reload_user_creds(const std::string &file) {
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
pt::read_json(file, inputTree);
|
||||
config::sunshine.username = inputTree.get<std::string>("username");
|
||||
config::sunshine.password = inputTree.get<std::string>("password");
|
||||
config::sunshine.salt = inputTree.get<std::string>("salt");
|
||||
}
|
||||
catch(std::exception &e) {
|
||||
BOOST_LOG(error) << "loading user credentials: "sv << e.what();
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_creds(const std::string &pkey, const std::string &cert) {
|
||||
fs::path pkey_path = pkey;
|
||||
fs::path cert_path = cert;
|
||||
|
||||
auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048);
|
||||
|
||||
auto pkey_dir = pkey_path;
|
||||
auto cert_dir = cert_path;
|
||||
pkey_dir.remove_filename();
|
||||
cert_dir.remove_filename();
|
||||
|
||||
std::error_code err_code {};
|
||||
fs::create_directories(pkey_dir, err_code);
|
||||
if(err_code) {
|
||||
BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::create_directories(cert_dir, err_code);
|
||||
if(err_code) {
|
||||
BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(write_file(pkey.c_str(), creds.pkey)) {
|
||||
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(write_file(cert.c_str(), creds.x509)) {
|
||||
BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::permissions(pkey_path,
|
||||
fs::perms::owner_read | fs::perms::owner_write,
|
||||
fs::perm_options::replace, err_code);
|
||||
|
||||
if(err_code) {
|
||||
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs::permissions(cert_path,
|
||||
fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write,
|
||||
fs::perm_options::replace, err_code);
|
||||
|
||||
if(err_code) {
|
||||
BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace http
|
||||
19
src/httpcommon.h
Normal file
19
src/httpcommon.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "network.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
int init();
|
||||
int create_creds(const std::string &pkey, const std::string &cert);
|
||||
int save_user_creds(
|
||||
const std::string &file,
|
||||
const std::string &username,
|
||||
const std::string &password,
|
||||
bool run_our_mouth = false);
|
||||
|
||||
int reload_user_creds(const std::string &file);
|
||||
extern std::string unique_id;
|
||||
extern net::net_e origin_pin_allowed;
|
||||
extern net::net_e origin_web_ui_allowed;
|
||||
|
||||
} // namespace http
|
||||
686
src/input.cpp
Normal file
686
src/input.cpp
Normal file
@@ -0,0 +1,686 @@
|
||||
//
|
||||
// Created by loki on 6/20/19.
|
||||
//
|
||||
|
||||
// define uint32_t for <moonlight-common-c/src/Input.h>
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#include <moonlight-common-c/src/Input.h>
|
||||
}
|
||||
|
||||
#include <bitset>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "config.h"
|
||||
#include "input.h"
|
||||
#include "main.h"
|
||||
#include "platform/common.h"
|
||||
#include "thread_pool.h"
|
||||
#include "utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
namespace input {
|
||||
|
||||
constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
|
||||
#define DISABLE_LEFT_BUTTON_DELAY ((util::ThreadPool::task_id_t)0x01)
|
||||
#define ENABLE_LEFT_BUTTON_DELAY nullptr
|
||||
|
||||
constexpr auto VKEY_SHIFT = 0x10;
|
||||
constexpr auto VKEY_LSHIFT = 0xA0;
|
||||
constexpr auto VKEY_RSHIFT = 0xA1;
|
||||
constexpr auto VKEY_CONTROL = 0x11;
|
||||
constexpr auto VKEY_LCONTROL = 0xA2;
|
||||
constexpr auto VKEY_RCONTROL = 0xA3;
|
||||
constexpr auto VKEY_MENU = 0x12;
|
||||
constexpr auto VKEY_LMENU = 0xA4;
|
||||
constexpr auto VKEY_RMENU = 0xA5;
|
||||
|
||||
enum class button_state_e {
|
||||
NONE,
|
||||
DOWN,
|
||||
UP
|
||||
};
|
||||
|
||||
template<std::size_t N>
|
||||
int alloc_id(std::bitset<N> &gamepad_mask) {
|
||||
for(int x = 0; x < gamepad_mask.size(); ++x) {
|
||||
if(!gamepad_mask[x]) {
|
||||
gamepad_mask[x] = true;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
void free_id(std::bitset<N> &gamepad_mask, int id) {
|
||||
gamepad_mask[id] = false;
|
||||
}
|
||||
|
||||
static util::TaskPool::task_id_t key_press_repeat_id {};
|
||||
static std::unordered_map<short, bool> key_press {};
|
||||
static std::array<std::uint8_t, 5> mouse_press {};
|
||||
|
||||
static platf::input_t platf_input;
|
||||
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
|
||||
|
||||
void free_gamepad(platf::input_t &platf_input, int id) {
|
||||
platf::gamepad(platf_input, id, platf::gamepad_state_t {});
|
||||
platf::free_gamepad(platf_input, id);
|
||||
|
||||
free_id(gamepadMask, id);
|
||||
}
|
||||
struct gamepad_t {
|
||||
gamepad_t() : gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {}
|
||||
~gamepad_t() {
|
||||
if(id >= 0) {
|
||||
task_pool.push([id = this->id]() {
|
||||
free_gamepad(platf_input, id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
platf::gamepad_state_t gamepad_state;
|
||||
|
||||
util::ThreadPool::task_id_t back_timeout_id;
|
||||
|
||||
int id;
|
||||
|
||||
// When emulating the HOME button, we may need to artificially release the back button.
|
||||
// Afterwards, the gamepad state on sunshine won't match the state on Moonlight.
|
||||
// To prevent Sunshine from sending erronious input data to the active application,
|
||||
// Sunshine forces the button to be in a specific state until the gamepad state matches that of
|
||||
// Moonlight once more.
|
||||
button_state_e back_button_state;
|
||||
};
|
||||
|
||||
struct input_t {
|
||||
enum shortkey_e {
|
||||
CTRL = 0x1,
|
||||
ALT = 0x2,
|
||||
SHIFT = 0x4,
|
||||
|
||||
SHORTCUT = CTRL | ALT | SHIFT
|
||||
};
|
||||
|
||||
input_t(
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
|
||||
platf::rumble_queue_t rumble_queue)
|
||||
: shortcutFlags {},
|
||||
active_gamepad_state {},
|
||||
gamepads(MAX_GAMEPADS),
|
||||
touch_port_event { std::move(touch_port_event) },
|
||||
rumble_queue { std::move(rumble_queue) },
|
||||
mouse_left_button_timeout {},
|
||||
touch_port { 0, 0, 0, 0, 0, 0, 1.0f } {}
|
||||
|
||||
// Keep track of alt+ctrl+shift key combo
|
||||
int shortcutFlags;
|
||||
|
||||
std::uint16_t active_gamepad_state;
|
||||
std::vector<gamepad_t> gamepads;
|
||||
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
|
||||
platf::rumble_queue_t rumble_queue;
|
||||
|
||||
util::ThreadPool::task_id_t mouse_left_button_timeout;
|
||||
|
||||
input::touch_port_t touch_port;
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply shortcut based on VKEY
|
||||
* On success
|
||||
* return > 0
|
||||
* On nothing
|
||||
* return 0
|
||||
*/
|
||||
inline int apply_shortcut(short keyCode) {
|
||||
constexpr auto VK_F1 = 0x70;
|
||||
constexpr auto VK_F13 = 0x7C;
|
||||
|
||||
BOOST_LOG(debug) << "Apply Shortcut: 0x"sv << util::hex((std::uint8_t)keyCode).to_string_view();
|
||||
|
||||
if(keyCode >= VK_F1 && keyCode <= VK_F13) {
|
||||
mail::man->event<int>(mail::switch_display)->raise(keyCode - VK_F1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch(keyCode) {
|
||||
case 0x4E /* VKEY_N */:
|
||||
display_cursor = !display_cursor;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin relative mouse move packet--"sv << std::endl
|
||||
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
|
||||
<< "deltaY ["sv << util::endian::big(packet->deltaY) << ']' << std::endl
|
||||
<< "--end relative mouse move packet--"sv;
|
||||
}
|
||||
|
||||
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin absolute mouse move packet--"sv << std::endl
|
||||
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
|
||||
<< "y ["sv << util::endian::big(packet->y) << ']' << std::endl
|
||||
<< "width ["sv << util::endian::big(packet->width) << ']' << std::endl
|
||||
<< "height ["sv << util::endian::big(packet->height) << ']' << std::endl
|
||||
<< "--end absolute mouse move packet--"sv;
|
||||
}
|
||||
|
||||
void print(PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse button packet--"sv << std::endl
|
||||
<< "action ["sv << util::hex(packet->action).to_string_view() << ']' << std::endl
|
||||
<< "button ["sv << util::hex(packet->button).to_string_view() << ']' << std::endl
|
||||
<< "--end mouse button packet--"sv;
|
||||
}
|
||||
|
||||
void print(PNV_SCROLL_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin mouse scroll packet--"sv << std::endl
|
||||
<< "scrollAmt1 ["sv << util::endian::big(packet->scrollAmt1) << ']' << std::endl
|
||||
<< "--end mouse scroll packet--"sv;
|
||||
}
|
||||
|
||||
void print(PNV_KEYBOARD_PACKET packet) {
|
||||
BOOST_LOG(debug)
|
||||
<< "--begin keyboard packet--"sv << std::endl
|
||||
<< "keyAction ["sv << util::hex(packet->keyAction).to_string_view() << ']' << std::endl
|
||||
<< "keyCode ["sv << util::hex(packet->keyCode).to_string_view() << ']' << std::endl
|
||||
<< "modifiers ["sv << util::hex(packet->modifiers).to_string_view() << ']' << std::endl
|
||||
<< "--end keyboard packet--"sv;
|
||||
}
|
||||
|
||||
void print(PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
// Moonlight spams controller packet even when not necessary
|
||||
BOOST_LOG(verbose)
|
||||
<< "--begin controller packet--"sv << std::endl
|
||||
<< "controllerNumber ["sv << packet->controllerNumber << ']' << std::endl
|
||||
<< "activeGamepadMask ["sv << util::hex(packet->activeGamepadMask).to_string_view() << ']' << std::endl
|
||||
<< "buttonFlags ["sv << util::hex(packet->buttonFlags).to_string_view() << ']' << std::endl
|
||||
<< "leftTrigger ["sv << util::hex(packet->leftTrigger).to_string_view() << ']' << std::endl
|
||||
<< "rightTrigger ["sv << util::hex(packet->rightTrigger).to_string_view() << ']' << std::endl
|
||||
<< "leftStickX ["sv << packet->leftStickX << ']' << std::endl
|
||||
<< "leftStickY ["sv << packet->leftStickY << ']' << std::endl
|
||||
<< "rightStickX ["sv << packet->rightStickX << ']' << std::endl
|
||||
<< "rightStickY ["sv << packet->rightStickY << ']' << std::endl
|
||||
<< "--end controller packet--"sv;
|
||||
}
|
||||
|
||||
constexpr int PACKET_TYPE_SCROLL_OR_KEYBOARD = PACKET_TYPE_SCROLL;
|
||||
void print(void *input) {
|
||||
int input_type = util::endian::big(*(int *)input);
|
||||
|
||||
switch(input_type) {
|
||||
case PACKET_TYPE_REL_MOUSE_MOVE:
|
||||
print((PNV_REL_MOUSE_MOVE_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_ABS_MOUSE_MOVE:
|
||||
print((PNV_ABS_MOUSE_MOVE_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_MOUSE_BUTTON:
|
||||
print((PNV_MOUSE_BUTTON_PACKET)input);
|
||||
break;
|
||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
|
||||
char *tmp_input = (char *)input + 4;
|
||||
if(tmp_input[0] == 0x0A) {
|
||||
print((PNV_SCROLL_PACKET)input);
|
||||
}
|
||||
else {
|
||||
print((PNV_KEYBOARD_PACKET)input);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_MULTI_CONTROLLER:
|
||||
print((PNV_MULTI_CONTROLLER_PACKET)input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
|
||||
input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
|
||||
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
|
||||
}
|
||||
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
|
||||
if(input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
|
||||
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
|
||||
}
|
||||
|
||||
auto &touch_port_event = input->touch_port_event;
|
||||
auto &touch_port = input->touch_port;
|
||||
if(touch_port_event->peek()) {
|
||||
touch_port = *touch_port_event->pop();
|
||||
}
|
||||
|
||||
float x = util::endian::big(packet->x);
|
||||
float y = util::endian::big(packet->y);
|
||||
|
||||
// Prevent divide by zero
|
||||
// Don't expect it to happen, but just in case
|
||||
if(!packet->width || !packet->height) {
|
||||
BOOST_LOG(warning) << "Moonlight passed invalid dimensions"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto width = (float)util::endian::big(packet->width);
|
||||
auto height = (float)util::endian::big(packet->height);
|
||||
|
||||
auto scalarX = touch_port.width / width;
|
||||
auto scalarY = touch_port.height / height;
|
||||
|
||||
x *= scalarX;
|
||||
y *= scalarY;
|
||||
|
||||
auto offsetX = touch_port.client_offsetX;
|
||||
auto offsetY = touch_port.client_offsetY;
|
||||
|
||||
std::clamp(x, offsetX, width - offsetX);
|
||||
std::clamp(y, offsetY, height - offsetY);
|
||||
|
||||
platf::touch_port_t abs_port {
|
||||
touch_port.offset_x, touch_port.offset_y,
|
||||
touch_port.env_width, touch_port.env_height
|
||||
};
|
||||
|
||||
platf::abs_mouse(platf_input, abs_port, (x - offsetX) * touch_port.scalar_inv, (y - offsetY) * touch_port.scalar_inv);
|
||||
}
|
||||
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
|
||||
auto constexpr BUTTON_RELEASED = 0x09;
|
||||
|
||||
auto constexpr BUTTON_LEFT = 0x01;
|
||||
auto constexpr BUTTON_RIGHT = 0x03;
|
||||
|
||||
auto release = packet->action == BUTTON_RELEASED;
|
||||
|
||||
auto button = util::endian::big(packet->button);
|
||||
if(button > 0 && button < mouse_press.size()) {
|
||||
if(mouse_press[button] != release) {
|
||||
// button state is already what we want
|
||||
return;
|
||||
}
|
||||
|
||||
mouse_press[button] = !release;
|
||||
}
|
||||
///////////////////////////////////
|
||||
/*/
|
||||
* When Moonlight sends mouse input through absolute coordinates,
|
||||
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
|
||||
* As a result, Sunshine will left click on hyperlinks in the browser before right clicking
|
||||
*
|
||||
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
|
||||
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
|
||||
* absolute mouse coordinates have been send.
|
||||
*
|
||||
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
|
||||
*
|
||||
* input->mouse_left_button_timeout can only be nullptr
|
||||
* when the last mouse coordinates were absolute
|
||||
/*/
|
||||
if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) {
|
||||
auto f = [=]() {
|
||||
auto left_released = mouse_press[BUTTON_LEFT];
|
||||
if(left_released) {
|
||||
// Already released left button
|
||||
return;
|
||||
}
|
||||
platf::button_mouse(platf_input, BUTTON_LEFT, release);
|
||||
|
||||
mouse_press[BUTTON_LEFT] = false;
|
||||
input->mouse_left_button_timeout = nullptr;
|
||||
};
|
||||
|
||||
input->mouse_left_button_timeout = task_pool.pushDelayed(std::move(f), 10ms).task_id;
|
||||
|
||||
return;
|
||||
}
|
||||
if(
|
||||
button == BUTTON_RIGHT && !release &&
|
||||
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) {
|
||||
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
|
||||
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
|
||||
|
||||
mouse_press[BUTTON_RIGHT] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
///////////////////////////////////
|
||||
|
||||
platf::button_mouse(platf_input, button, release);
|
||||
}
|
||||
|
||||
short map_keycode(short keycode) {
|
||||
auto it = config::input.keybindings.find(keycode);
|
||||
if(it != std::end(config::input.keybindings)) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return keycode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update flags for keyboard shortcut combo's
|
||||
*/
|
||||
inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
|
||||
switch(keyCode) {
|
||||
case VKEY_SHIFT:
|
||||
case VKEY_LSHIFT:
|
||||
case VKEY_RSHIFT:
|
||||
if(release) {
|
||||
*flags &= ~input_t::SHIFT;
|
||||
}
|
||||
else {
|
||||
*flags |= input_t::SHIFT;
|
||||
}
|
||||
break;
|
||||
case VKEY_CONTROL:
|
||||
case VKEY_LCONTROL:
|
||||
case VKEY_RCONTROL:
|
||||
if(release) {
|
||||
*flags &= ~input_t::CTRL;
|
||||
}
|
||||
else {
|
||||
*flags |= input_t::CTRL;
|
||||
}
|
||||
break;
|
||||
case VKEY_MENU:
|
||||
case VKEY_LMENU:
|
||||
case VKEY_RMENU:
|
||||
if(release) {
|
||||
*flags &= ~input_t::ALT;
|
||||
}
|
||||
else {
|
||||
*flags |= input_t::ALT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void repeat_key(short key_code) {
|
||||
// If key no longer pressed, stop repeating
|
||||
if(!key_press[key_code]) {
|
||||
key_press_repeat_id = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
platf::keyboard(platf_input, map_keycode(key_code), false);
|
||||
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id;
|
||||
}
|
||||
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
|
||||
auto constexpr BUTTON_RELEASED = 0x04;
|
||||
|
||||
auto release = packet->keyAction == BUTTON_RELEASED;
|
||||
auto keyCode = packet->keyCode & 0x00FF;
|
||||
|
||||
auto &pressed = key_press[keyCode];
|
||||
if(!pressed) {
|
||||
if(!release) {
|
||||
// A new key has been pressed down, we need to check for key combo's
|
||||
// If a keycombo has been pressed down, don't pass it through
|
||||
if(input->shortcutFlags == input_t::SHORTCUT && apply_shortcut(keyCode) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(key_press_repeat_id) {
|
||||
task_pool.cancel(key_press_repeat_id);
|
||||
}
|
||||
|
||||
if(config::input.key_repeat_delay.count() > 0) {
|
||||
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, keyCode).task_id;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Already released
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(!release) {
|
||||
// Already pressed down key
|
||||
return;
|
||||
}
|
||||
|
||||
pressed = !release;
|
||||
|
||||
update_shortcutFlags(&input->shortcutFlags, map_keycode(keyCode), release);
|
||||
platf::keyboard(platf_input, map_keycode(keyCode), release);
|
||||
}
|
||||
|
||||
void passthrough(PNV_SCROLL_PACKET packet) {
|
||||
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
|
||||
}
|
||||
|
||||
int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state, const platf::rumble_queue_t &rumble_queue) {
|
||||
auto xorGamepadMask = old_state ^ new_state;
|
||||
if(!xorGamepadMask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(int x = 0; x < sizeof(std::int16_t) * 8; ++x) {
|
||||
if((xorGamepadMask >> x) & 1) {
|
||||
auto &gamepad = gamepads[x];
|
||||
|
||||
if((old_state >> x) & 1) {
|
||||
if(gamepad.id < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
free_gamepad(platf_input, gamepad.id);
|
||||
gamepad.id = -1;
|
||||
}
|
||||
else {
|
||||
auto id = alloc_id(gamepadMask);
|
||||
|
||||
if(id < 0) {
|
||||
// Out of gamepads
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(platf::alloc_gamepad(platf_input, id, rumble_queue)) {
|
||||
free_id(gamepadMask, id);
|
||||
// allocating a gamepad failed: solution: ignore gamepads
|
||||
// The implementations of platf::alloc_gamepad already has logging
|
||||
return -1;
|
||||
}
|
||||
|
||||
gamepad.id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
|
||||
if(updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask, input->rumble_queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
input->active_gamepad_state = packet->activeGamepadMask;
|
||||
|
||||
if(packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) {
|
||||
BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!((input->active_gamepad_state >> packet->controllerNumber) & 1)) {
|
||||
BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto &gamepad = input->gamepads[packet->controllerNumber];
|
||||
|
||||
// If this gamepad has not been initialized, ignore it.
|
||||
// This could happen when platf::alloc_gamepad fails
|
||||
if(gamepad.id < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::uint16_t bf = packet->buttonFlags;
|
||||
platf::gamepad_state_t gamepad_state {
|
||||
bf,
|
||||
packet->leftTrigger,
|
||||
packet->rightTrigger,
|
||||
packet->leftStickX,
|
||||
packet->leftStickY,
|
||||
packet->rightStickX,
|
||||
packet->rightStickY
|
||||
};
|
||||
|
||||
auto bf_new = gamepad_state.buttonFlags;
|
||||
switch(gamepad.back_button_state) {
|
||||
case button_state_e::UP:
|
||||
if(!(platf::BACK & bf_new)) {
|
||||
gamepad.back_button_state = button_state_e::NONE;
|
||||
}
|
||||
gamepad_state.buttonFlags &= ~platf::BACK;
|
||||
break;
|
||||
case button_state_e::DOWN:
|
||||
if(platf::BACK & bf_new) {
|
||||
gamepad.back_button_state = button_state_e::NONE;
|
||||
}
|
||||
gamepad_state.buttonFlags |= platf::BACK;
|
||||
break;
|
||||
case button_state_e::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags;
|
||||
bf_new = gamepad_state.buttonFlags;
|
||||
|
||||
if(platf::BACK & bf) {
|
||||
if(platf::BACK & bf_new) {
|
||||
// Don't emulate home button if timeout < 0
|
||||
if(config::input.back_button_timeout >= 0ms) {
|
||||
auto f = [input, controller = packet->controllerNumber]() {
|
||||
auto &gamepad = input->gamepads[controller];
|
||||
|
||||
auto &state = gamepad.gamepad_state;
|
||||
|
||||
// Force the back button up
|
||||
gamepad.back_button_state = button_state_e::UP;
|
||||
state.buttonFlags &= ~platf::BACK;
|
||||
platf::gamepad(platf_input, gamepad.id, state);
|
||||
|
||||
// Press Home button
|
||||
state.buttonFlags |= platf::HOME;
|
||||
platf::gamepad(platf_input, gamepad.id, state);
|
||||
|
||||
// Release Home button
|
||||
state.buttonFlags &= ~platf::HOME;
|
||||
platf::gamepad(platf_input, gamepad.id, state);
|
||||
|
||||
gamepad.back_timeout_id = nullptr;
|
||||
};
|
||||
|
||||
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
|
||||
}
|
||||
}
|
||||
else if(gamepad.back_timeout_id) {
|
||||
task_pool.cancel(gamepad.back_timeout_id);
|
||||
gamepad.back_timeout_id = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
platf::gamepad(platf_input, gamepad.id, gamepad_state);
|
||||
|
||||
gamepad.gamepad_state = gamepad_state;
|
||||
}
|
||||
|
||||
void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) {
|
||||
void *payload = input_data.data();
|
||||
|
||||
int input_type = util::endian::big(*(int *)payload);
|
||||
|
||||
switch(input_type) {
|
||||
case PACKET_TYPE_REL_MOUSE_MOVE:
|
||||
passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_ABS_MOUSE_MOVE:
|
||||
passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_MOUSE_BUTTON:
|
||||
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
|
||||
break;
|
||||
case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
|
||||
char *tmp_input = (char *)payload + 4;
|
||||
if(tmp_input[0] == 0x0A) {
|
||||
passthrough((PNV_SCROLL_PACKET)payload);
|
||||
}
|
||||
else {
|
||||
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PACKET_TYPE_MULTI_CONTROLLER:
|
||||
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
task_pool.push(passthrough_helper, input, util::cmove(input_data));
|
||||
}
|
||||
|
||||
void reset(std::shared_ptr<input_t> &input) {
|
||||
task_pool.cancel(key_press_repeat_id);
|
||||
task_pool.cancel(input->mouse_left_button_timeout);
|
||||
|
||||
// Ensure input is synchronous, by using the task_pool
|
||||
task_pool.push([]() {
|
||||
for(int x = 0; x < mouse_press.size(); ++x) {
|
||||
if(mouse_press[x]) {
|
||||
platf::button_mouse(platf_input, x, true);
|
||||
mouse_press[x] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &kp : key_press) {
|
||||
platf::keyboard(platf_input, kp.first & 0x00FF, true);
|
||||
key_press[kp.first] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class deinit_t : public platf::deinit_t {
|
||||
public:
|
||||
~deinit_t() override {
|
||||
platf_input.reset();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
|
||||
platf_input = platf::input();
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
std::shared_ptr<input_t> alloc(safe::mail_t mail) {
|
||||
auto input = std::make_shared<input_t>(
|
||||
mail->event<input::touch_port_t>(mail::touch_port),
|
||||
mail->queue<platf::rumble_t>(mail::rumble));
|
||||
|
||||
// Workaround to ensure new frames will be captured when a client connects
|
||||
task_pool.pushDelayed([]() {
|
||||
platf::move_mouse(platf_input, 1, 1);
|
||||
platf::move_mouse(platf_input, -1, -1);
|
||||
},
|
||||
100ms);
|
||||
|
||||
return input;
|
||||
}
|
||||
} // namespace input
|
||||
35
src/input.h
Normal file
35
src/input.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by loki on 6/20/19.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_INPUT_H
|
||||
#define SUNSHINE_INPUT_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "platform/common.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
namespace input {
|
||||
struct input_t;
|
||||
|
||||
void print(void *input);
|
||||
void reset(std::shared_ptr<input_t> &input);
|
||||
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
|
||||
|
||||
|
||||
[[nodiscard]] std::unique_ptr<platf::deinit_t> init();
|
||||
|
||||
std::shared_ptr<input_t> alloc(safe::mail_t mail);
|
||||
|
||||
struct touch_port_t : public platf::touch_port_t {
|
||||
int env_width, env_height;
|
||||
|
||||
// Offset x and y coordinates of the client
|
||||
float client_offsetX, client_offsetY;
|
||||
|
||||
float scalar_inv;
|
||||
};
|
||||
} // namespace input
|
||||
|
||||
#endif //SUNSHINE_INPUT_H
|
||||
343
src/main.cpp
Normal file
343
src/main.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
//
|
||||
// Created by loki on 5/30/19.
|
||||
//
|
||||
|
||||
#include "process.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/log/attributes/clock.hpp>
|
||||
#include <boost/log/common.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/sinks.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "confighttp.h"
|
||||
#include "httpcommon.h"
|
||||
#include "main.h"
|
||||
#include "nvhttp.h"
|
||||
#include "rtsp.h"
|
||||
#include "thread_pool.h"
|
||||
#include "upnp.h"
|
||||
#include "version.h"
|
||||
#include "video.h"
|
||||
|
||||
#include "platform/common.h"
|
||||
extern "C" {
|
||||
#include <libavutil/log.h>
|
||||
#include <rs.h>
|
||||
}
|
||||
|
||||
safe::mail_t mail::man;
|
||||
|
||||
using namespace std::literals;
|
||||
namespace bl = boost::log;
|
||||
|
||||
util::ThreadPool task_pool;
|
||||
bl::sources::severity_logger<int> verbose(0); // Dominating output
|
||||
bl::sources::severity_logger<int> debug(1); // Follow what is happening
|
||||
bl::sources::severity_logger<int> info(2); // Should be informed about
|
||||
bl::sources::severity_logger<int> warning(3); // Strange events
|
||||
bl::sources::severity_logger<int> error(4); // Recoverable errors
|
||||
bl::sources::severity_logger<int> fatal(5); // Unrecoverable errors
|
||||
|
||||
bool display_cursor = true;
|
||||
|
||||
using text_sink = bl::sinks::asynchronous_sink<bl::sinks::text_ostream_backend>;
|
||||
boost::shared_ptr<text_sink> sink;
|
||||
|
||||
struct NoDelete {
|
||||
void operator()(void *) {}
|
||||
};
|
||||
|
||||
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
|
||||
|
||||
/** Print the help to stdout.
|
||||
|
||||
This function prints output to stdout.
|
||||
*/
|
||||
void print_help(const char *name) {
|
||||
std::cout
|
||||
<< "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl
|
||||
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
|
||||
<< std::endl
|
||||
<< " Note: The configuration will be created if it doesn't exist."sv << std::endl
|
||||
<< std::endl
|
||||
<< " --help | print help"sv << std::endl
|
||||
<< " --creds username password | set user credentials for the Web manager"sv << std::endl
|
||||
<< " --version | print the version of sunshine"sv << std::endl
|
||||
<< std::endl
|
||||
<< " flags"sv << std::endl
|
||||
<< " -0 | Read PIN from stdin"sv << std::endl
|
||||
<< " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl
|
||||
<< " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl
|
||||
<< " -2 | Force replacement of headers in video stream"sv << std::endl
|
||||
<< " -p | Enable/Disable UPnP"sv << std::endl
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/** Call the print_help function.
|
||||
|
||||
Calls the print_help function and then exits.
|
||||
*/
|
||||
namespace help {
|
||||
int entry(const char *name, int argc, char *argv[]) {
|
||||
print_help(name);
|
||||
return 0;
|
||||
}
|
||||
} // namespace help
|
||||
|
||||
/** Print the version details to stdout.
|
||||
|
||||
This function prints the version details to stdout and then exits.
|
||||
*/
|
||||
namespace version {
|
||||
int entry(const char *name, int argc, char *argv[]) {
|
||||
std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl;
|
||||
return 0;
|
||||
}
|
||||
} // namespace version
|
||||
|
||||
void log_flush() {
|
||||
sink->flush();
|
||||
}
|
||||
|
||||
std::map<int, std::function<void()>> signal_handlers;
|
||||
void on_signal_forwarder(int sig) {
|
||||
signal_handlers.at(sig)();
|
||||
}
|
||||
|
||||
template<class FN>
|
||||
void on_signal(int sig, FN &&fn) {
|
||||
signal_handlers.emplace(sig, std::forward<FN>(fn));
|
||||
|
||||
std::signal(sig, on_signal_forwarder);
|
||||
}
|
||||
|
||||
namespace gen_creds {
|
||||
int entry(const char *name, int argc, char *argv[]) {
|
||||
if(argc < 2 || argv[0] == "help"sv || argv[1] == "help"sv) {
|
||||
print_help(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
http::save_user_creds(config::sunshine.credentials_file, argv[0], argv[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace gen_creds
|
||||
|
||||
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
|
||||
{ "creds"sv, gen_creds::entry },
|
||||
{ "help"sv, help::entry },
|
||||
{ "version"sv, version::entry }
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
util::TaskPool::task_id_t force_shutdown = nullptr;
|
||||
|
||||
bool shutdown_by_interrupt = false;
|
||||
|
||||
auto exit_guard = util::fail_guard([&shutdown_by_interrupt, &force_shutdown]() {
|
||||
if(!shutdown_by_interrupt) {
|
||||
return;
|
||||
}
|
||||
|
||||
task_pool.cancel(force_shutdown);
|
||||
|
||||
std::cout << "Sunshine exited: Press enter to continue"sv << std::endl;
|
||||
|
||||
std::string _;
|
||||
std::getline(std::cin, _);
|
||||
});
|
||||
|
||||
mail::man = std::make_shared<safe::mail_raw_t>();
|
||||
|
||||
if(config::parse(argc, argv)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(config::sunshine.min_log_level >= 1) {
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
}
|
||||
else {
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
}
|
||||
|
||||
sink = boost::make_shared<text_sink>();
|
||||
|
||||
boost::shared_ptr<std::ostream> stream { &std::cout, NoDelete {} };
|
||||
sink->locked_backend()->add_stream(stream);
|
||||
sink->set_filter(severity >= config::sunshine.min_log_level);
|
||||
|
||||
sink->set_formatter([message = "Message"s, severity = "Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
|
||||
constexpr int DATE_BUFFER_SIZE = 21 + 2 + 1; // Full string plus ": \0"
|
||||
|
||||
auto log_level = view.attribute_values()[severity].extract<int>().get();
|
||||
|
||||
std::string_view log_type;
|
||||
switch(log_level) {
|
||||
case 0:
|
||||
log_type = "Verbose: "sv;
|
||||
break;
|
||||
case 1:
|
||||
log_type = "Debug: "sv;
|
||||
break;
|
||||
case 2:
|
||||
log_type = "Info: "sv;
|
||||
break;
|
||||
case 3:
|
||||
log_type = "Warning: "sv;
|
||||
break;
|
||||
case 4:
|
||||
log_type = "Error: "sv;
|
||||
break;
|
||||
case 5:
|
||||
log_type = "Fatal: "sv;
|
||||
break;
|
||||
};
|
||||
|
||||
char _date[DATE_BUFFER_SIZE];
|
||||
std::time_t t = std::time(nullptr);
|
||||
strftime(_date, DATE_BUFFER_SIZE, "[%Y:%m:%d:%H:%M:%S]: ", std::localtime(&t));
|
||||
|
||||
os << _date << log_type << view.attribute_values()[message].extract<std::string>();
|
||||
});
|
||||
|
||||
// Flush after each log record to ensure log file contents on disk isn't stale.
|
||||
// This is particularly important when running from a Windows service.
|
||||
sink->locked_backend()->auto_flush(true);
|
||||
|
||||
bl::core::get()->add_sink(sink);
|
||||
auto fg = util::fail_guard(log_flush);
|
||||
|
||||
if(!config::sunshine.cmd.name.empty()) {
|
||||
auto fn = cmd_to_func.find(config::sunshine.cmd.name);
|
||||
if(fn == std::end(cmd_to_func)) {
|
||||
BOOST_LOG(fatal) << "Unknown command: "sv << config::sunshine.cmd.name;
|
||||
|
||||
BOOST_LOG(info) << "Possible commands:"sv;
|
||||
for(auto &[key, _] : cmd_to_func) {
|
||||
BOOST_LOG(info) << '\t' << key;
|
||||
}
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
|
||||
}
|
||||
|
||||
task_pool.start(1);
|
||||
|
||||
// Create signal handler after logging has been initialized
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
on_signal(SIGINT, [&shutdown_by_interrupt, &force_shutdown, shutdown_event]() {
|
||||
BOOST_LOG(info) << "Interrupt handler called"sv;
|
||||
|
||||
auto task = []() {
|
||||
BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv;
|
||||
log_flush();
|
||||
std::abort();
|
||||
};
|
||||
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
|
||||
|
||||
shutdown_by_interrupt = true;
|
||||
shutdown_event->raise(true);
|
||||
});
|
||||
|
||||
on_signal(SIGTERM, [&force_shutdown, shutdown_event]() {
|
||||
BOOST_LOG(info) << "Terminate handler called"sv;
|
||||
|
||||
auto task = []() {
|
||||
BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv;
|
||||
log_flush();
|
||||
std::abort();
|
||||
};
|
||||
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
|
||||
|
||||
shutdown_event->raise(true);
|
||||
});
|
||||
|
||||
proc::refresh(config::stream.file_apps);
|
||||
|
||||
auto deinit_guard = platf::init();
|
||||
if(!deinit_guard) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
reed_solomon_init();
|
||||
auto input_deinit_guard = input::init();
|
||||
if(video::init()) {
|
||||
return 2;
|
||||
}
|
||||
if(http::init()) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::unique_ptr<platf::deinit_t> mDNS;
|
||||
auto sync_mDNS = std::async(std::launch::async, [&mDNS]() {
|
||||
mDNS = platf::publish::start();
|
||||
});
|
||||
|
||||
std::unique_ptr<platf::deinit_t> upnp_unmap;
|
||||
auto sync_upnp = std::async(std::launch::async, [&upnp_unmap]() {
|
||||
upnp_unmap = upnp::start();
|
||||
});
|
||||
|
||||
//FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced
|
||||
if(shutdown_event->peek()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::thread httpThread { nvhttp::start };
|
||||
std::thread configThread { confighttp::start };
|
||||
|
||||
stream::rtpThread();
|
||||
|
||||
httpThread.join();
|
||||
configThread.join();
|
||||
|
||||
task_pool.stop();
|
||||
task_pool.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string read_file(const char *path) {
|
||||
if(!std::filesystem::exists(path)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::ifstream in(path);
|
||||
|
||||
std::string input;
|
||||
std::string base64_cert;
|
||||
|
||||
while(!in.eof()) {
|
||||
std::getline(in, input);
|
||||
base64_cert += input + '\n';
|
||||
}
|
||||
|
||||
return base64_cert;
|
||||
}
|
||||
|
||||
int write_file(const char *path, const std::string_view &contents) {
|
||||
std::ofstream out(path);
|
||||
|
||||
if(!out.is_open()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
out << contents;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint16_t map_port(int port) {
|
||||
return (std::uint16_t)((int)config::sunshine.port + port);
|
||||
}
|
||||
@@ -5,8 +5,12 @@
|
||||
#ifndef SUNSHINE_MAIN_H
|
||||
#define SUNSHINE_MAIN_H
|
||||
|
||||
#include <boost/log/common.hpp>
|
||||
#include <string_view>
|
||||
|
||||
#include "thread_pool.h"
|
||||
#include "thread_safe.h"
|
||||
|
||||
#include <boost/log/common.hpp>
|
||||
|
||||
extern util::ThreadPool task_pool;
|
||||
extern bool display_cursor;
|
||||
@@ -19,4 +23,35 @@ extern boost::log::sources::severity_logger<int> error;
|
||||
extern boost::log::sources::severity_logger<int> fatal;
|
||||
|
||||
void log_flush();
|
||||
|
||||
void print_help(const char *name);
|
||||
|
||||
std::string read_file(const char *path);
|
||||
int write_file(const char *path, const std::string_view &contents);
|
||||
|
||||
std::uint16_t map_port(int port);
|
||||
|
||||
namespace mail {
|
||||
#define MAIL(x) \
|
||||
constexpr auto x = std::string_view { #x }
|
||||
|
||||
extern safe::mail_t man;
|
||||
|
||||
// Global mail
|
||||
MAIL(shutdown);
|
||||
MAIL(broadcast_shutdown);
|
||||
|
||||
MAIL(video_packets);
|
||||
MAIL(audio_packets);
|
||||
|
||||
MAIL(switch_display);
|
||||
|
||||
// Local mail
|
||||
MAIL(touch_port);
|
||||
MAIL(idr);
|
||||
MAIL(rumble);
|
||||
#undef MAIL
|
||||
} // namespace mail
|
||||
|
||||
|
||||
#endif //SUNSHINE_MAIN_H
|
||||
@@ -11,23 +11,24 @@ template<class T>
|
||||
class MoveByCopy {
|
||||
public:
|
||||
typedef T move_type;
|
||||
|
||||
private:
|
||||
move_type _to_move;
|
||||
public:
|
||||
|
||||
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) { }
|
||||
public:
|
||||
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {}
|
||||
|
||||
MoveByCopy(MoveByCopy &&other) = default;
|
||||
|
||||
|
||||
MoveByCopy(const MoveByCopy &other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
MoveByCopy& operator=(MoveByCopy &&other) = default;
|
||||
|
||||
MoveByCopy& operator=(const MoveByCopy &other) {
|
||||
this->_to_move = std::move(const_cast<MoveByCopy&>(other)._to_move);
|
||||
|
||||
MoveByCopy &operator=(MoveByCopy &&other) = default;
|
||||
|
||||
MoveByCopy &operator=(const MoveByCopy &other) {
|
||||
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ MoveByCopy<T> cmove(T &movable) {
|
||||
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
|
||||
template<class T>
|
||||
MoveByCopy<T> const_cmove(const T &movable) {
|
||||
return MoveByCopy<T>(std::move(const_cast<T&>(movable)));
|
||||
}
|
||||
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
|
||||
}
|
||||
} // namespace util
|
||||
#endif
|
||||
@@ -2,12 +2,12 @@
|
||||
// Created by loki on 12/27/19.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include "network.h"
|
||||
#include "utility.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace net {
|
||||
using namespace std::literals;
|
||||
namespace net {
|
||||
// In the format "xxx.xxx.xxx.xxx/x"
|
||||
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip);
|
||||
|
||||
@@ -16,22 +16,22 @@ std::vector<std::pair<std::uint32_t, std::uint32_t>> pc_ips {
|
||||
};
|
||||
std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips {
|
||||
ip_block("192.168.0.0/16"sv),
|
||||
ip_block("172.16.0.0/12"),
|
||||
ip_block("172.16.0.0/12"sv),
|
||||
ip_block("10.0.0.0/8"sv)
|
||||
};
|
||||
|
||||
std::uint32_t ip(const std::string_view &ip_str) {
|
||||
auto begin = std::begin(ip_str);
|
||||
auto end = std::end(ip_str);
|
||||
auto begin = std::begin(ip_str);
|
||||
auto end = std::end(ip_str);
|
||||
auto temp_end = std::find(begin, end, '.');
|
||||
|
||||
std::uint32_t ip = 0;
|
||||
auto shift = 24;
|
||||
auto shift = 24;
|
||||
while(temp_end != end) {
|
||||
ip += (util::from_chars(begin, temp_end) << shift);
|
||||
shift -= 8;
|
||||
|
||||
begin = temp_end + 1;
|
||||
begin = temp_end + 1;
|
||||
temp_end = std::find(begin, end, '.');
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ std::uint32_t ip(const std::string_view &ip_str) {
|
||||
// In the format "xxx.xxx.xxx.xxx/x"
|
||||
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip_str) {
|
||||
auto begin = std::begin(ip_str);
|
||||
auto end = std::find(begin, std::end(ip_str), '/');
|
||||
auto end = std::find(begin, std::end(ip_str), '/');
|
||||
|
||||
auto addr = ip({ begin, (std::size_t)(end - begin) });
|
||||
|
||||
@@ -82,15 +82,34 @@ net_e from_address(const std::string_view &view) {
|
||||
|
||||
std::string_view to_enum_string(net_e net) {
|
||||
switch(net) {
|
||||
case PC:
|
||||
return "pc"sv;
|
||||
case LAN:
|
||||
return "lan"sv;
|
||||
case WAN:
|
||||
return "wan"sv;
|
||||
case PC:
|
||||
return "pc"sv;
|
||||
case LAN:
|
||||
return "lan"sv;
|
||||
case WAN:
|
||||
return "wan"sv;
|
||||
}
|
||||
|
||||
// avoid warning
|
||||
return "wan"sv;
|
||||
}
|
||||
}
|
||||
|
||||
host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port) {
|
||||
enet_address_set_host(&addr, "0.0.0.0");
|
||||
enet_address_set_port(&addr, port);
|
||||
|
||||
return host_t { enet_host_create(AF_INET, &addr, peers, 1, 0, 0) };
|
||||
}
|
||||
|
||||
void free_host(ENetHost *host) {
|
||||
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
|
||||
ENetPeer *peer = &peer_ref;
|
||||
|
||||
if(peer) {
|
||||
enet_peer_disconnect_now(peer, 0);
|
||||
}
|
||||
});
|
||||
|
||||
enet_host_destroy(host);
|
||||
}
|
||||
} // namespace net
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user