mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
861 Commits
Author | SHA1 | Date | |
---|---|---|---|
aed0234d47 | |||
07ebbe56e6 | |||
95bbeb103e | |||
bfb5570b7a | |||
1e19cf7ccc | |||
0d8999d64f | |||
91fde777cf | |||
061ab12b10 | |||
c516e87180 | |||
de6bfdca0a | |||
788f70d5f3 | |||
b0a35789f6 | |||
eb7fbf011f | |||
50bb6e7204 | |||
15610bb296 | |||
ae0ffdc4d5 | |||
033a6929c3 | |||
7c8292a88d | |||
ab5dad7d91 | |||
0744d215c6 | |||
149ddfa8f8 | |||
3830870a83 | |||
b94be080f6 | |||
83382d8dcf | |||
f815228677 | |||
d7d8d23de7 | |||
5834c6ed54 | |||
b0512b3ba7 | |||
d3a7a6ef55 | |||
99fad5bc83 | |||
a54171e1cd | |||
b18d597bdc | |||
9125dc4cce | |||
b0e3ea8765 | |||
7c457546f1 | |||
83bef1a840 | |||
b4d89d5666 | |||
3ba8a77d9a | |||
82c31dc47a | |||
7fce694577 | |||
e352d9750d | |||
3d9eac8e6d | |||
6c85b33539 | |||
d979ada4fe | |||
faeeae17d1 | |||
82d1a2b4f9 | |||
cac3496145 | |||
53bfa077ae | |||
0b9366fa28 | |||
c95b709f39 | |||
5745da5466 | |||
ef853e34d6 | |||
7177913d9b | |||
86b6ddb625 | |||
53b9a46feb | |||
d46c1f8687 | |||
c92477b457 | |||
059244bb32 | |||
f10c0d2558 | |||
f5ae89500f | |||
2e3a24d32e | |||
f955d81c5e | |||
4f1fa3bc32 | |||
8305d907e3 | |||
ab73d61178 | |||
8f88d1b7c4 | |||
c5cdbc48a9 | |||
fe00f31147 | |||
79ddb43ac3 | |||
89f95f6459 | |||
87c13b6f26 | |||
d16ea653fe | |||
51dc7fceb0 | |||
bc1c6f7b9a | |||
3fdf77d29b | |||
0b97502029 | |||
cba2bf5cb8 | |||
d8b37328a1 | |||
bb9cf83744 | |||
56fc6ae47c | |||
0cf17cf3db | |||
1bd3297055 | |||
6a10662bc4 | |||
2d84ad8963 | |||
1ec69cbc0d | |||
0057744e57 | |||
45892b4347 | |||
3d41e8882a | |||
436b8216ec | |||
03ba2a680e | |||
2821962d12 | |||
43d38a2e63 | |||
ea28d217e0 | |||
89fe6d57c9 | |||
1d0e1143bf | |||
1a99353559 | |||
ad97d04e37 | |||
25c3abe873 | |||
8eeea50ede | |||
0ded11c6a2 | |||
b6c542212d | |||
182bde4884 | |||
eb84f677cf | |||
3ecb637e70 | |||
bfc826b898 | |||
efc6d4c15a | |||
a96da35f11 | |||
2bff3798af | |||
0fb50a3273 | |||
1c17eee125 | |||
14a43a9cd2 | |||
7bbc7366f2 | |||
879bedd781 | |||
8b79bfab4f | |||
f022e09938 | |||
a63b6a0e3d | |||
dd1facec06 | |||
a46b5716be | |||
e96b7bfd62 | |||
a60c1c1365 | |||
4c39fcd614 | |||
e6b341f2a0 | |||
bdf9e76a6b | |||
e0fc395fbe | |||
107c03653d | |||
a5058cf951 | |||
ed2726c9d2 | |||
6b31ff7e79 | |||
86d3925bf4 | |||
8c2cde4c13 | |||
2d6e34ee6d | |||
adb5577b01 | |||
84ef3f223d | |||
fa50008f3d | |||
6a5537f90f | |||
e0d33a9683 | |||
4092c9a473 | |||
fcaac3d515 | |||
e86d1aad02 | |||
2c94b4c332 | |||
2b6933f6e2 | |||
2e2a7f073e | |||
515c7852ba | |||
5577277944 | |||
ed9e4ff168 | |||
d10915c7a8 | |||
40763fc15d | |||
88d3271c40 | |||
21940c922d | |||
93adc8587d | |||
1907f8d1d1 | |||
ca8848827e | |||
3a7473ba91 | |||
a99da27ff6 | |||
c476649f3e | |||
062473d6f4 | |||
d0ed43a8e5 | |||
701098d19d | |||
c33a0ab439 | |||
b8af848d3a | |||
3dd7fe5d4a | |||
81ab199308 | |||
92938d2c35 | |||
c914632663 | |||
c95104fada | |||
2209fe30e2 | |||
979e0ddc88 | |||
9fab714674 | |||
2eb6463ab0 | |||
5e4d6fbd3f | |||
30df5f3558 | |||
252dd7e09f | |||
2915690d22 | |||
1993596232 | |||
8946e8997f | |||
dfeb156da9 | |||
c10c35e38e | |||
67bfdde113 | |||
c62178a7fe | |||
c12a4323e7 | |||
fd1c4abad7 | |||
bd4fe5a1a7 | |||
190f5ebfc4 | |||
79493df32e | |||
71d523437e | |||
268f503875 | |||
5ac47b823f | |||
ec572da822 | |||
37f96b8cf2 | |||
545db60b98 | |||
f65fdcdbc1 | |||
579acabe5e | |||
f2a9ff8551 | |||
2e40e07563 | |||
cb3af837c6 | |||
3cce89ea3f | |||
5f2dfb24ca | |||
4bc02a7389 | |||
eeeca8afd0 | |||
4f607df5fc | |||
e06b3e2052 | |||
b14b18e4be | |||
fb616b4b53 | |||
a1c5bc00bc | |||
0921632f87 | |||
38ad465233 | |||
881de01073 | |||
2d973e0b90 | |||
e9b43b9b2a | |||
b9193989b0 | |||
be6d9791f5 | |||
baac1b1d80 | |||
40e2d410dd | |||
11617eea99 | |||
5748c4d0ec | |||
2e5b903eae | |||
9405b17e14 | |||
7d1f0e2a69 | |||
c245c24752 | |||
5b0410a207 | |||
62e4c42aa6 | |||
fe1b14ff57 | |||
c074088f2f | |||
0391280c13 | |||
4f98b9e41b | |||
a8de9ac472 | |||
ec7f3fa057 | |||
5cfefd8b15 | |||
7e6b53311d | |||
16bc68f2f9 | |||
974d6b48b2 | |||
db2b1e2dc7 | |||
98656b5d2f | |||
3c7570d814 | |||
fc3e30a826 | |||
2f0476b43c | |||
e310c7d744 | |||
8a649b89c8 | |||
95f52987ef | |||
82b8c66d51 | |||
83fb1843ee | |||
58b10069ab | |||
9cda739672 | |||
276bba050b | |||
147e49b362 | |||
bc338c1f4a | |||
e8fbb18a71 | |||
682c349831 | |||
14b311f6ab | |||
8a40f355c8 | |||
ae2f90c5c2 | |||
7aff773e2c | |||
0cdfa6e7f6 | |||
fc637eef6d | |||
b4c3141170 | |||
41257f7af5 | |||
b204f3ec90 | |||
b4926b3535 | |||
77d63e9848 | |||
94dcd58c2e | |||
689ce16f1b | |||
edc64b8336 | |||
03db5bfd8d | |||
9ad340ab02 | |||
962e26040d | |||
b07884c1da | |||
bad0fb62ed | |||
28e522ce97 | |||
c206cdfa4c | |||
c31ec077f5 | |||
afc6a60746 | |||
0a495b67e6 | |||
37ea7073d5 | |||
feaeef5a70 | |||
5586670d38 | |||
65a9139ffe | |||
3d76f078cd | |||
a1de1b11bc | |||
b5a3e3a4f0 | |||
d7b0ad4916 | |||
2a80a6b217 | |||
9df72f54d5 | |||
ef75ca36c9 | |||
84c0b698f5 | |||
3d9a4ef1e6 | |||
cf51fdceef | |||
85a52ab01f | |||
f2792c0b40 | |||
97109087a4 | |||
953f07f21f | |||
03f1035690 | |||
a561c60f7e | |||
7963b328f9 | |||
3b7e216409 | |||
9ae43a6f75 | |||
6b86de1fb9 | |||
4e6d4ac437 | |||
3b197651cc | |||
c675f1c20c | |||
e87955d59a | |||
c718d8d803 | |||
454a68d1b7 | |||
45f6f74cbe | |||
cb7412a457 | |||
a9fc82365c | |||
edc0fed9e2 | |||
1c718963ea | |||
cd40ccdb9d | |||
80e1dc2446 | |||
d03c4cc4b9 | |||
7bd434e755 | |||
640bcb90c6 | |||
08e86a1378 | |||
fefcc3937a | |||
25210dfa52 | |||
56fbb7b9cf | |||
c55d33b956 | |||
0c5bce101e | |||
0a50e2a95c | |||
5ad940924b | |||
6eb49930bf | |||
3a0d5fb190 | |||
8c2eee2e25 | |||
2e19c5f08a | |||
548b53437f | |||
e1ca9a8029 | |||
389ef2aea9 | |||
357691bad5 | |||
beadb56b05 | |||
745fb5ea29 | |||
58434cac3b | |||
c34dff17ff | |||
73f00ca195 | |||
40503b4212 | |||
4ada388828 | |||
e2d261259d | |||
677e393f47 | |||
e5dae8f5a7 | |||
650de119a4 | |||
6d7975afeb | |||
811e3f8cc8 | |||
4f3be34db1 | |||
b160fd2320 | |||
002bf08b97 | |||
a6bab968f5 | |||
408b336057 | |||
7e5716185e | |||
6d47bd6477 | |||
989c8b1857 | |||
06e9e0afab | |||
d75523c111 | |||
dacec803c6 | |||
0ae4416242 | |||
079d7eb6ca | |||
b764b4d0e5 | |||
2c2d61d908 | |||
6a323b9371 | |||
d76ff16162 | |||
952f3a586a | |||
3ecb9702ef | |||
563936ea08 | |||
13cd2704ae | |||
ae83d9dca1 | |||
66a059ecdf | |||
53936a4f76 | |||
488a095fdf | |||
1878de9f6b | |||
2a2f3441ef | |||
53c939a97f | |||
8d7ed32fc2 | |||
a1fa5dc3cb | |||
4b9a84ccee | |||
650957b6d1 | |||
6e21d041fe | |||
e2b70fd795 | |||
2d49bcc437 | |||
8b3dce3256 | |||
8cf6a1e72c | |||
a3ce7197ac | |||
5da1e55f8e | |||
661fd4efa8 | |||
047e6e11bf | |||
db8bc1af2d | |||
1386e4f91c | |||
11e2717809 | |||
57bc0a2b95 | |||
8efb30c4ee | |||
969feefcc5 | |||
db1527ab9b | |||
e88a60fa50 | |||
7ab3e38da7 | |||
7b0b836809 | |||
c8e7cfcd7b | |||
c516c89c97 | |||
c2937d7857 | |||
fe644cfa82 | |||
e84e5388a5 | |||
5e11d0ea13 | |||
a3ecb6e0e9 | |||
1d30f13fd8 | |||
b9ae28483c | |||
6a6d316e09 | |||
22d2ee6bbd | |||
68e55613fc | |||
1d44d3a109 | |||
11a641cc0d | |||
7570fbf7a4 | |||
67f7e28867 | |||
9b2d30b4d5 | |||
399815525f | |||
191cfa08d5 | |||
fcf1fa8aff | |||
73302cb060 | |||
70a8533f5e | |||
fe724db12d | |||
096a8ec6ba | |||
cc5f9c9aa7 | |||
3e14b41600 | |||
ccc2bd8667 | |||
45f86b17f1 | |||
90aebecc5b | |||
b0170ef671 | |||
d418b4e624 | |||
47505547af | |||
de2e73c9d4 | |||
ab1a43163b | |||
2556dd320f | |||
2120381c0e | |||
dc06900f7f | |||
7571dd5042 | |||
d6caa872b8 | |||
2e9829ddd1 | |||
9621ea90f7 | |||
f58da1254b | |||
a6ba7f8feb | |||
9c89f12275 | |||
41181f7260 | |||
7251862ecf | |||
c0512d5b5f | |||
b9731dff21 | |||
3981abc5ca | |||
6aef437f58 | |||
9b7752896a | |||
d074027610 | |||
b137148186 | |||
e1f6260034 | |||
b24cbaa904 | |||
cc0b110d61 | |||
82a1e7d253 | |||
87b47689eb | |||
c3346f9192 | |||
3b0cf02574 | |||
4404889019 | |||
4e642d97fc | |||
44bd25646d | |||
09b04037a0 | |||
3c50e1e4d8 | |||
179ba9a9e6 | |||
9ce2b1983a | |||
37dde278cb | |||
1cc57e7d1d | |||
60a5cdb154 | |||
f290cbc148 | |||
2e2e9b1332 | |||
7915e3815b | |||
beb34deed8 | |||
a263d8a364 | |||
1eccf4c899 | |||
d8d19d839a | |||
17e573d67f | |||
59de83b45d | |||
712bd6e8f3 | |||
75b21d070a | |||
e91c9e7bf4 | |||
2562272775 | |||
819621e307 | |||
4b9229311c | |||
4c4c1de190 | |||
6b6b7feff2 | |||
9e3ce26ba0 | |||
eac19a7c83 | |||
ee1486274d | |||
8692e2afda | |||
f995268444 | |||
bd2e974718 | |||
d1c64c7509 | |||
30a19cfe8a | |||
cc3097b09a | |||
c00681b52d | |||
61931c179f | |||
c7190cb94a | |||
a1bd27dfde | |||
6ac3a3de57 | |||
e198ed7a9c | |||
4b89464349 | |||
dba599c127 | |||
586f9076f0 | |||
add39cfc5f | |||
e3ad148c15 | |||
acffacd67d | |||
54a16f4d2f | |||
35cb506d6e | |||
7675a1ee58 | |||
863f5e28b3 | |||
1a6f6096e4 | |||
b57982b6cb | |||
4132356141 | |||
e560e95310 | |||
65f4ff6a30 | |||
ab38f33530 | |||
1f04ee8252 | |||
739bd6f696 | |||
066145ab31 | |||
54d0f764d4 | |||
1398689ebf | |||
647625628d | |||
e17df69b80 | |||
f001d18eea | |||
f49980350a | |||
d0a90ecea4 | |||
4506b46ba0 | |||
a8c36d7366 | |||
781eaec683 | |||
bdee8f318a | |||
82dac4d208 | |||
386e1f3b46 | |||
a3cbbf3ea9 | |||
e647debcfb | |||
fba9bd2b76 | |||
56217e0176 | |||
77c5801c4c | |||
7db879cca9 | |||
8d0cb2e875 | |||
dc7b386295 | |||
03c8c6385c | |||
841de2f752 | |||
9de6e229d9 | |||
8dfe471ef2 | |||
da05f315be | |||
d014a7ab25 | |||
d0118c63c0 | |||
9ddfad99ea | |||
82d74fcb2f | |||
0f4f029a76 | |||
2c581e9998 | |||
f2028c506a | |||
200db77140 | |||
007604cc21 | |||
89486e9172 | |||
a60ca96fd1 | |||
0ade2c869d | |||
9986382850 | |||
89b9165c25 | |||
5d290165a2 | |||
d267171cc7 | |||
cebe325e81 | |||
4e6178881b | |||
6bca8194cb | |||
a4885f332d | |||
cf97fb2051 | |||
20a8853854 | |||
dd487a5055 | |||
72ca07f571 | |||
c7305889c3 | |||
bc24497760 | |||
200e65c730 | |||
b32d8ef1e7 | |||
8cfc613a3d | |||
5fbc2cbac7 | |||
9d7a32aa1f | |||
bcc9ef82d1 | |||
d6b28191e8 | |||
acd8a9ea69 | |||
d4b8a8ff9e | |||
a698f7bdf5 | |||
16f07ff0c3 | |||
ed4526751e | |||
e241518f8d | |||
a6677edbf5 | |||
5748e163f8 | |||
85fa6b60e2 | |||
f0bb941c47 | |||
b7df1e8596 | |||
ff8eda3682 | |||
8c4bd91c78 | |||
07c868edcd | |||
69061869a7 | |||
98b2ac9bdc | |||
ef2bbff4e5 | |||
7bb8e56072 | |||
df0926f6fd | |||
75572279a9 | |||
328c6a0f7c | |||
ecd5062975 | |||
c01ae5610d | |||
564e7f31c3 | |||
efa61952b3 | |||
ae27a26ebe | |||
d81076f720 | |||
528f16c5e1 | |||
13ac8bfa90 | |||
935c58b17b | |||
31e133bf06 | |||
eb7fb6694c | |||
a298577da1 | |||
8cd6d43ec4 | |||
b8fd143cc1 | |||
045ae058a3 | |||
52c9816db2 | |||
32440580f6 | |||
906d986f4b | |||
0198651ca0 | |||
d911c40897 | |||
ca8a117335 | |||
f75e2f0ada | |||
ea12b62235 | |||
0af4af7e50 | |||
f017491f44 | |||
ed433d3312 | |||
9ba2e83857 | |||
af0ec74704 | |||
251402e844 | |||
1a5318ebe0 | |||
15aac3f436 | |||
5f373632e6 | |||
0d518bf8df | |||
16b735c3a4 | |||
1239f094fd | |||
6986fa51eb | |||
f4bc88bf73 | |||
ad915536a3 | |||
f48b31664e | |||
d6c0eb1998 | |||
d3dcfba463 | |||
3619dcd777 | |||
86c002e318 | |||
42a807ce9c | |||
9bfff9539e | |||
edf4d919fe | |||
b0fb3d8c61 | |||
5143090142 | |||
bc1555ca5b | |||
146a392f1a | |||
e2fc205a35 | |||
d4bdb216d2 | |||
bffc1d6a1f | |||
df0d890c11 | |||
dceee4dede | |||
dedbbf12bb | |||
bdb53d07f5 | |||
547991df72 | |||
fd17557a96 | |||
a9e928a799 | |||
84b3e086e6 | |||
5e3c794aec | |||
98acbb2b2b | |||
318f0753b0 | |||
8908adbdee | |||
fad7a26550 | |||
38005fc0ef | |||
e3d3181ad6 | |||
83d069e4d9 | |||
f2db5a8229 | |||
6ebb777256 | |||
db50c531d9 | |||
218f7af2e3 | |||
a19d2956c1 | |||
7a6b5b965d | |||
8a871aaddd | |||
bec0a0df10 | |||
1068a2dfbe | |||
b62be5d584 | |||
9686019693 | |||
32a066b4d4 | |||
d5a415e01e | |||
33f83b8486 | |||
adedf8d34f | |||
9243071f03 | |||
36050850d4 | |||
c6d5cc555a | |||
b271aa2aaa | |||
d24961b3b9 | |||
e52543efe7 | |||
0b0e27ad3b | |||
9f460f6c99 | |||
c4529bb9a3 | |||
5a069d2ffc | |||
4fc78e455a | |||
75e2198315 | |||
0d4bb4ed2d | |||
577738ad0a | |||
a8dfdb03fb | |||
aebd470d49 | |||
ce3dfd9678 | |||
b82801b145 | |||
a6ab35f1fb | |||
a265cea4fb | |||
166760651e | |||
41a9191223 | |||
1465e7760e | |||
1a043f1dc0 | |||
70c5b00c90 | |||
bb5177dc79 | |||
f60a9a632b | |||
024c47132b | |||
73ce9cb4cc | |||
f45d439094 | |||
2d5749a78c | |||
f078a10028 | |||
701f88e532 | |||
1524a1997d | |||
b16f812f91 | |||
cde307d644 | |||
c47f6d093c | |||
f5336938c9 | |||
4985548edf | |||
8bc4841e84 | |||
634f9833a1 | |||
80052e62ee | |||
0672b6a157 | |||
1e71bfa151 | |||
d4c2c8197a | |||
c9baed2df9 | |||
1e2065c84a | |||
537d683dfb | |||
e6f1b9f8c8 | |||
5815d2bfce | |||
7692e93306 | |||
8768b83251 | |||
35d18810fc | |||
ccd40a1834 | |||
5c1f0c3541 | |||
e237e52ae4 | |||
effec90528 | |||
de4a2247c0 | |||
b20b7cbc98 | |||
3c347b9b77 | |||
794c4066d8 | |||
8c8e3072a7 | |||
228708eec4 | |||
dcafaedaa8 | |||
a85297f2c8 | |||
91f7450cbd | |||
e7b074991d | |||
5f83ce2a28 | |||
44af896dee | |||
9eaa9a98ee | |||
629880cd58 | |||
42266ed14f | |||
9dd7a197e4 | |||
6f39da20a9 | |||
1ebb6f9257 | |||
e30e982ef0 | |||
42a80c6185 | |||
0d495f4d65 | |||
71f5e66bd1 | |||
a6f71b68f3 | |||
a527f343cd | |||
f8e1c454d9 | |||
da6e55f09f | |||
d277e2788a | |||
53ba747b78 | |||
b9862acd1d | |||
d1c19382a1 | |||
4141983ccd | |||
2b8b5001e0 | |||
532126561e | |||
3c026c4eef | |||
ae6c15158f | |||
6aa57247f9 | |||
50862ac945 | |||
01ce6faa20 | |||
6e571aef95 | |||
d6091c83e2 | |||
5ca55fc13e | |||
2b60da02e8 | |||
f02297152c | |||
af7c0bbc72 | |||
40727d5a6c | |||
9bdd73dbc6 | |||
3e1ed09f12 | |||
3682c625d7 | |||
70cd52ac95 | |||
3264da4b2f | |||
06c1648f55 | |||
94c3e5d41d | |||
7ae20d8c3b | |||
0ff4411abf | |||
3d68b7c9e5 | |||
f8b1cae0c9 | |||
5bee0fc453 | |||
7162765824 | |||
99aa6be887 | |||
afaaff298f | |||
68cf9af6a3 | |||
8d5752d6b5 | |||
98dbcc4c69 | |||
687d8fd120 | |||
114ace23f7 | |||
3fbc221db0 | |||
f2e6e137a6 | |||
bb232f1b3e | |||
9dc153bc22 | |||
b9bbdf5978 | |||
e0db0e394d | |||
3e9001721e | |||
bbf3b0f86f | |||
7f3eb9f11b | |||
c783c51915 | |||
0816d222c6 | |||
af16c70143 | |||
748b6f6e2e | |||
3bd600ed52 | |||
78e6ad0f59 | |||
2d71dc68f1 | |||
7dc2a00d47 | |||
7ece78a05b | |||
ff2bace1bf | |||
e63ce01bdc | |||
4439ce71d0 | |||
27ec094775 | |||
1bed53e07b | |||
8ae06c0c4f | |||
372db7865b | |||
cfeffc551e | |||
d519815a7e | |||
32932eacf5 | |||
1a057cf5a3 | |||
e3936dad9b | |||
4ec9b85506 | |||
fbae081c33 | |||
cfb490b7e3 | |||
1947a3cfd5 | |||
fca6774d4a | |||
5fd1e978ec | |||
4985c549dd | |||
e6c378aef0 | |||
0657835ba3 | |||
36934de04a | |||
e735ab80b1 | |||
9904ca89d4 | |||
554ab54690 | |||
5abcd18ba0 | |||
03bf41b250 | |||
0df6bde5cb | |||
e3a28b640f | |||
94576fae44 | |||
d091d9c3e4 | |||
01c9e48f4f | |||
5639c18c2f | |||
25b835f4d8 | |||
78fb5b3093 | |||
73b85ef9df | |||
93c604a639 | |||
053676a320 | |||
d1a142cefd | |||
36cf2a4126 | |||
a3bc9ae8d0 | |||
6d77ae24b3 | |||
d9bbed5f69 | |||
d6ce405e71 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[*]
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{c,cpp,h,hpp}]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
22
.github/CONTRIBUTING.md
vendored
Normal file
22
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
## Contributing to obs-websocket
|
||||||
|
|
||||||
|
### Translating obs-websocket to your language
|
||||||
|
Localization happens on Crowdin: https://crowdin.com/project/obs-websocket
|
||||||
|
|
||||||
|
### Writing code for obs-websocket
|
||||||
|
#### Coding Guidelines
|
||||||
|
- Function and variable names: snake_case for C names, CamelCase for C++ names
|
||||||
|
- Tabs are 8 columns wide
|
||||||
|
- 80 columns max.
|
||||||
|
|
||||||
|
#### Commit Guidelines
|
||||||
|
- Commits follow the 50/72 standard:
|
||||||
|
- 50 characters max for the title
|
||||||
|
- One empty line after the title
|
||||||
|
- Description wrapped to 72 columns max per line.
|
||||||
|
- Commit titles:
|
||||||
|
- Use present tense
|
||||||
|
- Prefix the title with a "scope" name
|
||||||
|
- e.g: "CI: fix wrong behaviour when packaging for OS X"
|
||||||
|
- Typical scopes: CI, General, Request, Event, Server
|
||||||
|
- Look at existing commits for more examples
|
16
.github/ISSUE_TEMPLATE.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
##### Issue type
|
||||||
|
Bug report? Feature request? Other?
|
||||||
|
|
||||||
|
##### Description
|
||||||
|
*Replace this with a description of the bug encountered or feature requested.*
|
||||||
|
|
||||||
|
##### Steps to reproduce and other useful info
|
||||||
|
*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section.*
|
||||||
|
|
||||||
|
##### Technical information
|
||||||
|
- **Operating System** :
|
||||||
|
- **OBS Studio version** :
|
||||||
|
|
||||||
|
##### Development Environment
|
||||||
|
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
|
||||||
|
Remove this section if it doesn't apply to your case.*
|
BIN
.github/images/mediaunit_logo_black.png
vendored
Normal file
BIN
.github/images/mediaunit_logo_black.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
.github/images/supportclass_logo_blacktext.png
vendored
Normal file
BIN
.github/images/supportclass_logo_blacktext.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,3 +1,9 @@
|
|||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
/build/
|
/build/
|
||||||
/build32/
|
/build32/
|
||||||
/build64/
|
/build64/
|
||||||
|
/release/
|
||||||
|
/installer/Output/
|
||||||
|
|
||||||
|
.vscode
|
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,3 +1,6 @@
|
|||||||
[submodule "deps/mbedtls"]
|
[submodule "deps/websocketpp"]
|
||||||
path = deps/mbedtls
|
path = deps/websocketpp
|
||||||
url = https://github.com/ARMmbed/mbedtls
|
url = https://github.com/zaphoyd/websocketpp.git
|
||||||
|
[submodule "deps/asio"]
|
||||||
|
path = deps/asio
|
||||||
|
url = https://github.com/chriskohlhoff/asio.git
|
||||||
|
49
.travis.yml
Normal file
49
.travis.yml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
language: cpp
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
# AWS key ID
|
||||||
|
- secure: pAiNUGVbjP12BfnWPk0FFTkbnk4Tocvv88XiT3rzRqkQaD7/iyEogLBfHM4nOEgFiIMHbC41aE83w5JgRNPwn6mTgoQBOglzqq1tGuXfqPyV2VStk8beji1evubGoVjjPaoPTFyIdQc5GGxdHyogI/ed9Hb3ccyykYvjyolj9XoCiW42QHx60AHGwl+So+dEa8xydj9SLRPlZ/AitmI/cPVN3YotA7s37BLFiab54enxk7T4rwpR1nU0HVfoCpn5F4wZYxRq+LlSVFzC8vVE9cpDSLS5kjrZIZaT18tYG1/untCj+wqMIZbghaJXLtPSRW2YPHcJTz8q1YSXnJ19+0uiAIMAqaVv0kD5BAM97byYDBW+b9H6SYFkb/Pw/qcK9amMzMBjDPFpYFkl9Q2kzhsNs3HsZf/flSZjtrkQJiP3SOi/KvKzVK9X4Wym6hYZWHgmMTTYFrvr6BYnf2GkpfKNjm1d2kc0NNrq4d5H4NOEQB8MP+QH+o+BPeM6d9dthrUc1Pw+BXzOAr85CN4qtpPGoAl/Dbfgd6eu/88E2LpUufW2VFAOPWjykSOqzSN3orh7AaWuE34VFEnQ+2y3uIE8AKoyXzJv6zYkyNnNewKZeGe2kKYNwLn5UxQA9JEj7a+tvVevk4xBSkkjFAvjSG2z8/F1FXNbEfoLX1Hz/bU=
|
||||||
|
# AWS key secret
|
||||||
|
- secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA=
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: linux
|
||||||
|
env: _generate_docs
|
||||||
|
script: "./CI/generate-docs.sh"
|
||||||
|
|
||||||
|
- os: linux
|
||||||
|
env: _linux_build
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- docker run -d --name xenial -v $(dirname $(pwd)):/root -v /home/travis/package:/package
|
||||||
|
-e TRAVIS_BRANCH="$TRAVIS_BRANCH" -e TRAVIS_TAG="$TRAVIS_TAG" -w /root nimmis/ubuntu:16.04
|
||||||
|
- docker exec -it xenial /root/obs-websocket/CI/install-dependencies-xenial.sh
|
||||||
|
script:
|
||||||
|
- docker exec -it xenial /root/obs-websocket/CI/build-xenial.sh
|
||||||
|
after_success:
|
||||||
|
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
- provider: s3
|
||||||
|
region: eu-central-1
|
||||||
|
bucket: obs-websocket-linux-builds
|
||||||
|
access_key_id: "$AWS_ID"
|
||||||
|
secret_access_key: "$AWS_SECRET"
|
||||||
|
local_dir: /home/travis/package
|
||||||
|
skip_cleanup: true
|
||||||
|
acl: public_read
|
||||||
|
on:
|
||||||
|
repo: Palakis/obs-websocket
|
||||||
|
condition:
|
||||||
|
- "$TRAVIS_OS_NAME = linux"
|
||||||
|
- "-d /home/travis/package"
|
||||||
|
all_branches: true
|
62
BUILDING.md
Normal file
62
BUILDING.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Compiling obs-websocket
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
|
||||||
|
[CMake](https://cmake.org/download/), [Boost](https://www.boost.org/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
|
||||||
|
computer.
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
In cmake-gui, you'll have to set the following variables :
|
||||||
|
|
||||||
|
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
|
||||||
|
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
|
||||||
|
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
|
||||||
|
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
On Debian/Ubuntu :
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt-get install libboost-all-dev
|
||||||
|
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||||
|
cd obs-websocket
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||||
|
make -j4
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
## OS X
|
||||||
|
|
||||||
|
As a prerequisite, you will need Xcode for your current OSX version, the Xcode command line tools, and [Homebrew](https://brew.sh/).
|
||||||
|
Homebrew's setup will guide you in getting your system set up, you should be good to go once Homebrew is successfully up and running.
|
||||||
|
|
||||||
|
Use of the Travis macOS CI scripts is recommended. Please note that these
|
||||||
|
scripts install new software and can change several settings on your system. An
|
||||||
|
existing obs-studio development environment is not required, as
|
||||||
|
`install-build-obs-macos.sh` will install it for you. If you already have a
|
||||||
|
working obs-studio development environment and have built obs-studio, you can
|
||||||
|
skip that script.
|
||||||
|
|
||||||
|
Of course, you're encouraged to dig through the contents of these scripts to
|
||||||
|
look for issues or specificities.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||||
|
cd obs-websocket
|
||||||
|
./CI/install-dependencies-macos.sh
|
||||||
|
./CI/install-build-obs-macos.sh
|
||||||
|
./CI/build-macos.sh
|
||||||
|
./CI/package-macos.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder.
|
||||||
|
|
||||||
|
## Automated Builds
|
||||||
|
|
||||||
|
- Windows: [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||||
|
- Linux: [](https://travis-ci.org/Palakis/obs-websocket)
|
||||||
|
- macOS: [](https://dev.azure.com/Palakis/obs-websocket/_build)
|
28
CI/build-macos.sh
Executable file
28
CI/build-macos.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
OSTYPE=$(uname)
|
||||||
|
|
||||||
|
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||||
|
echo "[obs-websocket - Error] macOS build script can be run on Darwin-type OS only."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HAS_CMAKE=$(type cmake 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "${HAS_CMAKE}" = "" ]; then
|
||||||
|
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||||
|
|
||||||
|
echo "[obs-websocket] Building 'obs-websocket' for macOS."
|
||||||
|
mkdir -p build && cd build
|
||||||
|
cmake .. \
|
||||||
|
-DQTDIR=/usr/local/opt/qt \
|
||||||
|
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
||||||
|
-DLIBOBS_LIB=../../obs-studio/libobs \
|
||||||
|
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
||||||
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
&& make -j4
|
8
CI/build-xenial.sh
Executable file
8
CI/build-xenial.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
cd /root/obs-websocket
|
||||||
|
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||||
|
make -j4
|
33
CI/generate-docs.sh
Executable file
33
CI/generate-docs.sh
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
echo "-- Generating documentation."
|
||||||
|
echo "-- Node version: $(node -v)"
|
||||||
|
echo "-- NPM version: $(npm -v)"
|
||||||
|
|
||||||
|
cd docs
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "-- Documentation successfully generated."
|
||||||
|
|
||||||
|
if git diff --quiet; then
|
||||||
|
echo "-- No documentation changes to commit."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; then
|
||||||
|
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
REMOTE_URL="$(git config remote.origin.url)"
|
||||||
|
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
||||||
|
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
||||||
|
|
||||||
|
git config user.name "Travis CI"
|
||||||
|
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||||
|
|
||||||
|
git add ./generated
|
||||||
|
git pull
|
||||||
|
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||||
|
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
|
43
CI/install-build-obs-macos.sh
Executable file
43
CI/install-build-obs-macos.sh
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
OSTYPE=$(uname)
|
||||||
|
|
||||||
|
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||||
|
echo "[obs-websocket - Error] macOS obs-studio build script can be run on Darwin-type OS only."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HAS_CMAKE=$(type cmake 2>/dev/null)
|
||||||
|
HAS_GIT=$(type git 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "${HAS_CMAKE}" = "" ]; then
|
||||||
|
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${HAS_GIT}" = "" ]; then
|
||||||
|
echo "[obs-websocket - Error] Git not installed - please install Xcode developer tools or via Homebrew."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[obs-websocket] Downloading and unpacking OBS dependencies"
|
||||||
|
wget --quiet --retry-connrefused --waitretry=1 https://obs-nightly.s3.amazonaws.com/osx-deps-2018-08-09.tar.gz
|
||||||
|
tar -xf ./osx-deps-2018-08-09.tar.gz -C /tmp
|
||||||
|
|
||||||
|
# Build obs-studio
|
||||||
|
cd ..
|
||||||
|
echo "[obs-websocket] Cloning obs-studio from GitHub.."
|
||||||
|
git clone https://github.com/obsproject/obs-studio
|
||||||
|
cd obs-studio
|
||||||
|
OBSLatestTag=$(git describe --tags --abbrev=0)
|
||||||
|
git checkout $OBSLatestTag
|
||||||
|
mkdir build && cd build
|
||||||
|
echo "[obs-websocket] Building obs-studio.."
|
||||||
|
cmake .. \
|
||||||
|
-DBUILD_CAPTIONS=true \
|
||||||
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
|
||||||
|
-DDISABLE_PLUGINS=true \
|
||||||
|
-DENABLE_SCRIPTING=0 \
|
||||||
|
-DDepsPath=/tmp/obsdeps \
|
||||||
|
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
|
||||||
|
&& make -j4
|
129
CI/install-build-obs.cmd
Normal file
129
CI/install-build-obs.cmd
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
@echo off
|
||||||
|
SETLOCAL EnableDelayedExpansion
|
||||||
|
|
||||||
|
REM Check if obs-studio build exists.
|
||||||
|
REM If the obs-studio directory does exist, check if the last OBS tag built
|
||||||
|
REM matches the latest OBS tag.
|
||||||
|
REM If the tags match, do not build obs-studio.
|
||||||
|
REM If the tags do not match, build obs-studio.
|
||||||
|
REM If the obs-studio directory doesn't exist, build obs-studio.
|
||||||
|
echo Checking for obs-studio build...
|
||||||
|
|
||||||
|
set OBSLatestTagPrePull=0
|
||||||
|
set OBSLatestTagPostPull=0
|
||||||
|
echo Latest tag pre-pull: %OBSLatestTagPrePull%
|
||||||
|
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||||
|
|
||||||
|
REM Set up the build flag as undefined.
|
||||||
|
set "BuildOBS="
|
||||||
|
|
||||||
|
REM Check the last tag successfully built by CI.
|
||||||
|
if exist C:\projects\obs-studio-last-tag-built.txt (
|
||||||
|
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
|
||||||
|
) else (
|
||||||
|
set OBSLastTagBuilt=0
|
||||||
|
)
|
||||||
|
|
||||||
|
REM If obs-studio directory exists, run git pull and get the latest tag number.
|
||||||
|
if exist C:\projects\obs-studio\ (
|
||||||
|
echo obs-studio directory exists
|
||||||
|
echo Updating tag info
|
||||||
|
cd C:\projects\obs-studio\
|
||||||
|
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-pre-pull.txt
|
||||||
|
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt
|
||||||
|
git checkout master
|
||||||
|
git pull
|
||||||
|
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-post-pull.txt
|
||||||
|
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt
|
||||||
|
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt
|
||||||
|
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Check the obs-studio tags for mismatches.
|
||||||
|
REM If a new tag was pulled, set the build flag.
|
||||||
|
if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% (
|
||||||
|
echo Latest tag pre-pull: %OBSLatestTagPrePull%
|
||||||
|
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||||
|
echo Tags do not match. Need to rebuild OBS.
|
||||||
|
set BuildOBS=true
|
||||||
|
)
|
||||||
|
|
||||||
|
REM If the latest git tag doesn't match the last built tag, set the build flag.
|
||||||
|
if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
|
||||||
|
echo Last built OBS tag: %OBSLastTagBuilt%
|
||||||
|
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||||
|
echo Tags do not match. Need to rebuild OBS.
|
||||||
|
set BuildOBS=true
|
||||||
|
)
|
||||||
|
|
||||||
|
REM If obs-studio directory does not exist, clone the git repo, get the latest
|
||||||
|
REM tag number, and set the build flag.
|
||||||
|
if not exist C:\projects\obs-studio (
|
||||||
|
echo obs-studio directory does not exist
|
||||||
|
git clone https://github.com/obsproject/obs-studio
|
||||||
|
cd C:\projects\obs-studio\
|
||||||
|
git describe --tags --abbrev=0 > C:\projects\obs-studio-latest-tag.txt
|
||||||
|
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt
|
||||||
|
set BuildOBS=true
|
||||||
|
)
|
||||||
|
|
||||||
|
REM If the needed obs-studio libs for this build_config do not exist,
|
||||||
|
REM set the build flag.
|
||||||
|
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib (
|
||||||
|
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
|
||||||
|
set BuildOBS=true
|
||||||
|
)
|
||||||
|
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
|
||||||
|
echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist
|
||||||
|
set BuildOBS=true
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Some debug info
|
||||||
|
echo:
|
||||||
|
echo Latest tag pre-pull: %OBSLatestTagPrePull%
|
||||||
|
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||||
|
echo Latest tag: %OBSLatestTag%
|
||||||
|
echo Last built OBS tag: %OBSLastTagBuilt%
|
||||||
|
|
||||||
|
if defined BuildOBS (
|
||||||
|
echo BuildOBS: true
|
||||||
|
) else (
|
||||||
|
echo BuildOBS: false
|
||||||
|
)
|
||||||
|
echo:
|
||||||
|
|
||||||
|
REM If the build flag is set, build obs-studio.
|
||||||
|
if defined BuildOBS (
|
||||||
|
echo Building obs-studio...
|
||||||
|
echo git checkout %OBSLatestTag%
|
||||||
|
git checkout %OBSLatestTag%
|
||||||
|
echo:
|
||||||
|
echo Removing previous build dirs...
|
||||||
|
if exist build rmdir /s /q C:\projects\obs-studio\build
|
||||||
|
if exist build32 rmdir /s /q C:\projects\obs-studio\build32
|
||||||
|
if exist build64 rmdir /s /q C:\projects\obs-studio\build64
|
||||||
|
echo Making new build dirs...
|
||||||
|
mkdir build
|
||||||
|
mkdir build32
|
||||||
|
mkdir build64
|
||||||
|
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
|
||||||
|
cd ./build32
|
||||||
|
cmake -G "Visual Studio 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||||
|
echo:
|
||||||
|
echo:
|
||||||
|
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
|
||||||
|
cd ../build64
|
||||||
|
cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||||
|
echo:
|
||||||
|
echo:
|
||||||
|
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
|
||||||
|
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
|
||||||
|
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
cd ..
|
||||||
|
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt
|
||||||
|
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
|
||||||
|
) else (
|
||||||
|
echo Last OBS tag built is: %OBSLastTagBuilt%
|
||||||
|
echo No need to rebuild OBS.
|
||||||
|
)
|
61
CI/install-dependencies-macos.sh
Executable file
61
CI/install-dependencies-macos.sh
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OSTYPE=$(uname)
|
||||||
|
|
||||||
|
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||||
|
echo "[obs-websocket - Error] macOS install dependencies script can be run on Darwin-type OS only."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HAS_BREW=$(type brew 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "${HAS_BREW}" = "" ]; then
|
||||||
|
echo "[obs-websocket - Error] Please install Homebrew (https://www.brew.sh/) to build obs-websocket on macOS."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OBS Studio deps
|
||||||
|
echo "[obs-websocket] Updating Homebrew.."
|
||||||
|
brew update >/dev/null
|
||||||
|
echo "[obs-websocket] Checking installed Homebrew formulas.."
|
||||||
|
BREW_PACKAGES=$(brew list)
|
||||||
|
BREW_DEPENDENCIES="jack speexdsp ccache swig mbedtls"
|
||||||
|
|
||||||
|
for DEPENDENCY in ${BREW_DEPENDENCIES}; do
|
||||||
|
if echo "${BREW_PACKAGES}" | grep -q "^${DEPENDENCY}\$"; then
|
||||||
|
echo "[obs-websocket] Upgrading OBS-Studio dependency '${DEPENDENCY}'.."
|
||||||
|
brew upgrade ${DEPENDENCY} 2>/dev/null
|
||||||
|
else
|
||||||
|
echo "[obs-websocket] Installing OBS-Studio dependency '${DEPENDENCY}'.."
|
||||||
|
brew install ${DEPENDENCY} 2>/dev/null
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# qtwebsockets deps
|
||||||
|
echo "[obs-websocket] Installing obs-websocket dependency 'QT 5.10.1'.."
|
||||||
|
# =!= NOTICE =!=
|
||||||
|
# When building QT5 from sources on macOS 10.13+, use local qt5 formula:
|
||||||
|
# brew install ./CI/macos/qt.rb
|
||||||
|
# Pouring from the bottle is much quicker though, so use bottle for now.
|
||||||
|
# =!= NOTICE =!=
|
||||||
|
|
||||||
|
brew install https://gist.githubusercontent.com/DDRBoxman/b3956fab6073335a4bf151db0dcbd4ad/raw/ed1342a8a86793ea8c10d8b4d712a654da121ace/qt.rb
|
||||||
|
|
||||||
|
# Pin this version of QT5 to avoid `brew upgrade`
|
||||||
|
# upgrading it to incompatible version
|
||||||
|
brew pin qt
|
||||||
|
|
||||||
|
# Fetch and install Packages app
|
||||||
|
# =!= NOTICE =!=
|
||||||
|
# Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist
|
||||||
|
# =!= NOTICE =!=
|
||||||
|
|
||||||
|
HAS_PACKAGES=$(type packagesbuild 2>/dev/null)
|
||||||
|
|
||||||
|
if [ "${HAS_PACKAGES}" = "" ]; then
|
||||||
|
echo "[obs-websocket] Installing Packaging app (might require password due to 'sudo').."
|
||||||
|
curl -o './Packages.pkg' --retry-connrefused -s --retry-delay 1 'https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg'
|
||||||
|
sudo installer -pkg ./Packages.pkg -target /
|
||||||
|
fi
|
19
CI/install-dependencies-xenial.sh
Executable file
19
CI/install-dependencies-xenial.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
add-apt-repository -y ppa:obsproject/obs-studio
|
||||||
|
apt-get -qq update
|
||||||
|
|
||||||
|
apt-get install -y \
|
||||||
|
libc-dev-bin \
|
||||||
|
libc6-dev git \
|
||||||
|
build-essential \
|
||||||
|
checkinstall \
|
||||||
|
cmake \
|
||||||
|
obs-studio \
|
||||||
|
qtbase5-dev
|
||||||
|
|
||||||
|
# Dirty hack
|
||||||
|
wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h
|
||||||
|
|
||||||
|
ldconfig
|
6
CI/install-setup-qt.cmd
Normal file
6
CI/install-setup-qt.cmd
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
REM Set default values to use AppVeyor's built-in Qt.
|
||||||
|
set QTDIR32=C:\Qt\5.10.1\msvc2015
|
||||||
|
set QTDIR64=C:\Qt\5.10.1\msvc2015_64
|
||||||
|
set QTCompileVersion=5.10.1
|
726
CI/macos/obs-websocket.pkgproj
Normal file
726
CI/macos/obs-websocket.pkgproj
Normal file
@ -0,0 +1,726 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PROJECT</key>
|
||||||
|
<dict>
|
||||||
|
<key>PACKAGE_FILES</key>
|
||||||
|
<dict>
|
||||||
|
<key>DEFAULT_INSTALL_LOCATION</key>
|
||||||
|
<string>/</string>
|
||||||
|
<key>HIERARCHY</key>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>../../build/obs-websocket.so</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>bin</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>../../data</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>obs-websocket</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>plugins</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>obs-studio</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Application Support</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Automator</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Documentation</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Extensions</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Filesystems</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Frameworks</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Input Methods</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Internet Plug-Ins</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>LaunchAgents</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>LaunchDaemons</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>PreferencePanes</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Preferences</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Printers</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>PrivilegedHelperTools</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>QuickLook</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>QuickTime</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Screen Savers</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Scripts</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Services</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Widgets</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Library</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CHILDREN</key>
|
||||||
|
<array/>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Shared</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>1023</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>80</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>Users</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>GID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PERMISSIONS</key>
|
||||||
|
<integer>493</integer>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>UID</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>PAYLOAD_TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>VERSION</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
</dict>
|
||||||
|
<key>PACKAGE_SCRIPTS</key>
|
||||||
|
<dict>
|
||||||
|
<key>RESOURCES</key>
|
||||||
|
<array/>
|
||||||
|
</dict>
|
||||||
|
<key>PACKAGE_SETTINGS</key>
|
||||||
|
<dict>
|
||||||
|
<key>AUTHENTICATION</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>CONCLUSION_ACTION</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>IDENTIFIER</key>
|
||||||
|
<string>fr.palakis.obswebsocket</string>
|
||||||
|
<key>OVERWRITE_PERMISSIONS</key>
|
||||||
|
<false/>
|
||||||
|
<key>VERSION</key>
|
||||||
|
<string>4.6.0</string>
|
||||||
|
</dict>
|
||||||
|
<key>PROJECT_COMMENTS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NOTES</key>
|
||||||
|
<data>
|
||||||
|
PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M
|
||||||
|
IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv
|
||||||
|
c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l
|
||||||
|
cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7
|
||||||
|
IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250
|
||||||
|
ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp
|
||||||
|
dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u
|
||||||
|
dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD
|
||||||
|
b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg
|
||||||
|
dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5
|
||||||
|
Pgo8L2JvZHk+CjwvaHRtbD4K
|
||||||
|
</data>
|
||||||
|
</dict>
|
||||||
|
<key>PROJECT_SETTINGS</key>
|
||||||
|
<dict>
|
||||||
|
<key>BUILD_PATH</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>../../release</string>
|
||||||
|
<key>PATH_TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>EXCLUDED_FILES</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>PATTERNS_ARRAY</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.DS_Store</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>PROTECTED</key>
|
||||||
|
<true/>
|
||||||
|
<key>PROXY_NAME</key>
|
||||||
|
<string>Remove .DS_Store files</string>
|
||||||
|
<key>PROXY_TOOLTIP</key>
|
||||||
|
<string>Remove ".DS_Store" files created by the Finder.</string>
|
||||||
|
<key>STATE</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>PATTERNS_ARRAY</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.pbdevelopment</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>PROTECTED</key>
|
||||||
|
<true/>
|
||||||
|
<key>PROXY_NAME</key>
|
||||||
|
<string>Remove .pbdevelopment files</string>
|
||||||
|
<key>PROXY_TOOLTIP</key>
|
||||||
|
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
|
||||||
|
<key>STATE</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>PATTERNS_ARRAY</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>CVS</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.cvsignore</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.cvspass</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.svn</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.git</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>.gitignore</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>PROTECTED</key>
|
||||||
|
<true/>
|
||||||
|
<key>PROXY_NAME</key>
|
||||||
|
<string>Remove SCM metadata</string>
|
||||||
|
<key>PROXY_TOOLTIP</key>
|
||||||
|
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
|
||||||
|
<key>STATE</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>PATTERNS_ARRAY</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>classes.nib</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>designable.db</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>info.nib</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>PROTECTED</key>
|
||||||
|
<true/>
|
||||||
|
<key>PROXY_NAME</key>
|
||||||
|
<string>Optimize nib files</string>
|
||||||
|
<key>PROXY_TOOLTIP</key>
|
||||||
|
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
|
||||||
|
<key>STATE</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>PATTERNS_ARRAY</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>REGULAR_EXPRESSION</key>
|
||||||
|
<false/>
|
||||||
|
<key>STRING</key>
|
||||||
|
<string>Resources Disabled</string>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>PROTECTED</key>
|
||||||
|
<true/>
|
||||||
|
<key>PROXY_NAME</key>
|
||||||
|
<string>Remove Resources Disabled folders</string>
|
||||||
|
<key>PROXY_TOOLTIP</key>
|
||||||
|
<string>Remove "Resources Disabled" folders.</string>
|
||||||
|
<key>STATE</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SEPARATOR</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>NAME</key>
|
||||||
|
<string>obs-websocket</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>TYPE</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>VERSION</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
163
CI/macos/qt.rb
Normal file
163
CI/macos/qt.rb
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# Patches for Qt must be at the very least submitted to Qt's Gerrit codereview
|
||||||
|
# rather than their bug-report Jira. The latter is rarely reviewed by Qt.
|
||||||
|
class Qt < Formula
|
||||||
|
desc "Cross-platform application and UI framework"
|
||||||
|
homepage "https://www.qt.io/"
|
||||||
|
url "https://download.qt.io/archive/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz"
|
||||||
|
mirror "https://mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz"
|
||||||
|
sha256 "05ffba7b811b854ed558abf2be2ddbd3bb6ddd0b60ea4b5da75d277ac15e740a"
|
||||||
|
head "https://code.qt.io/qt/qt5.git", :branch => "5.10.1", :shallow => false
|
||||||
|
|
||||||
|
bottle do
|
||||||
|
sha256 "8b4bad005596a5f8790150fe455db998ac2406f4e0f04140d6656205d844d266" => :high_sierra
|
||||||
|
sha256 "9c488554935fb573554a4e36d36d3c81e47245b7fefc4b61edef894e67ba1740" => :sierra
|
||||||
|
sha256 "c0407afba5951df6cc4c6f6c1c315972bd41c99cecb4e029919c4c15ab6f7bdc" => :el_capitan
|
||||||
|
end
|
||||||
|
|
||||||
|
keg_only "Qt 5 has CMake issues when linked"
|
||||||
|
|
||||||
|
option "with-docs", "Build documentation"
|
||||||
|
option "with-examples", "Build examples"
|
||||||
|
|
||||||
|
deprecated_option "with-mysql" => "with-mysql-client"
|
||||||
|
|
||||||
|
# OS X 10.7 Lion is still supported in Qt 5.5, but is no longer a reference
|
||||||
|
# configuration and thus untested in practice. Builds on OS X 10.7 have been
|
||||||
|
# reported to fail: <https://github.com/Homebrew/homebrew/issues/45284>.
|
||||||
|
depends_on :macos => :mountain_lion
|
||||||
|
|
||||||
|
depends_on "pkg-config" => :build
|
||||||
|
depends_on :xcode => :build
|
||||||
|
depends_on "mysql-client" => :optional
|
||||||
|
depends_on "postgresql" => :optional
|
||||||
|
|
||||||
|
# Restore `.pc` files for framework-based build of Qt 5 on OS X. This
|
||||||
|
# partially reverts <https://codereview.qt-project.org/#/c/140954/> merged
|
||||||
|
# between the 5.5.1 and 5.6.0 releases. (Remove this as soon as feasible!)
|
||||||
|
#
|
||||||
|
# Core formulae known to fail without this patch (as of 2016-10-15):
|
||||||
|
# * gnuplot (with `--with-qt` option)
|
||||||
|
# * mkvtoolnix (with `--with-qt` option, silent build failure)
|
||||||
|
# * poppler (with `--with-qt` option)
|
||||||
|
patch do
|
||||||
|
url "https://raw.githubusercontent.com/Homebrew/formula-patches/e8fe6567/qt5/restore-pc-files.patch"
|
||||||
|
sha256 "48ff18be2f4050de7288bddbae7f47e949512ac4bcd126c2f504be2ac701158b"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fix compile error on macOS 10.13 around QFixed:
|
||||||
|
# https://github.com/Homebrew/homebrew-core/issues/27095
|
||||||
|
# https://bugreports.qt.io/browse/QTBUG-67545
|
||||||
|
patch do
|
||||||
|
url "https://raw.githubusercontent.com/z00m1n/formula-patches/0de0e229/qt/QTBUG-67545.patch"
|
||||||
|
sha256 "4a115097c7582c7dce4207f5500d13feb8c990eb8a05a43f41953985976ebe6c"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fix compile error on macOS 10.13 caused by qtlocation dependency
|
||||||
|
# mapbox-gl-native using Boost 1.62.0 does not build with C++ 17:
|
||||||
|
# https://github.com/Homebrew/homebrew-core/issues/27095
|
||||||
|
# https://bugreports.qt.io/browse/QTBUG-67810
|
||||||
|
patch do
|
||||||
|
url "https://raw.githubusercontent.com/z00m1n/formula-patches/a1a1f0dd/qt/QTBUG-67810.patch"
|
||||||
|
sha256 "8ee0bf71df1043f08ebae3aa35036be29c4d9ebff8a27e3b0411a6bd635e9382"
|
||||||
|
end
|
||||||
|
|
||||||
|
def install
|
||||||
|
args = %W[
|
||||||
|
-verbose
|
||||||
|
-prefix #{prefix}
|
||||||
|
-release
|
||||||
|
-opensource -confirm-license
|
||||||
|
-system-zlib
|
||||||
|
-qt-libpng
|
||||||
|
-qt-libjpeg
|
||||||
|
-qt-freetype
|
||||||
|
-qt-pcre
|
||||||
|
-nomake tests
|
||||||
|
-no-rpath
|
||||||
|
-pkg-config
|
||||||
|
-dbus-runtime
|
||||||
|
-no-assimp
|
||||||
|
]
|
||||||
|
|
||||||
|
args << "-nomake" << "examples" if build.without? "examples"
|
||||||
|
|
||||||
|
if build.with? "mysql-client"
|
||||||
|
args << "-plugin-sql-mysql"
|
||||||
|
(buildpath/"brew_shim/mysql_config").write <<~EOS
|
||||||
|
#!/bin/sh
|
||||||
|
if [ x"$1" = x"--libs" ]; then
|
||||||
|
mysql_config --libs | sed "s/-lssl -lcrypto//"
|
||||||
|
else
|
||||||
|
exec mysql_config "$@"
|
||||||
|
fi
|
||||||
|
EOS
|
||||||
|
chmod 0755, "brew_shim/mysql_config"
|
||||||
|
args << "-mysql_config" << buildpath/"brew_shim/mysql_config"
|
||||||
|
end
|
||||||
|
|
||||||
|
args << "-plugin-sql-psql" if build.with? "postgresql"
|
||||||
|
|
||||||
|
system "./configure", *args
|
||||||
|
system "make"
|
||||||
|
ENV.deparallelize
|
||||||
|
system "make", "install"
|
||||||
|
|
||||||
|
if build.with? "docs"
|
||||||
|
system "make", "docs"
|
||||||
|
system "make", "install_docs"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Some config scripts will only find Qt in a "Frameworks" folder
|
||||||
|
frameworks.install_symlink Dir["#{lib}/*.framework"]
|
||||||
|
|
||||||
|
# The pkg-config files installed suggest that headers can be found in the
|
||||||
|
# `include` directory. Make this so by creating symlinks from `include` to
|
||||||
|
# the Frameworks' Headers folders.
|
||||||
|
Pathname.glob("#{lib}/*.framework/Headers") do |path|
|
||||||
|
include.install_symlink path => path.parent.basename(".framework")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Move `*.app` bundles into `libexec` to expose them to `brew linkapps` and
|
||||||
|
# because we don't like having them in `bin`.
|
||||||
|
# (Note: This move breaks invocation of Assistant via the Help menu
|
||||||
|
# of both Designer and Linguist as that relies on Assistant being in `bin`.)
|
||||||
|
libexec.mkpath
|
||||||
|
Pathname.glob("#{bin}/*.app") { |app| mv app, libexec }
|
||||||
|
end
|
||||||
|
|
||||||
|
def caveats; <<~EOS
|
||||||
|
We agreed to the Qt opensource license for you.
|
||||||
|
If this is unacceptable you should uninstall.
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
test do
|
||||||
|
(testpath/"hello.pro").write <<~EOS
|
||||||
|
QT += core
|
||||||
|
QT -= gui
|
||||||
|
TARGET = hello
|
||||||
|
CONFIG += console
|
||||||
|
CONFIG -= app_bundle
|
||||||
|
TEMPLATE = app
|
||||||
|
SOURCES += main.cpp
|
||||||
|
EOS
|
||||||
|
|
||||||
|
(testpath/"main.cpp").write <<~EOS
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QCoreApplication a(argc, argv);
|
||||||
|
qDebug() << "Hello World!";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EOS
|
||||||
|
|
||||||
|
system bin/"qmake", testpath/"hello.pro"
|
||||||
|
system "make"
|
||||||
|
assert_predicate testpath/"hello", :exist?
|
||||||
|
assert_predicate testpath/"main.o", :exist?
|
||||||
|
system "./hello"
|
||||||
|
end
|
||||||
|
end
|
40
CI/package-macos.sh
Executable file
40
CI/package-macos.sh
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
OSTYPE=$(uname)
|
||||||
|
|
||||||
|
if [ "${OSTYPE}" != "Darwin" ]; then
|
||||||
|
echo "[obs-websocket - Error] macOS package script can be run on Darwin-type OS only."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[obs-websocket] Preparing package build"
|
||||||
|
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
|
||||||
|
|
||||||
|
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
|
export GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
|
||||||
|
|
||||||
|
export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
|
||||||
|
export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
|
||||||
|
|
||||||
|
export FILENAME="obs-websocket-$VERSION.pkg"
|
||||||
|
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
|
||||||
|
|
||||||
|
echo "[obs-websocket] Modifying obs-websocket.so"
|
||||||
|
install_name_tool \
|
||||||
|
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
||||||
|
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
||||||
|
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||||
|
./build/obs-websocket.so
|
||||||
|
|
||||||
|
# Check if replacement worked
|
||||||
|
echo "[obs-websocket] Dependencies for obs-websocket"
|
||||||
|
otool -L ./build/obs-websocket.so
|
||||||
|
|
||||||
|
echo "[obs-websocket] Actual package build"
|
||||||
|
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||||
|
|
||||||
|
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
|
||||||
|
mv ./release/obs-websocket.pkg ./release/$FILENAME
|
||||||
|
cp ./release/$FILENAME ./release/$LATEST_FILENAME
|
24
CI/package-xenial.sh
Executable file
24
CI/package-xenial.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd /root/obs-websocket
|
||||||
|
|
||||||
|
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
|
export PKG_VERSION="1-$GIT_HASH-$TRAVIS_BRANCH-git"
|
||||||
|
|
||||||
|
if [ -n "${TRAVIS_TAG}" ]; then
|
||||||
|
export PKG_VERSION="$TRAVIS_TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd /root/obs-websocket/build
|
||||||
|
|
||||||
|
PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \
|
||||||
|
--backup=no --deldoc=yes --install=no \
|
||||||
|
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
|
||||||
|
--pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \
|
||||||
|
--pkggroup="video" \
|
||||||
|
--pkgsource="https://github.com/Palakis/obs-websocket" \
|
||||||
|
--pakdir="/package"
|
||||||
|
|
||||||
|
chmod ao+r /package/*
|
202
CMakeLists.txt
202
CMakeLists.txt
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.2)
|
||||||
project(obs-websocket)
|
project(obs-websocket)
|
||||||
|
|
||||||
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
||||||
@ -6,53 +6,187 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
if (WIN32 OR APPLE)
|
||||||
include(external/FindLibObs.cmake)
|
include(external/FindLibObs.cmake)
|
||||||
find_package(LibObs REQUIRED)
|
|
||||||
find_package(Qt5Core REQUIRED)
|
|
||||||
find_package(Qt5WebSockets REQUIRED)
|
|
||||||
find_package(Qt5Widgets REQUIRED)
|
|
||||||
|
|
||||||
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
|
|
||||||
set(ENABLED_PROGRAMS false)
|
|
||||||
|
|
||||||
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
|
|
||||||
if(OBS_FRONTEND_LIB EQUAL "OBS_FRONTEND_LIB-NOTFOUND")
|
|
||||||
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_definitions(-DASIO_STANDALONE)
|
||||||
|
|
||||||
|
if (UNIX)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (WIN32 OR APPLE)
|
||||||
|
include(external/FindLibObs.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(LibObs REQUIRED)
|
||||||
|
find_package(Qt5Core REQUIRED)
|
||||||
|
find_package(Qt5Widgets REQUIRED)
|
||||||
|
|
||||||
set(obs-websocket_SOURCES
|
set(obs-websocket_SOURCES
|
||||||
obs-websocket.cpp
|
src/obs-websocket.cpp
|
||||||
WSServer.cpp
|
src/WSServer.cpp
|
||||||
WSRequestHandler.cpp
|
src/ConnectionProperties.cpp
|
||||||
WSEvents.cpp
|
src/WSRequestHandler.cpp
|
||||||
Config.cpp
|
src/WSRequestHandler_General.cpp
|
||||||
Utils.cpp
|
src/WSRequestHandler_Profiles.cpp
|
||||||
forms/settings-dialog.cpp)
|
src/WSRequestHandler_Recording.cpp
|
||||||
|
src/WSRequestHandler_ReplayBuffer.cpp
|
||||||
|
src/WSRequestHandler_SceneCollections.cpp
|
||||||
|
src/WSRequestHandler_Scenes.cpp
|
||||||
|
src/WSRequestHandler_SceneItems.cpp
|
||||||
|
src/WSRequestHandler_Sources.cpp
|
||||||
|
src/WSRequestHandler_Streaming.cpp
|
||||||
|
src/WSRequestHandler_StudioMode.cpp
|
||||||
|
src/WSRequestHandler_Transitions.cpp
|
||||||
|
src/WSEvents.cpp
|
||||||
|
src/Config.cpp
|
||||||
|
src/Utils.cpp
|
||||||
|
src/forms/settings-dialog.cpp)
|
||||||
|
|
||||||
set(obs-websocket_HEADERS
|
set(obs-websocket_HEADERS
|
||||||
obs-websocket.h
|
src/obs-websocket.h
|
||||||
WSServer.h
|
src/WSServer.h
|
||||||
WSRequestHandler.h
|
src/ConnectionProperties.h
|
||||||
WSEvents.h
|
src/WSRequestHandler.h
|
||||||
Config.h
|
src/WSEvents.h
|
||||||
Utils.h
|
src/Config.h
|
||||||
forms/settings-dialog.h)
|
src/Utils.h
|
||||||
|
src/forms/settings-dialog.h)
|
||||||
|
|
||||||
|
# --- Platform-independent build settings ---
|
||||||
add_library(obs-websocket MODULE
|
add_library(obs-websocket MODULE
|
||||||
${obs-websocket_SOURCES}
|
${obs-websocket_SOURCES}
|
||||||
${obs-websocket_HEADERS})
|
${obs-websocket_HEADERS})
|
||||||
add_dependencies(obs-websocket mbedcrypto)
|
|
||||||
include_directories(
|
include_directories(
|
||||||
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
|
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
|
||||||
${Qt5Core_INCLUDES}
|
${Qt5Core_INCLUDES}
|
||||||
${Qt5WebSockets_INCLUDES}
|
|
||||||
${Qt5Widgets_INCLUDES}
|
${Qt5Widgets_INCLUDES}
|
||||||
${mbedcrypto_INCLUDES}
|
"${CMAKE_SOURCE_DIR}/deps/asio/asio/include"
|
||||||
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
|
"${CMAKE_SOURCE_DIR}/deps/websocketpp")
|
||||||
|
|
||||||
target_link_libraries(obs-websocket
|
target_link_libraries(obs-websocket
|
||||||
libobs
|
libobs
|
||||||
${OBS_FRONTEND_LIB}
|
|
||||||
Qt5::Core
|
Qt5::Core
|
||||||
Qt5::WebSockets
|
Qt5::Widgets)
|
||||||
Qt5::Widgets
|
|
||||||
mbedcrypto)
|
# --- End of section ---
|
||||||
|
|
||||||
|
# --- Windows-specific build settings and tasks ---
|
||||||
|
if(WIN32)
|
||||||
|
if(NOT DEFINED OBS_FRONTEND_LIB)
|
||||||
|
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
|
||||||
|
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||||
|
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
set(ARCH_NAME "64bit")
|
||||||
|
set(OBS_BUILDDIR_ARCH "build64")
|
||||||
|
else()
|
||||||
|
set(ARCH_NAME "32bit")
|
||||||
|
set(OBS_BUILDDIR_ARCH "build32")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(obs-websocket
|
||||||
|
"${OBS_FRONTEND_LIB}")
|
||||||
|
|
||||||
|
# --- Release package helper ---
|
||||||
|
# The "release" folder has a structure similar OBS' one on Windows
|
||||||
|
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
||||||
|
|
||||||
|
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||||
|
# If config is Release, package release files
|
||||||
|
COMMAND if $<CONFIG:Release>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E make_directory
|
||||||
|
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
|
||||||
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy_directory
|
||||||
|
"${PROJECT_SOURCE_DIR}/data"
|
||||||
|
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
|
||||||
|
"$<TARGET_FILE:obs-websocket>"
|
||||||
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
# If config is RelWithDebInfo, package release files
|
||||||
|
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E make_directory
|
||||||
|
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
|
||||||
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy_directory
|
||||||
|
"${PROJECT_SOURCE_DIR}/data"
|
||||||
|
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
|
||||||
|
"$<TARGET_FILE:obs-websocket>"
|
||||||
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:RelWithDebInfo>==1 ("${CMAKE_COMMAND}" -E copy
|
||||||
|
"$<TARGET_PDB_FILE:obs-websocket>"
|
||||||
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
# Copy to obs-studio dev environment for immediate testing
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy
|
||||||
|
"$<TARGET_FILE:obs-websocket>"
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy
|
||||||
|
"$<TARGET_PDB_FILE:obs-websocket>"
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E make_directory
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy_directory
|
||||||
|
"${PROJECT_SOURCE_DIR}/data"
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
|
||||||
|
)
|
||||||
|
# --- End of sub-section ---
|
||||||
|
|
||||||
|
endif()
|
||||||
|
# --- End of section ---
|
||||||
|
|
||||||
|
# --- Linux-specific build settings and tasks ---
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||||
|
target_link_libraries(obs-websocket obs-frontend-api)
|
||||||
|
|
||||||
|
file(GLOB locale_files data/locale/*.ini)
|
||||||
|
execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_MACHINE)
|
||||||
|
|
||||||
|
install(TARGETS obs-websocket
|
||||||
|
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
|
||||||
|
# Dirty fix for Ubuntu
|
||||||
|
install(TARGETS obs-websocket
|
||||||
|
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
|
||||||
|
|
||||||
|
install(FILES ${locale_files}
|
||||||
|
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
|
||||||
|
endif()
|
||||||
|
# --- End of section ---
|
||||||
|
|
||||||
|
# -- OS X specific build settings and tasks --
|
||||||
|
if(APPLE)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default")
|
||||||
|
|
||||||
|
set(CMAKE_SKIP_RPATH TRUE)
|
||||||
|
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||||
|
target_link_libraries(obs-websocket "${OBS_FRONTEND_LIB}")
|
||||||
|
endif()
|
||||||
|
# -- End of section --
|
||||||
|
153
Config.cpp
153
Config.cpp
@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mbedtls/base64.h>
|
|
||||||
#include <mbedtls/sha256.h>
|
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
#include "Config.h"
|
|
||||||
|
|
||||||
#define CONFIG_SECTION_NAME "obs-websocket"
|
|
||||||
#define CONFIG_PARAM_SECRET "auth_hash"
|
|
||||||
#define CONFIG_PARAM_SALT "auth_salt"
|
|
||||||
#define CONFIG_PARAM_AUTHREQUIRED "auth_required"
|
|
||||||
|
|
||||||
Config *Config::_instance = new Config();
|
|
||||||
|
|
||||||
Config::Config() {
|
|
||||||
// Default settings
|
|
||||||
AuthRequired = false;
|
|
||||||
Secret = "";
|
|
||||||
Salt = "";
|
|
||||||
SettingsLoaded = false;
|
|
||||||
|
|
||||||
mbedtls_entropy_init(&entropy);
|
|
||||||
mbedtls_ctr_drbg_init(&rng);
|
|
||||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
|
||||||
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
|
|
||||||
|
|
||||||
SessionChallenge = GenerateSalt();
|
|
||||||
}
|
|
||||||
|
|
||||||
Config::~Config() {
|
|
||||||
mbedtls_ctr_drbg_free(&rng);
|
|
||||||
mbedtls_entropy_free(&entropy);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* Config::GenerateSalt() {
|
|
||||||
// Generate 32 random chars
|
|
||||||
unsigned char *random_chars = (unsigned char *)bzalloc(32);
|
|
||||||
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
|
|
||||||
|
|
||||||
// Convert the 32 random chars to a base64 string
|
|
||||||
unsigned char *salt = (unsigned char*)bzalloc(64);
|
|
||||||
size_t salt_bytes;
|
|
||||||
mbedtls_base64_encode(salt, 64, &salt_bytes, random_chars, 32);
|
|
||||||
salt[salt_bytes] = 0; // Null-terminate the string
|
|
||||||
|
|
||||||
bfree(random_chars);
|
|
||||||
return (char *)salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* Config::GenerateSecret(const char *password, const char *salt) {
|
|
||||||
size_t passwordLength = strlen(password);
|
|
||||||
size_t saltLength = strlen(salt);
|
|
||||||
|
|
||||||
// Concatenate the password and the salt
|
|
||||||
unsigned char *passAndSalt = (unsigned char*)bzalloc(passwordLength + saltLength);
|
|
||||||
memcpy(passAndSalt, password, passwordLength);
|
|
||||||
memcpy(passAndSalt + passwordLength, salt, saltLength);
|
|
||||||
passAndSalt[passwordLength + saltLength] = 0; // Null-terminate the string
|
|
||||||
|
|
||||||
// Generate a SHA256 hash of the password
|
|
||||||
unsigned char *challengeHash = (unsigned char *)bzalloc(32);
|
|
||||||
mbedtls_sha256(passAndSalt, passwordLength + saltLength, challengeHash, 0);
|
|
||||||
|
|
||||||
// Encode SHA256 hash to Base64
|
|
||||||
unsigned char *challenge = (unsigned char*)bzalloc(64);
|
|
||||||
size_t challenge_bytes = 0;
|
|
||||||
mbedtls_base64_encode(challenge, 64, &challenge_bytes, challengeHash, 32);
|
|
||||||
challenge[64] = 0; // Null-terminate the string
|
|
||||||
|
|
||||||
bfree(passAndSalt);
|
|
||||||
bfree(challengeHash);
|
|
||||||
return (char*)challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Config::SetPassword(const char *password) {
|
|
||||||
const char *new_salt = GenerateSalt();
|
|
||||||
const char *new_challenge = GenerateSecret(password, new_salt);
|
|
||||||
|
|
||||||
this->Salt = new_salt;
|
|
||||||
this->Secret = new_challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Config::CheckAuth(const char *response) {
|
|
||||||
size_t secretLength = strlen(this->Secret);
|
|
||||||
size_t sessChallengeLength = strlen(this->SessionChallenge);
|
|
||||||
|
|
||||||
// Concatenate auth secret with the challenge sent to the user
|
|
||||||
char *challengeAndResponse = (char*)bzalloc(secretLength + sessChallengeLength);
|
|
||||||
memcpy(challengeAndResponse, this->Secret, secretLength);
|
|
||||||
memcpy(challengeAndResponse + secretLength, this->SessionChallenge, sessChallengeLength);
|
|
||||||
challengeAndResponse[secretLength + sessChallengeLength] = 0; // Null-terminate the string
|
|
||||||
|
|
||||||
// Generate a SHA256 hash of challengeAndResponse
|
|
||||||
unsigned char *hash = (unsigned char*)bzalloc(32);
|
|
||||||
mbedtls_sha256((unsigned char*)challengeAndResponse, secretLength + sessChallengeLength, hash, 0);
|
|
||||||
|
|
||||||
// Encode the SHA256 hash to Base64
|
|
||||||
unsigned char *expected_response = (unsigned char*)bzalloc(64);
|
|
||||||
size_t base64_size = 0;
|
|
||||||
mbedtls_base64_encode(expected_response, 64, &base64_size, hash, 32);
|
|
||||||
expected_response[64] = 0; // Null-terminate the string
|
|
||||||
|
|
||||||
if (strcmp((char*)expected_response, response) == 0) {
|
|
||||||
SessionChallenge = GenerateSalt();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_data) {
|
|
||||||
Config *conf = static_cast<Config *>(private_data);
|
|
||||||
|
|
||||||
if (saving) {
|
|
||||||
obs_data_t *settings = obs_data_create();
|
|
||||||
obs_data_set_bool(settings, CONFIG_PARAM_AUTHREQUIRED, conf->AuthRequired);
|
|
||||||
obs_data_set_string(settings, CONFIG_PARAM_SECRET, conf->Secret);
|
|
||||||
obs_data_set_string(settings, CONFIG_PARAM_SALT, conf->Salt);
|
|
||||||
|
|
||||||
obs_data_set_obj(save_data, CONFIG_SECTION_NAME, settings);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
obs_data_t *settings = obs_data_get_obj(save_data, CONFIG_SECTION_NAME);
|
|
||||||
if (settings) {
|
|
||||||
conf->AuthRequired = obs_data_get_bool(settings, CONFIG_PARAM_AUTHREQUIRED);
|
|
||||||
conf->Secret = obs_data_get_string(settings, CONFIG_PARAM_SECRET);
|
|
||||||
conf->Salt = obs_data_get_string(settings, CONFIG_PARAM_SALT);
|
|
||||||
|
|
||||||
conf->SettingsLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Config* Config::Current() {
|
|
||||||
return _instance;
|
|
||||||
}
|
|
104
PROTOCOL.md
104
PROTOCOL.md
@ -1,104 +0,0 @@
|
|||||||
obs-websocket protocol reference
|
|
||||||
================================
|
|
||||||
|
|
||||||
## General introduction
|
|
||||||
**This document is still a WIP. Some things are missing (but won't stay like this for long).**
|
|
||||||
|
|
||||||
Messages exchanged between the client and the server are JSON objects.
|
|
||||||
The protocol in general in based on the OBS Remote protoctol created by Bill Hamilton, with new commands specific to OBS Studio.
|
|
||||||
|
|
||||||
## Events
|
|
||||||
|
|
||||||
### Description
|
|
||||||
Events are sent exclusively by the server and broadcasted to every client connected to the server.
|
|
||||||
An event message has at least one field :
|
|
||||||
- **update-type** (string) : the type of event
|
|
||||||
Additional fields will be present in the event message if the event type requires it.
|
|
||||||
|
|
||||||
### Event types
|
|
||||||
#### "SwitchScenes"
|
|
||||||
OBS is switching to another scene.
|
|
||||||
Additional fields :
|
|
||||||
- **scene-name** : the name of the scene being switched to
|
|
||||||
|
|
||||||
#### "ScenesChanged"
|
|
||||||
The scene list has been modified (scenes added, removed or moved).
|
|
||||||
|
|
||||||
#### "StreamStarting"
|
|
||||||
Streaming is starting but isn't completely started yet.
|
|
||||||
|
|
||||||
#### "StreamStarted"
|
|
||||||
*New in OBS Studio*
|
|
||||||
Streaming has been started successfully.
|
|
||||||
|
|
||||||
#### "StreamStopping"
|
|
||||||
Streaming is stopping but isn't completely stopped yet.
|
|
||||||
|
|
||||||
#### "StreamStopped"
|
|
||||||
*New in OBS Studio*
|
|
||||||
Streaming has been stopped successfully.
|
|
||||||
|
|
||||||
#### "RecordingStarting"
|
|
||||||
*New in OBS Studio*
|
|
||||||
Recording is starting but isn't completely started yet.
|
|
||||||
|
|
||||||
#### "RecordingStarted"
|
|
||||||
*New in OBS Studio*
|
|
||||||
Recording has been started successfully.
|
|
||||||
|
|
||||||
#### "RecordingStopping"
|
|
||||||
*New in OBS Studio*
|
|
||||||
Recording is stopping but isn't completely stopped yet.
|
|
||||||
|
|
||||||
#### "RecordingStopped"
|
|
||||||
*New in OBS Studio*
|
|
||||||
Recording has been stopped successfully.
|
|
||||||
|
|
||||||
#### "StreamStatus"
|
|
||||||
Sent every second with those additional fields :
|
|
||||||
- **streaming** (bool) : streaming state
|
|
||||||
- **recording** (bool) : recording state
|
|
||||||
- **preview-only** (bool) : always false.
|
|
||||||
- **bytes-per-sec** (integer) : during streaming, amount of data (in bytes) transmitted by the stream encoder
|
|
||||||
- **strain** (double) : i have no idea what this is
|
|
||||||
- **total-stream-time** (integer) : total time since the beginning of streaming
|
|
||||||
- **num-total-frames** (integer) : total number of frames transmitted since the beginning
|
|
||||||
- **num-dropped-frames** (integer) : number of frames dropped by the encoder for the current streaming session
|
|
||||||
- **fps** (double) : current framerate
|
|
||||||
|
|
||||||
#### "Exiting"
|
|
||||||
*New in OBS Studio*
|
|
||||||
OBS is exiting.
|
|
||||||
|
|
||||||
## Requests
|
|
||||||
|
|
||||||
### Description
|
|
||||||
Requests are sent by the client and have at least two fields :
|
|
||||||
- **"request-type"** (string) : one of the request types listed in the sub-section "Requests".
|
|
||||||
- **"message-id"** (string) : an string defined by the client that will be embedded in the response from the server.
|
|
||||||
Depending on the request type, additional fields are needed in the request message (see the "Request types" section below for more informations).
|
|
||||||
|
|
||||||
Once a request is sent, the server processes it and sends a JSON response to the client with the following fields in it :
|
|
||||||
- **"message-id"** (string) : the custom string you specified in the request.
|
|
||||||
- **"status"** (string) : two possible values : "ok" or "error".
|
|
||||||
- **"error"** (string) : the error message associated with an error reponse (when "status" equals "error").
|
|
||||||
Additional fields can be sent in the response if a request type requires it.
|
|
||||||
|
|
||||||
### Request types
|
|
||||||
#### "GetVersion"
|
|
||||||
#### "GetAuthRequired"
|
|
||||||
#### "Authenticate"
|
|
||||||
#### "GetCurrentScene"
|
|
||||||
#### "SetCurrentScene"
|
|
||||||
#### "GetSceneList"
|
|
||||||
#### "SetSourceRender"
|
|
||||||
#### "StartStopStreaming"
|
|
||||||
#### "StartStopRecording"
|
|
||||||
*New in OBS Studio*
|
|
||||||
#### "GetStreamingStatus"
|
|
||||||
#### "GetTransitionList"
|
|
||||||
*New in OBS Studio*
|
|
||||||
#### "GetCurrentTransition"
|
|
||||||
*New in OBS Studio*
|
|
||||||
#### "SetCurrentTransition"
|
|
||||||
*New in OBS Studio*
|
|
138
README.md
138
README.md
@ -1,19 +1,131 @@
|
|||||||
obs-websocket
|
obs-websocket
|
||||||
==============
|
==============
|
||||||
Websocket API for OBS Studio.
|
Remote control of OBS Studio made easy.
|
||||||
|
|
||||||
## Build prerequisites
|
Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis)
|
||||||
You need QT 5.7 (with QtWebSockets), CMake, and a working development environment for OBS Studio installed on your computer.
|
|
||||||
|
|
||||||
## How to build
|
[](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [](https://travis-ci.org/Palakis/obs-websocket)
|
||||||
In CMake, you'll need to fill these CMake variables :
|
|
||||||
- **QTDIR** (path) : location of the Qt environment suited for your compiler and architecture
|
|
||||||
- **LIBOBS_INCLUDE_DIR** (path) : location of the libobs subfolder in the source code of OBS Studio
|
|
||||||
- **LIBOBS_LIB** (filepath) : location of the obs.lib file
|
|
||||||
- **OBS_FRONTEND_LIB** (filepath) : location of the obs-frontend-api.lib file
|
|
||||||
|
|
||||||
After building the obs-websocket plugin's binary, copy its Qt dependencies (QtCore, QtNetwork and QtWebSockets library binaries) in the same folder.
|
## Downloads
|
||||||
|
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
||||||
|
|
||||||
## How to use
|
## Using obs-websocket
|
||||||
There's currently no frontend or language API available for obs-websocket. However, the full protocol reference is documented in the [PROTOCOL.md](PROTOCOL.md) file.
|
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
|
||||||
A simple websocket client can connect to the plugin's embedded server.
|
|
||||||
|
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
|
||||||
|
|
||||||
|
### Possible use cases
|
||||||
|
- Remote control OBS from a phone or tablet on the same local network
|
||||||
|
- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
|
||||||
|
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
||||||
|
|
||||||
|
### For developers
|
||||||
|
|
||||||
|
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
|
||||||
|
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
|
||||||
|
|
||||||
|
Here's a list of available language APIs for obs-websocket :
|
||||||
|
- Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan
|
||||||
|
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
|
||||||
|
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
||||||
|
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
|
||||||
|
|
||||||
|
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `stephane /dot/ lepin /at/ gmail /dot/ com` !
|
||||||
|
|
||||||
|
## Compiling obs-websocket
|
||||||
|
|
||||||
|
See the [build instructions](BUILDING.md).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Branches
|
||||||
|
|
||||||
|
The two main development branches are:
|
||||||
|
|
||||||
|
- `4.x-current`: actively-maintained codebase for 4.x releases. Backwards-compatible (unless stated otherwise) with existing clients until 5.0.
|
||||||
|
- `5.x`: upcoming 5.0 version
|
||||||
|
|
||||||
|
**New features and fixes must be based off and contributed to `4.x-current`**, as obs-websocket 5.0 is not in active development yet.
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
Pull Requests must never be based off your fork's main branch (in our case, `4.x-current` or `5.x`). Start your work in a new branch
|
||||||
|
based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work.
|
||||||
|
|
||||||
|
If your Pull Request is not ready to merge yet, tag it with the `work in progress` label. You can also use the `help needed` label if you have questions, need a hand or want to ask for input.
|
||||||
|
|
||||||
|
### Code style & formatting
|
||||||
|
|
||||||
|
Source code is indented with tabs, with spaces allowed for alignment.
|
||||||
|
|
||||||
|
Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples).
|
||||||
|
These are using to automatically generate the [protocol specification](docs/generated/protocol.md).
|
||||||
|
|
||||||
|
Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (success) {
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("something went wrong");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
is better like this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (!success) {
|
||||||
|
return req->SendErrorResponse("something went wrong");
|
||||||
|
}
|
||||||
|
return req->SendOKResponse();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
**Your help is welcome on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket
|
||||||
|
|
||||||
|
## Special thanks
|
||||||
|
|
||||||
|
In (almost) order of appearance:
|
||||||
|
|
||||||
|
- [Brendan H.](https://github.com/haganbmj): Code contributions and gooder English in the Protocol specification
|
||||||
|
- [Mikhail Swift](https://github.com/mikhailswift): Code contributions
|
||||||
|
- [Tobias Frahmer](https://github.com/Frahmer): Initial German localization
|
||||||
|
- [Genture](https://github.com/Genteure): Initial Simplified Chinese and Traditional Chinese localizations
|
||||||
|
- [Larissa Gabilan](https://github.com/laris151): Initial Portuguese localization
|
||||||
|
- [Andy Asquelt](https://github.com/asquelt): Initial Polish localization
|
||||||
|
- [Marcel Haazen](https://github.com/nekocentral): Initial Dutch localization
|
||||||
|
- [Peter Antonvich](https://github.com/pantonvich): Code contributions
|
||||||
|
- [yinzara](https://github.com/yinzara): Code contributions
|
||||||
|
- [Chris Angelico](https://github.com/Rosuav): Code contributions
|
||||||
|
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi): Code contributions
|
||||||
|
- [Marwin M](https://github.com/dragonbane0): Code contributions
|
||||||
|
- [Logan S.](https://github.com/lsdaniel): Code contributions
|
||||||
|
- [RainbowEK](https://github.com/RainbowEK): Code contributions
|
||||||
|
- [RytoEX](https://github.com/RytoEX): CI script and code contributions
|
||||||
|
- [Theodore Stoddard](https://github.com/TStod): Code contributions
|
||||||
|
- [Philip Loche](https://github.com/PicoCentauri): Code contributions
|
||||||
|
- [Patrick Heyer](https://github.com/PatTheMav): Code contributions and CI fixes
|
||||||
|
- [Alex Van Camp](https://github.com/Lange): Code contributions
|
||||||
|
- [Freddie Meyer](https://github.com/DungFu): Code contributions
|
||||||
|
- [Casey Muller](https://github.com/caseymrm): CI fixes
|
||||||
|
- [Chris Angelico](https://github.com/Rosuav): Documentation fixes
|
||||||
|
|
||||||
|
And also: special thanks to supporters of the project!
|
||||||
|
|
||||||
|
## Supporters
|
||||||
|
|
||||||
|
They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
|
||||||
|
|
||||||
|
[](http://supportclass.net)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
|
||||||
|
|
||||||
|
[](http://www.mediaunit.no/)
|
||||||
|
115
Utils.cpp
115
Utils.cpp
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "Utils.h"
|
|
||||||
|
|
||||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) {
|
|
||||||
obs_data_array_t *items = obs_data_array_create();
|
|
||||||
obs_scene_t *scene = obs_scene_from_source(source);
|
|
||||||
if (scene == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
|
|
||||||
obs_data_array_t *data = static_cast<obs_data_array_t *>(param);
|
|
||||||
obs_data_array_push_back(data, GetSceneItemData(currentItem));
|
|
||||||
return true;
|
|
||||||
}, items);
|
|
||||||
|
|
||||||
obs_scene_release(scene);
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
|
|
||||||
if (!item) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 pos;
|
|
||||||
obs_sceneitem_get_pos(item, &pos);
|
|
||||||
|
|
||||||
vec2 bounds;
|
|
||||||
obs_sceneitem_get_bounds(item, &bounds);
|
|
||||||
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_string(data, "name", obs_source_get_name(obs_sceneitem_get_source(item)));
|
|
||||||
obs_data_set_string(data, "type", obs_source_get_id(obs_sceneitem_get_source(item)));
|
|
||||||
obs_data_set_double(data, "volume", obs_source_get_volume(obs_sceneitem_get_source(item)));
|
|
||||||
obs_data_set_double(data, "x", pos.x);
|
|
||||||
obs_data_set_double(data, "y", pos.y);
|
|
||||||
obs_data_set_double(data, "cx", bounds.x);
|
|
||||||
obs_data_set_double(data, "cy", bounds.y);
|
|
||||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name) {
|
|
||||||
struct current_search {
|
|
||||||
const char* query;
|
|
||||||
obs_sceneitem_t* result;
|
|
||||||
};
|
|
||||||
|
|
||||||
current_search search;
|
|
||||||
search.query = name;
|
|
||||||
search.result = NULL;
|
|
||||||
|
|
||||||
obs_scene_t *scene = obs_scene_from_source(source);
|
|
||||||
if (scene == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_scene_enum_items(scene, [](obs_scene_t *scene, obs_sceneitem_t *currentItem, void *param) {
|
|
||||||
current_search *search = static_cast<current_search *>(param);
|
|
||||||
|
|
||||||
const char* currentItemName = obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
|
||||||
if (strcmp(currentItemName, search->query) == 0) {
|
|
||||||
search->result = currentItem;
|
|
||||||
obs_sceneitem_addref(search->result);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}, &search);
|
|
||||||
|
|
||||||
return search.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_data_array_t* Utils::GetScenes() {
|
|
||||||
obs_frontend_source_list sceneList = {};
|
|
||||||
obs_frontend_get_scenes(&sceneList);
|
|
||||||
|
|
||||||
obs_data_array_t* scenes = obs_data_array_create();
|
|
||||||
for (size_t i = 0; i < (&sceneList)->sources.num; i++) {
|
|
||||||
obs_source_t* scene = (&sceneList)->sources.array[i];
|
|
||||||
obs_data_array_push_back(scenes, GetSceneData(scene));
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_frontend_source_list_free(&sceneList);
|
|
||||||
|
|
||||||
return scenes;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_data_t* Utils::GetSceneData(obs_source *source) {
|
|
||||||
obs_data_t* sceneData = obs_data_create();
|
|
||||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
|
||||||
obs_data_set_array(sceneData, "sources", GetSceneItems(source));
|
|
||||||
|
|
||||||
return sceneData;
|
|
||||||
}
|
|
215
WSEvents.cpp
215
WSEvents.cpp
@ -1,215 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "WSEvents.h"
|
|
||||||
|
|
||||||
WSEvents::WSEvents(WSServer *server) {
|
|
||||||
_srv = server;
|
|
||||||
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
|
||||||
|
|
||||||
QTimer *statusTimer = new QTimer();
|
|
||||||
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
|
|
||||||
statusTimer->start(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
WSEvents::~WSEvents() {
|
|
||||||
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private_data)
|
|
||||||
{
|
|
||||||
WSEvents *owner = static_cast<WSEvents *>(private_data);
|
|
||||||
|
|
||||||
// TODO : implement SourceChanged, SourceOrderChanged and RepopulateSources
|
|
||||||
|
|
||||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
|
|
||||||
owner->OnSceneChange();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
|
|
||||||
owner->OnSceneListChange();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) {
|
|
||||||
owner->OnStreamStarting();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
|
|
||||||
owner->OnStreamStarted();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
|
|
||||||
owner->OnStreamStopping();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
|
|
||||||
owner->OnStreamStopped();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
|
|
||||||
owner->OnRecordingStarting();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
|
|
||||||
owner->OnRecordingStarted();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
|
|
||||||
owner->OnRecordingStarting();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
|
|
||||||
owner->OnRecordingStopped();
|
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_EXIT) {
|
|
||||||
obs_frontend_save();
|
|
||||||
owner->OnExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) {
|
|
||||||
obs_source_t *source = obs_frontend_get_current_scene();
|
|
||||||
const char *name = obs_source_get_name(source);
|
|
||||||
|
|
||||||
obs_data_t *update = obs_data_create();
|
|
||||||
|
|
||||||
obs_data_set_string(update, "update-type", updateType);
|
|
||||||
if (additionalFields != NULL) {
|
|
||||||
obs_data_apply(update, additionalFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
_srv->broadcast(obs_data_get_json(update));
|
|
||||||
|
|
||||||
obs_data_release(update);
|
|
||||||
obs_source_release(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnSceneChange() {
|
|
||||||
// Implements an existing update type from bilhamil's OBS Remote
|
|
||||||
obs_source_t *source = obs_frontend_get_current_scene();
|
|
||||||
const char *name = obs_source_get_name(source);
|
|
||||||
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_string(data, "scene-name", name);
|
|
||||||
|
|
||||||
broadcastUpdate("SwitchScenes", data);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
obs_source_release(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnSceneListChange() {
|
|
||||||
broadcastUpdate("ScenesChanged");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnStreamStarting() {
|
|
||||||
// Implements an existing update type from bilhamil's OBS Remote
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_bool(data, "preview-only", false);
|
|
||||||
|
|
||||||
broadcastUpdate("StreamStarting", data);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnStreamStarted() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
_streamStartTime = os_gettime_ns();
|
|
||||||
_lastBytesSent = 0;
|
|
||||||
broadcastUpdate("StreamStarted");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnStreamStopping() {
|
|
||||||
// Implements an existing update type from bilhamil's OBS Remote
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_bool(data, "preview-only", false);
|
|
||||||
|
|
||||||
broadcastUpdate("StreamStopping", data);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnStreamStopped() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
_streamStartTime = 0;
|
|
||||||
broadcastUpdate("StreamStopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnRecordingStarting() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
broadcastUpdate("RecordingStarting");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnRecordingStarted() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
broadcastUpdate("RecordingStarted");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnRecordingStopping() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
broadcastUpdate("RecordingStopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnRecordingStopped() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
broadcastUpdate("RecordingStopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::OnExit() {
|
|
||||||
// New update type specific to OBS Studio
|
|
||||||
broadcastUpdate("Exiting");
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSEvents::StreamStatus() {
|
|
||||||
bool streamingActive = obs_frontend_streaming_active();
|
|
||||||
bool recordingActive = obs_frontend_recording_active();
|
|
||||||
|
|
||||||
obs_output_t *streamOutput = obs_frontend_get_streaming_output();
|
|
||||||
|
|
||||||
if (!streamOutput || !streamingActive || !recordingActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t bytesSent = obs_output_get_total_bytes(streamOutput);
|
|
||||||
uint64_t bytesSentTime = os_gettime_ns();
|
|
||||||
|
|
||||||
if (bytesSent < _lastBytesSent) {
|
|
||||||
bytesSent = 0;
|
|
||||||
}
|
|
||||||
if (bytesSent == 0) {
|
|
||||||
_lastBytesSent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t bitsBetween = (bytesSent - _lastBytesSent) * 8;
|
|
||||||
double timePassed = double(bytesSentTime - _lastBytesSentTime) / 1000000000.0;
|
|
||||||
|
|
||||||
uint64_t bitsPerSec = bitsBetween / timePassed;
|
|
||||||
uint64_t bytesPerSec = bitsPerSec / 8;
|
|
||||||
|
|
||||||
_lastBytesSent = bytesSent;
|
|
||||||
_lastBytesSentTime = bytesSentTime;
|
|
||||||
|
|
||||||
uint64_t totalStreamTime = (os_gettime_ns() - _streamStartTime) / 1000000000;
|
|
||||||
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_bool(data, "streaming", streamingActive);
|
|
||||||
obs_data_set_bool(data, "recording", recordingActive);
|
|
||||||
obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote
|
|
||||||
obs_data_set_int(data, "bytes-per-sec", bytesPerSec); // BUG : Computation seems buggy
|
|
||||||
obs_data_set_double(data, "strain", 0.0); // dafuq is strain
|
|
||||||
obs_data_set_int(data, "total-stream-time", totalStreamTime);
|
|
||||||
obs_data_set_int(data, "num-total-frames", obs_output_get_total_frames(streamOutput));
|
|
||||||
obs_data_set_int(data, "num-dropped-frames", obs_output_get_frames_dropped(streamOutput));
|
|
||||||
obs_data_set_double(data, "fps", obs_get_active_fps());
|
|
||||||
|
|
||||||
broadcastUpdate("StreamStatus", data);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
obs_output_release(streamOutput);
|
|
||||||
}
|
|
63
WSEvents.h
63
WSEvents.h
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef WSEVENTS_H
|
|
||||||
#define WSEVENTS_H
|
|
||||||
|
|
||||||
#include <QtWebSockets/QWebSocket>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
#include <util/platform.h>
|
|
||||||
#include "WSServer.h"
|
|
||||||
|
|
||||||
class WSEvents : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit WSEvents(WSServer *server);
|
|
||||||
~WSEvents();
|
|
||||||
static void FrontendEventHandler(enum obs_frontend_event event, void *private_data);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void StreamStatus();
|
|
||||||
|
|
||||||
private:
|
|
||||||
WSServer *_srv;
|
|
||||||
uint64_t _streamStartTime;
|
|
||||||
uint64_t _lastBytesSent;
|
|
||||||
uint64_t _lastBytesSentTime;
|
|
||||||
void broadcastUpdate(const char *updateType, obs_data_t *additionalFields);
|
|
||||||
|
|
||||||
void OnSceneChange();
|
|
||||||
void OnSceneListChange();
|
|
||||||
|
|
||||||
void OnStreamStarting();
|
|
||||||
void OnStreamStarted();
|
|
||||||
void OnStreamStopping();
|
|
||||||
void OnStreamStopped();
|
|
||||||
|
|
||||||
void OnRecordingStarting();
|
|
||||||
void OnRecordingStarted();
|
|
||||||
void OnRecordingStopping();
|
|
||||||
void OnRecordingStopped();
|
|
||||||
|
|
||||||
void OnExit();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // WSEVENTS_H
|
|
@ -1,324 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "WSRequestHandler.h"
|
|
||||||
#include "obs-websocket.h"
|
|
||||||
#include "Config.h"
|
|
||||||
#include "Utils.h"
|
|
||||||
|
|
||||||
WSRequestHandler::WSRequestHandler(QWebSocket *client) :
|
|
||||||
_authenticated(false),
|
|
||||||
_messageId(0),
|
|
||||||
_requestType(""),
|
|
||||||
_requestData(nullptr)
|
|
||||||
{
|
|
||||||
_client = client;
|
|
||||||
|
|
||||||
messageMap["GetVersion"] = WSRequestHandler::HandleGetVersion;
|
|
||||||
messageMap["GetAuthRequired"] = WSRequestHandler::HandleGetAuthRequired;
|
|
||||||
messageMap["Authenticate"] = WSRequestHandler::HandleAuthenticate;
|
|
||||||
|
|
||||||
messageMap["SetCurrentScene"] = WSRequestHandler::HandleSetCurrentScene;
|
|
||||||
messageMap["GetCurrentScene"] = WSRequestHandler::HandleGetCurrentScene;
|
|
||||||
messageMap["GetSceneList"] = WSRequestHandler::HandleGetSceneList;
|
|
||||||
messageMap["SetSourceOrder"] = WSRequestHandler::ErrNotImplemented;
|
|
||||||
messageMap["SetSourceRender"] = WSRequestHandler::HandleSetSourceRender;
|
|
||||||
messageMap["SetSceneItemPositionAndSize"] = WSRequestHandler::ErrNotImplemented;
|
|
||||||
messageMap["GetStreamingStatus"] = WSRequestHandler::HandleGetStreamingStatus;
|
|
||||||
messageMap["StartStopStreaming"] = WSRequestHandler::HandleStartStopStreaming;
|
|
||||||
messageMap["StartStopRecording"] = WSRequestHandler::HandleStartStopRecording;
|
|
||||||
messageMap["ToggleMute"] = WSRequestHandler::ErrNotImplemented;
|
|
||||||
messageMap["GetVolumes"] = WSRequestHandler::ErrNotImplemented;
|
|
||||||
messageMap["SetVolume"] = WSRequestHandler::ErrNotImplemented;
|
|
||||||
|
|
||||||
messageMap["GetTransitionList"] = WSRequestHandler::HandleGetTransitionList;
|
|
||||||
messageMap["GetCurrentTransition"] = WSRequestHandler::HandleGetCurrentTransition;
|
|
||||||
messageMap["SetCurrentTransition"] = WSRequestHandler::HandleSetCurrentTransition;
|
|
||||||
|
|
||||||
authNotRequired.insert("GetVersion");
|
|
||||||
authNotRequired.insert("GetAuthRequired");
|
|
||||||
authNotRequired.insert("Authenticate");
|
|
||||||
|
|
||||||
blog(LOG_INFO, "[obs-websockets] new client connected from %s:%d", _client->peerAddress().toString().toLocal8Bit(), _client->peerPort());
|
|
||||||
|
|
||||||
connect(_client, &QWebSocket::textMessageReceived, this, &WSRequestHandler::processTextMessage);
|
|
||||||
connect(_client, &QWebSocket::disconnected, this, &WSRequestHandler::socketDisconnected);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::processTextMessage(QString textMessage) {
|
|
||||||
QByteArray msgData = textMessage.toLocal8Bit();
|
|
||||||
const char *msg = msgData;
|
|
||||||
|
|
||||||
_requestData = obs_data_create_from_json(msg);
|
|
||||||
if (!_requestData) {
|
|
||||||
blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", msg);
|
|
||||||
SendErrorResponse("invalid JSON payload");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestType = obs_data_get_string(_requestData, "request-type");
|
|
||||||
_messageId = obs_data_get_string(_requestData, "message-id");
|
|
||||||
|
|
||||||
if (Config::Current()->AuthRequired
|
|
||||||
&& !_authenticated
|
|
||||||
&& authNotRequired.find(_requestType) == authNotRequired.end())
|
|
||||||
{
|
|
||||||
SendErrorResponse("Not Authenticated");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
|
|
||||||
|
|
||||||
if (handlerFunc != NULL) {
|
|
||||||
handlerFunc(this);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SendErrorResponse("invalid request type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::socketDisconnected() {
|
|
||||||
blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", _client->peerAddress().toString().toStdString(), _client->peerPort());
|
|
||||||
|
|
||||||
_authenticated = false;
|
|
||||||
_client->deleteLater();
|
|
||||||
emit disconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::sendTextMessage(QString textMessage) {
|
|
||||||
_client->sendTextMessage(textMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
WSRequestHandler::~WSRequestHandler() {
|
|
||||||
if (_requestData != NULL) {
|
|
||||||
obs_data_release(_requestData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields) {
|
|
||||||
obs_data_t *response = obs_data_create();
|
|
||||||
obs_data_set_string(response, "status", "ok");
|
|
||||||
obs_data_set_string(response, "message-id", _messageId);
|
|
||||||
|
|
||||||
if (additionalFields != NULL) {
|
|
||||||
obs_data_apply(response, additionalFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
_client->sendTextMessage(obs_data_get_json(response));
|
|
||||||
|
|
||||||
obs_data_release(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::SendErrorResponse(const char *errorMessage) {
|
|
||||||
obs_data_t *response = obs_data_create();
|
|
||||||
obs_data_set_string(response, "status", "error");
|
|
||||||
obs_data_set_string(response, "error", errorMessage);
|
|
||||||
obs_data_set_string(response, "message-id", _messageId);
|
|
||||||
|
|
||||||
_client->sendTextMessage(obs_data_get_json(response));
|
|
||||||
|
|
||||||
obs_data_release(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetVersion(WSRequestHandler *owner) {
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_double(data, "version", 1.1);
|
|
||||||
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
|
||||||
obs_data_set_string(data, "obs-studio-version", OBS_VERSION);
|
|
||||||
owner->SendOKResponse(data);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) {
|
|
||||||
bool authRequired = Config::Current()->AuthRequired;
|
|
||||||
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_bool(data, "authRequired", authRequired);
|
|
||||||
|
|
||||||
if (authRequired) {
|
|
||||||
obs_data_set_string(data, "challenge", Config::Current()->SessionChallenge);
|
|
||||||
obs_data_set_string(data, "salt", Config::Current()->Salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
owner->SendOKResponse(data);
|
|
||||||
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner) {
|
|
||||||
const char *auth = obs_data_get_string(owner->_requestData, "auth");
|
|
||||||
if (!auth || strlen(auth) < 1) {
|
|
||||||
owner->SendErrorResponse("auth not specified!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(owner->_authenticated) && Config::Current()->CheckAuth(auth)) {
|
|
||||||
owner->_authenticated = true;
|
|
||||||
owner->SendOKResponse();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
owner->SendErrorResponse("Authentication Failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler *owner) {
|
|
||||||
const char *sceneName = obs_data_get_string(owner->_requestData, "scene-name");
|
|
||||||
obs_source_t *source = obs_get_source_by_name(sceneName);
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
obs_frontend_set_current_scene(source);
|
|
||||||
owner->SendOKResponse();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
owner->SendErrorResponse("requested scene does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_source_release(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner) {
|
|
||||||
obs_source_t *source = obs_frontend_get_current_scene();
|
|
||||||
const char *name = obs_source_get_name(source);
|
|
||||||
|
|
||||||
obs_data_array_t *scene_items = Utils::GetSceneItems(source);
|
|
||||||
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_string(data, "name", name);
|
|
||||||
obs_data_set_array(data, "sources", scene_items);
|
|
||||||
|
|
||||||
owner->SendOKResponse(data);
|
|
||||||
obs_data_release(data);
|
|
||||||
obs_data_array_release(scene_items);
|
|
||||||
//obs_source_release(source); // causes a source destroy sometimes
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetSceneList(WSRequestHandler *owner) {
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_string(data, "current-scene", obs_source_get_name(obs_frontend_get_current_scene()));
|
|
||||||
obs_data_set_array(data, "scenes", Utils::GetScenes());
|
|
||||||
|
|
||||||
owner->SendOKResponse(data);
|
|
||||||
|
|
||||||
//obs_data_release(data); // da hell ? sometimes causes a crash too, like in GetCurrentScene...
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleSetSourceRender(WSRequestHandler *owner) {
|
|
||||||
const char *itemName = obs_data_get_string(owner->_requestData, "source");
|
|
||||||
bool isVisible = obs_data_get_bool(owner->_requestData, "render");
|
|
||||||
if (itemName == NULL) {
|
|
||||||
owner->SendErrorResponse("invalid request parameters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_source_t* currentScene = obs_frontend_get_current_scene();
|
|
||||||
|
|
||||||
obs_sceneitem_t *sceneItem = Utils::GetSceneItemFromName(currentScene, itemName);
|
|
||||||
if (sceneItem != NULL) {
|
|
||||||
obs_sceneitem_set_visible(sceneItem, isVisible);
|
|
||||||
obs_sceneitem_release(sceneItem);
|
|
||||||
owner->SendOKResponse();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
owner->SendErrorResponse("specified scene item doesn't exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_source_release(currentScene);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler *owner) {
|
|
||||||
obs_data_t *data = obs_data_create();
|
|
||||||
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
|
|
||||||
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
|
|
||||||
obs_data_set_bool(data, "preview-only", false);
|
|
||||||
|
|
||||||
owner->SendOKResponse(data);
|
|
||||||
obs_data_release(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner) {
|
|
||||||
if (obs_frontend_streaming_active()) {
|
|
||||||
obs_frontend_streaming_stop();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
obs_frontend_streaming_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
owner->SendOKResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner) {
|
|
||||||
if (obs_frontend_recording_active()) {
|
|
||||||
obs_frontend_recording_stop();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
obs_frontend_recording_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
owner->SendOKResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) {
|
|
||||||
obs_frontend_source_list transitionList = {};
|
|
||||||
obs_frontend_get_transitions(&transitionList);
|
|
||||||
|
|
||||||
obs_data_array_t* transitions = obs_data_array_create();
|
|
||||||
for (size_t i = 0; i < (&transitionList)->sources.num; i++) {
|
|
||||||
obs_source_t* transition = (&transitionList)->sources.array[i];
|
|
||||||
|
|
||||||
obs_data_t *obj = obs_data_create();
|
|
||||||
obs_data_set_string(obj, "name", obs_source_get_name(transition));
|
|
||||||
|
|
||||||
obs_data_array_push_back(transitions, obj);
|
|
||||||
}
|
|
||||||
obs_frontend_source_list_free(&transitionList);
|
|
||||||
|
|
||||||
obs_data_t *response = obs_data_create();
|
|
||||||
obs_data_set_string(response, "current-transition", obs_source_get_name(obs_frontend_get_current_transition()));
|
|
||||||
obs_data_set_array(response, "transitions", transitions);
|
|
||||||
owner->SendOKResponse(response);
|
|
||||||
|
|
||||||
obs_data_release(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler *owner) {
|
|
||||||
obs_data_t *response = obs_data_create();
|
|
||||||
obs_data_set_string(response, "name", obs_source_get_name(obs_frontend_get_current_transition()));
|
|
||||||
owner->SendOKResponse(response);
|
|
||||||
|
|
||||||
obs_data_release(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler *owner) {
|
|
||||||
const char *name = obs_data_get_string(owner->_requestData, "transition-name");
|
|
||||||
obs_source_t *transition = obs_get_source_by_name(name);
|
|
||||||
|
|
||||||
if (transition) {
|
|
||||||
obs_frontend_set_current_transition(transition);
|
|
||||||
owner->SendOKResponse();
|
|
||||||
|
|
||||||
obs_source_release(transition);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
owner->SendErrorResponse("requested transition does not exist");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner) {
|
|
||||||
owner->SendErrorResponse("not implemented");
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef WSREQUESTHANDLER_H
|
|
||||||
#define WSREQUESTHANDLER_H
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <QtWebSockets/QWebSocket>
|
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
|
|
||||||
class WSRequestHandler : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit WSRequestHandler(QWebSocket *client);
|
|
||||||
~WSRequestHandler();
|
|
||||||
void sendTextMessage(QString textMessage);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void processTextMessage(QString textMessage);
|
|
||||||
void socketDisconnected();
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void disconnected();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QWebSocket *_client;
|
|
||||||
bool _authenticated;
|
|
||||||
const char *_messageId;
|
|
||||||
const char *_requestType;
|
|
||||||
obs_data_t *_requestData;
|
|
||||||
|
|
||||||
std::map<std::string, void(*)(WSRequestHandler*)> messageMap;
|
|
||||||
std::set<std::string> authNotRequired;
|
|
||||||
|
|
||||||
void SendOKResponse(obs_data_t *additionalFields = NULL);
|
|
||||||
void SendErrorResponse(const char *errorMessage);
|
|
||||||
static void ErrNotImplemented(WSRequestHandler *owner);
|
|
||||||
|
|
||||||
static void HandleGetVersion(WSRequestHandler *owner);
|
|
||||||
static void HandleGetAuthRequired(WSRequestHandler *owner);
|
|
||||||
static void HandleAuthenticate(WSRequestHandler *owner);
|
|
||||||
|
|
||||||
static void HandleSetCurrentScene(WSRequestHandler *owner);
|
|
||||||
static void HandleGetCurrentScene(WSRequestHandler *owner);
|
|
||||||
static void HandleGetSceneList(WSRequestHandler *owner);
|
|
||||||
static void HandleSetSourceRender(WSRequestHandler *owner);
|
|
||||||
|
|
||||||
static void HandleGetStreamingStatus(WSRequestHandler *owner);
|
|
||||||
static void HandleStartStopStreaming(WSRequestHandler *owner);
|
|
||||||
static void HandleStartStopRecording(WSRequestHandler *owner);
|
|
||||||
|
|
||||||
static void HandleGetTransitionList(WSRequestHandler *owner);
|
|
||||||
static void HandleGetCurrentTransition(WSRequestHandler *owner);
|
|
||||||
static void HandleSetCurrentTransition(WSRequestHandler *owner);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // WSPROTOCOL_H
|
|
81
WSServer.cpp
81
WSServer.cpp
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "WSServer.h"
|
|
||||||
#include "WSRequestHandler.h"
|
|
||||||
#include <QtWebSockets/QWebSocketServer>
|
|
||||||
#include <QtWebSockets/QWebSocket>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
|
|
||||||
QT_USE_NAMESPACE
|
|
||||||
|
|
||||||
WSServer::WSServer(quint16 port, QObject *parent) :
|
|
||||||
QObject(parent),
|
|
||||||
_wsServer(Q_NULLPTR),
|
|
||||||
_clients()
|
|
||||||
{
|
|
||||||
_serverThread = new QThread();
|
|
||||||
_wsServer = new QWebSocketServer(
|
|
||||||
QStringLiteral("OBS Websocket API"),
|
|
||||||
QWebSocketServer::NonSecureMode,
|
|
||||||
this);
|
|
||||||
_wsServer->moveToThread(_serverThread);
|
|
||||||
_serverThread->start();
|
|
||||||
|
|
||||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
|
||||||
if (serverStarted) {
|
|
||||||
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WSServer::~WSServer()
|
|
||||||
{
|
|
||||||
_wsServer->close();
|
|
||||||
qDeleteAll(_clients.begin(), _clients.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSServer::broadcast(QString message)
|
|
||||||
{
|
|
||||||
Q_FOREACH(WSRequestHandler *pClient, _clients) {
|
|
||||||
pClient->sendTextMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSServer::onNewConnection()
|
|
||||||
{
|
|
||||||
QWebSocket *pSocket = _wsServer->nextPendingConnection();
|
|
||||||
|
|
||||||
if (pSocket) {
|
|
||||||
WSRequestHandler *pHandler = new WSRequestHandler(pSocket);
|
|
||||||
|
|
||||||
connect(pHandler, &WSRequestHandler::disconnected, this, &WSServer::socketDisconnected);
|
|
||||||
_clients << pHandler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WSServer::socketDisconnected()
|
|
||||||
{
|
|
||||||
WSRequestHandler *pClient = qobject_cast<WSRequestHandler *>(sender());
|
|
||||||
|
|
||||||
if (pClient) {
|
|
||||||
_clients.removeAll(pClient);
|
|
||||||
pClient->deleteLater();
|
|
||||||
}
|
|
||||||
}
|
|
49
WSServer.h
49
WSServer.h
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef WSSERVER_H
|
|
||||||
#define WSSERVER_H
|
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
|
||||||
#include <QtCore/QList>
|
|
||||||
#include <QtCore/QByteArray>
|
|
||||||
#include "WSRequestHandler.h"
|
|
||||||
|
|
||||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
|
||||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
|
||||||
|
|
||||||
class WSServer : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit WSServer(quint16 port, QObject *parent = Q_NULLPTR);
|
|
||||||
virtual ~WSServer();
|
|
||||||
void broadcast(QString message);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void onNewConnection();
|
|
||||||
void socketDisconnected();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QWebSocketServer *_wsServer;
|
|
||||||
QList<WSRequestHandler *> _clients;
|
|
||||||
QThread *_serverThread;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // WSSERVER_H
|
|
40
appveyor.yml
Normal file
40
appveyor.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
environment:
|
||||||
|
CURL_VERSION: 7.39.0
|
||||||
|
|
||||||
|
install:
|
||||||
|
- git submodule update --init --recursive
|
||||||
|
- cd C:\projects\
|
||||||
|
- if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -
|
||||||
|
- 7z x dependencies2015.zip -odependencies2015
|
||||||
|
- set DepsPath32=%CD%\dependencies2015\win32
|
||||||
|
- set DepsPath64=%CD%\dependencies2015\win64
|
||||||
|
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd
|
||||||
|
- set build_config=RelWithDebInfo
|
||||||
|
- call C:\projects\obs-websocket\CI\install-build-obs.cmd
|
||||||
|
- cd C:\projects\obs-websocket\
|
||||||
|
- mkdir build32
|
||||||
|
- mkdir build64
|
||||||
|
- cd ./build32
|
||||||
|
- cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||||
|
- cd ../build64
|
||||||
|
- cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
|
||||||
|
before_deploy:
|
||||||
|
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
|
||||||
|
- set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
|
||||||
|
- iscc "C:\projects\obs-websocket\installer\installer.iss"
|
||||||
|
|
||||||
|
deploy_script:
|
||||||
|
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip"
|
||||||
|
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe"
|
||||||
|
|
||||||
|
test: off
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- C:\projects\dependencies2015.zip
|
||||||
|
- C:\projects\obs-studio-last-tag-built.txt
|
||||||
|
- C:\projects\obs-studio\
|
23
azure-pipelines.yml
Normal file
23
azure-pipelines.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
pool:
|
||||||
|
vmImage: 'macOS-10.13'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- script: ./CI/install-dependencies-macos.sh
|
||||||
|
displayName: 'Install Dependencies'
|
||||||
|
|
||||||
|
- script: ./CI/install-build-obs-macos.sh
|
||||||
|
displayName: 'Build OBS'
|
||||||
|
|
||||||
|
- script: ./CI/build-macos.sh
|
||||||
|
displayName: 'Build obs-websocket'
|
||||||
|
|
||||||
|
- script: ./CI/package-macos.sh
|
||||||
|
displayName: 'Package'
|
||||||
|
|
||||||
|
- task: PublishBuildArtifacts@1
|
||||||
|
inputs:
|
||||||
|
pathtoPublish: './release'
|
||||||
|
artifactName: 'build'
|
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
files:
|
||||||
|
- source: /data/locale/en-US.ini
|
||||||
|
translation: /data/locale/%locale%.ini
|
0
data/locale/ar-SA.ini
Normal file
0
data/locale/ar-SA.ini
Normal file
16
data/locale/de-DE.ini
Normal file
16
data/locale/de-DE.ini
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
OBSWebsocket.Settings.DialogTitle="WebSockets-Servereinstellungen"
|
||||||
|
OBSWebsocket.Settings.ServerEnable="WebSockets-Server aktivieren"
|
||||||
|
OBSWebsocket.Settings.ServerPort="Server-Port"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Authentifizierung aktivieren"
|
||||||
|
OBSWebsocket.Settings.Password="Passwort"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Infobereichbenachrichtigungen aktivieren"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Neue Websocket-Verbindung"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Client %1 verbunden"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="Websocket-Client getrennt"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Websocket-Serverfehler"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="Der WebSockets-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es mit anderen Einstellungen, einem OBS-Neustart oder einem Systemneustart erneut."
|
||||||
|
OBSWebsocket.ProfileChanged.Started="WebSockets-Server in diesem Profil aktiviert. Server gestartet."
|
||||||
|
OBSWebsocket.ProfileChanged.Stopped="WebSockets-Server in diesem Profil deaktiviert. Server gestoppt."
|
||||||
|
OBSWebsocket.ProfileChanged.Restarted="WebSockets-Server in diesem Profil geändert. Server startet neu."
|
@ -1,4 +1,16 @@
|
|||||||
Menu.SettingsItem="Websocket server settings"
|
OBSWebsocket.Settings.DialogTitle="WebSockets Server Settings"
|
||||||
Settings.DialogTitle="obs-websocket"
|
OBSWebsocket.Settings.ServerEnable="Enable WebSockets server"
|
||||||
Settings.AuthRequired="Enable authentication"
|
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||||
Settings.Password="Password"
|
OBSWebsocket.Settings.AuthRequired="Enable authentication"
|
||||||
|
OBSWebsocket.Settings.Password="Password"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Client %1 connected"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system."
|
||||||
|
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
|
||||||
|
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
|
||||||
|
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."
|
12
data/locale/es-ES.ini
Normal file
12
data/locale/es-ES.ini
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor WebSockets"
|
||||||
|
OBSWebsocket.Settings.ServerPort="Puerto del Servidor"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Habilitar autenticación"
|
||||||
|
OBSWebsocket.Settings.Password="Contraseña"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Habilitar alertas en la bandeja de sistema"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Falla en el servidor WebSockets"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="El servidor obs-websocket no se pudo iniciar, tal vez porque: \n - el puerto TCP %1 podría estar actualmente siendo usado este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en la configuración del servidor WebSocket, o detenga cualquier aplicación que pudiese estar utilizando este puerto \n - Un error de red desconocido ha afectado su sistema. Inténtelo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema."
|
@ -1,4 +1,16 @@
|
|||||||
Menu.SettingsItem="Paramètres du serveur Websocket"
|
OBSWebsocket.Settings.DialogTitle="Paramètres du serveur WebSockets"
|
||||||
Settings.DialogTitle="obs-websocket"
|
OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket"
|
||||||
Settings.AuthRequired="Activer l'authentification"
|
OBSWebsocket.Settings.ServerPort="Port du serveur"
|
||||||
Settings.Password="Mot de passe"
|
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
|
||||||
|
OBSWebsocket.Settings.Password="Mot de passe"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Débogage dans le fichier journal"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Notifications de connexion/déconnexion"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Nouvelle connexion WebSocket"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSockets"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="Le serveur WebSockets n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est en cours d'utilisation sur ce système, certainement par un autre programme. Essayez un port différent dans les réglages du serveur WebSocket, ou arrêtez tout programme susceptible d'utiliser ce port.\n - Une erreur réseau inconnue est survenue. Essayez à nouveau en modifiant vos réglages, en redémarrant OBS ou en redémarrant votre ordinateur."
|
||||||
|
OBSWebsocket.ProfileChanged.Started="Serveur WebSockets actif dans ce profil."
|
||||||
|
OBSWebsocket.ProfileChanged.Stopped="Serveur WebSockets désactivé dans ce profil."
|
||||||
|
OBSWebsocket.ProfileChanged.Restarted="Le port actuel diffère du port configuré dans ce profil. Serveur WebSockets redémarré."
|
||||||
|
0
data/locale/hi-IN.ini
Normal file
0
data/locale/hi-IN.ini
Normal file
12
data/locale/it-IT.ini
Normal file
12
data/locale/it-IT.ini
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
OBSWebsocket.Settings.ServerEnable="Abilitare il server WebSockets"
|
||||||
|
OBSWebsocket.Settings.ServerPort="Porta del server"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione"
|
||||||
|
OBSWebsocket.Settings.Password="Password"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Attivare la registrazione di debug"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Attivare gli avvisi di vassoio di sistema"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Nuova connessione WebSocket"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="%1 cliente collegato"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di WebSockets: \n - %1 porta TCP potrebbe essere attualmente in uso altrove su questo sistema, possibilmente da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server di WebSockets, o arrestare tutte le applicazioni che potrebbero utilizzare questa porta. \n - è verificato un errore di rete sconosciuto sul sistema. Riprova modificando le impostazioni, riavviare OBS o riavvio del sistema."
|
11
data/locale/ja-JP.ini
Normal file
11
data/locale/ja-JP.ini
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
OBSWebsocket.Settings.ServerEnable="WebSockets サーバーを有効にする"
|
||||||
|
OBSWebsocket.Settings.ServerPort="サーバーポート"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="認証を有効にする"
|
||||||
|
OBSWebsocket.Settings.Password="パスワード"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="デバッグログを有効にする"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="システムトレイ通知を有効にする"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="新しい WebSocket 接続"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="接続されているクライアント %1"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket クライアントが切断されました"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="切断されたクライアント %1"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="WebSockets サーバー障害"
|
0
data/locale/ko-KR.ini
Normal file
0
data/locale/ko-KR.ini
Normal file
9
data/locale/nl-NL.ini
Normal file
9
data/locale/nl-NL.ini
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
OBSWebsocket.Settings.ServerPort="Serverpoort"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Activeer debug logs"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Client %1 verbonden"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Fout in WebSocket server"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel elders wordt gebruikt op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort in te stellen in de WebSocket Server-instellingen of stop elke toepassing die deze poort zou kunnen gebruiken.\n Een onbekende Netwerkfout op uw systeem. Probeer het opnieuw door de instellingen te wijzigen, OBS te herstarten of uw systeem te herstarten."
|
10
data/locale/pl-PL.ini
Normal file
10
data/locale/pl-PL.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Wymagaj uwierzytelniania"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia w zasobniku systemowym"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Klient %1 połączony"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Klient %1 rozłączony"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSockets"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera WebSockets, może a powodu: \n - TCP port %1 może być obecnie używany gdzie indziej w tym systemie, możliwie przez inną aplikację. Spróbuj ustawić inny port TCP w ustawieniach serwera WebSockets, lub zatrzymać dowolną aplikację, która może używać tego portu \n -nieznany błąd sieci wydarzył się w systemie. Spróbuj ponownie, zmieniając ustawienia, ponownie uruchamiając OBS lub ponownie uruchamiając system."
|
12
data/locale/pt-PT.ini
Normal file
12
data/locale/pt-PT.ini
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets"
|
||||||
|
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Activar autenticação"
|
||||||
|
OBSWebsocket.Settings.Password="Palavra passe"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Habilitar registro de debug"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Ativar Alertas da bandeja do sistema"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Nova conexão WebSocket"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="O servidor de WebSockets falhou ao iniciar, talvez porque: \n - TCP port %1 pode estar atualmente em uso em outro lugar sobre este sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSockets, ou parar qualquer aplicativo que poderia estar usando este porto \n - um erro de rede desconhecido aconteceu no seu sistema. Tente novamente alterar configurações, reiniciando OBS ou reiniciando o sistema."
|
13
data/locale/ru-RU.ini
Normal file
13
data/locale/ru-RU.ini
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
OBSWebsocket.Settings.DialogTitle="Настройки сервера WebSockets"
|
||||||
|
OBSWebsocket.Settings.ServerEnable="Включить сервер WebSockets"
|
||||||
|
OBSWebsocket.Settings.ServerPort="Порт сервера"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Включить авторизацию"
|
||||||
|
OBSWebsocket.Settings.Password="Пароль"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="Новое соединение WebSocket"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSockets"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="Ошибка запуска сервера WebSockets. Вероятные причины:\n - Возможно, TCP-порт %1 занят другим приложением в системе. Попробуйте задать другой TCP-порт в настройках сервера WebSockets или закройте приложение, которое может использовать данный порт.\n - Произошла неизвестная сетевая ошибка в системе. Попробуйте снова после изменения настроек, перезапуска OBS или системы."
|
12
data/locale/zh-CN.ini
Normal file
12
data/locale/zh-CN.ini
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
OBSWebsocket.Settings.ServerEnable="启用 WebSockets 服务器"
|
||||||
|
OBSWebsocket.Settings.ServerPort="服务器端口"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="启用身份验证"
|
||||||
|
OBSWebsocket.Settings.Password="密码"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="启用调试日志"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="启用系统托盘通知"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="新 WebSocket 连接"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="客户端 %1 已连接"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客户端已断开"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="客户端 %1 已断开连接"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="WebSockets 服务器错误"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="WebSockets 服务器启动失败,可能是因为:\n - TCP 端口 %1 可能被本机的另一个应用程序占用。尝试在 WebSockets 服务器设置中设置不同的 TCP 端口,或关闭任何可能使用此端口的应用程序。\n - 在您的系统上发生了未知的网络错误。请尝试更改设置、重新启动 OBS 或重新启动系统。"
|
9
data/locale/zh-TW.ini
Normal file
9
data/locale/zh-TW.ini
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
OBSWebsocket.Settings.ServerPort="伺服器連接埠"
|
||||||
|
OBSWebsocket.Settings.DebugEnable="啟用除錯日誌"
|
||||||
|
OBSWebsocket.Settings.AlertsEnable="啟用系統列通知"
|
||||||
|
OBSWebsocket.NotifyConnect.Title="新的 WebSocket 連線"
|
||||||
|
OBSWebsocket.NotifyConnect.Message="客戶端 %1 已連線"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket 客戶端已離線"
|
||||||
|
OBSWebsocket.NotifyDisconnect.Message="客戶端 %1 已離線"
|
||||||
|
OBSWebsocket.Server.StartFailed.Title="WebSocket 伺服器錯誤"
|
||||||
|
OBSWebsocket.Server.StartFailed.Message="WebSockets 伺服器啟動失敗,可能的原因有:\n - TCP 連接埠 %1 被系統的其他程式所使用,試著為 WebSockets 伺服器指定不同的 TCP 連接埠,或是關閉任何可能使用此連接埠的程式。\n - 發生的未知的網路錯誤,試著更改設定、重新啟動 OBS 或是重新啟動您的系統。"
|
1
deps/asio
vendored
Submodule
1
deps/asio
vendored
Submodule
Submodule deps/asio added at b73dc1d2c0
1
deps/mbedtls
vendored
1
deps/mbedtls
vendored
Submodule deps/mbedtls deleted from 1a6a15c795
1
deps/websocketpp
vendored
Submodule
1
deps/websocketpp
vendored
Submodule
Submodule deps/websocketpp added at c6d7e295bf
11
docs/.editorconfig
Normal file
11
docs/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md, *.mustache]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
4
docs/.gitignore
vendored
Normal file
4
docs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
1
docs/.npmrc
Normal file
1
docs/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
package-lock=false
|
21
docs/README.md
Normal file
21
docs/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## Installation
|
||||||
|
|
||||||
|
Install node and update npm if necessary.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd obs-websocket/docs
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Just extract the comments.
|
||||||
|
npm run comments
|
||||||
|
|
||||||
|
# Just render the markdown.
|
||||||
|
npm run docs
|
||||||
|
|
||||||
|
# Do both comments and markdown.
|
||||||
|
npm run build
|
||||||
|
```
|
104
docs/comments.js
Normal file
104
docs/comments.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const glob = require('glob');
|
||||||
|
const parseComments = require('parse-comments');
|
||||||
|
const config = require('./config.json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read each file and call `parse-comments` on it.
|
||||||
|
*
|
||||||
|
* @param {String|Array} `files` List of file paths to read from.
|
||||||
|
* @return {Object|Array} Array of `parse-comments` objects.
|
||||||
|
*/
|
||||||
|
const parseFiles = files => {
|
||||||
|
let response = [];
|
||||||
|
files.forEach(file => {
|
||||||
|
const f = fs.readFileSync(file, 'utf8').toString();
|
||||||
|
response = response.concat(parseComments(f));
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters/sorts the results from `parse-comments`.
|
||||||
|
* @param {Object|Array} `comments` Array of `parse-comments` objects.
|
||||||
|
* @return {Object} Filtered comments sorted by `@api` and `@category`.
|
||||||
|
*/
|
||||||
|
const processComments = comments => {
|
||||||
|
let sorted = {};
|
||||||
|
let errors = [];
|
||||||
|
|
||||||
|
comments.forEach(comment => {
|
||||||
|
if (comment.typedef) {
|
||||||
|
comment.comment = undefined;
|
||||||
|
comment.context = undefined;
|
||||||
|
sorted['typedefs'] = sorted['typedefs'] || [];
|
||||||
|
sorted['typedefs'].push(comment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof comment.api === 'undefined') return;
|
||||||
|
let validationFailures = validateComment(comment);
|
||||||
|
|
||||||
|
if (validationFailures) {
|
||||||
|
errors.push(validationFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the object based on its api (ie. requests, events) and category (ie. general, scenes, etc).
|
||||||
|
comment.category = comment.category || 'miscellaneous';
|
||||||
|
|
||||||
|
// Remove some unnecessary properties to avoid result differences in travis.
|
||||||
|
comment.comment = undefined;
|
||||||
|
comment.context = undefined;
|
||||||
|
|
||||||
|
// Create an entry in sorted for the api/category if one does not exist.
|
||||||
|
sorted[comment.api] = sorted[comment.api] || {};
|
||||||
|
sorted[comment.api][comment.category] = sorted[comment.api][comment.category] || [];
|
||||||
|
|
||||||
|
// Store the comment in the appropriate api/category.
|
||||||
|
sorted[comment.api][comment.category].push(comment);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
throw JSON.stringify(errors, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rudimentary validation of documentation content, returns an error object or undefined.
|
||||||
|
const validateComment = comment => {
|
||||||
|
let errors = [];
|
||||||
|
[].concat(comment.params).concat(comment.returns).filter(Boolean).forEach(param => {
|
||||||
|
if (typeof param.name !== 'string' || param.name === '') {
|
||||||
|
errors.push({
|
||||||
|
description: `Invalid param or return value name`,
|
||||||
|
param: param
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof param.type !== 'string' || param.type === '') {
|
||||||
|
errors.push({
|
||||||
|
description: `Invalid param or return value type`,
|
||||||
|
param: param
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
return {
|
||||||
|
errors: errors,
|
||||||
|
fullContext: Object.assign({}, comment)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = glob.sync(config.srcGlob);
|
||||||
|
const comments = processComments(parseFiles(files));
|
||||||
|
|
||||||
|
if (!fs.existsSync(config.outDirectory)){
|
||||||
|
fs.mkdirSync(config.outDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(config.outDirectory, 'comments.json'), JSON.stringify(comments, null, 2));
|
5
docs/config.json
Normal file
5
docs/config.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"srcGlob": "./../src/**/*.@(cpp|h)",
|
||||||
|
"srcTemplate": "./protocol.hbs",
|
||||||
|
"outDirectory": "./generated"
|
||||||
|
}
|
37
docs/docs.js
Normal file
37
docs/docs.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const toc = require('markdown-toc');
|
||||||
|
const handlebars = require('handlebars');
|
||||||
|
const config = require('./config.json');
|
||||||
|
|
||||||
|
const helpers = require('handlebars-helpers')({
|
||||||
|
handlebars: handlebars
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allows pipe characters to be used within markdown tables.
|
||||||
|
handlebars.registerHelper('depipe', (text) => {
|
||||||
|
return typeof text === 'string' ? text.replace('|', '\\|') : text;
|
||||||
|
});
|
||||||
|
|
||||||
|
const insertHeader = (text) => {
|
||||||
|
return '<!-- This file was generated based on handlebars templates. Do not edit directly! -->\n\n' + text;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes `protocol.md` using `protocol.mustache`.
|
||||||
|
*
|
||||||
|
* @param {Object} `data` Data to assign to the mustache template.
|
||||||
|
*/
|
||||||
|
const generateProtocol = (templatePath, data) => {
|
||||||
|
const template = fs.readFileSync(templatePath).toString();
|
||||||
|
const generated = handlebars.compile(template)(data);
|
||||||
|
return insertHeader(toc.insert(generated));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!fs.existsSync(config.outDirectory)){
|
||||||
|
fs.mkdirSync(config.outDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const comments = fs.readFileSync(path.join(config.outDirectory, 'comments.json'), 'utf8');
|
||||||
|
const markdown = generateProtocol(config.srcTemplate, JSON.parse(comments));
|
||||||
|
fs.writeFileSync(path.join(config.outDirectory, 'protocol.md'), markdown);
|
7610
docs/generated/comments.json
Normal file
7610
docs/generated/comments.json
Normal file
File diff suppressed because it is too large
Load Diff
2978
docs/generated/protocol.md
Normal file
2978
docs/generated/protocol.md
Normal file
File diff suppressed because it is too large
Load Diff
21
docs/package.json
Normal file
21
docs/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "obs-websocket-docs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "docs.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"docs": "node ./docs.js",
|
||||||
|
"comments": "node ./comments.js",
|
||||||
|
"build": "npm run comments && npm run docs"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.2",
|
||||||
|
"handlebars": "^4.0.10",
|
||||||
|
"handlebars-helpers": "^0.9.6",
|
||||||
|
"markdown-toc": "^1.1.0",
|
||||||
|
"parse-comments": "^0.4.3"
|
||||||
|
}
|
||||||
|
}
|
11
docs/partials/eventsHeader.md
Normal file
11
docs/partials/eventsHeader.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Events
|
||||||
|
Events are broadcast by the server to each connected client when a recognized action occurs within OBS.
|
||||||
|
|
||||||
|
An event message will contain at least the following base fields:
|
||||||
|
- `update-type` _String_: the type of event.
|
||||||
|
- `stream-timecode` _String (optional)_: time elapsed between now and stream start (only present if OBS Studio is streaming).
|
||||||
|
- `rec-timecode` _String (optional)_: time elapsed between now and recording start (only present if OBS Studio is recording).
|
||||||
|
|
||||||
|
Timecodes are sent using the format: `HH:MM:SS.mmm`
|
||||||
|
|
||||||
|
Additional fields may be present in the event message depending on the event type.
|
34
docs/partials/introduction.md
Normal file
34
docs/partials/introduction.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# obs-websocket 4.6.0 protocol reference
|
||||||
|
|
||||||
|
# General Introduction
|
||||||
|
Messages are exchanged between the client and the server as JSON objects.
|
||||||
|
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
`obs-websocket` uses SHA256 to transmit credentials.
|
||||||
|
|
||||||
|
A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
|
||||||
|
- A `challenge`: a random string that will be used to generate the auth response.
|
||||||
|
- A `salt`: applied to the password when generating the auth response.
|
||||||
|
|
||||||
|
To generate the answer to the auth challenge, follow this procedure:
|
||||||
|
- Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`).
|
||||||
|
- Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`.
|
||||||
|
- Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`).
|
||||||
|
- Generate a binary SHA256 hash of the result and encode it to base64.
|
||||||
|
- Voilà, this last base64 string is the `auth response`. You may now use it to authenticate to the server with the [`Authenticate`](#authenticate) request.
|
||||||
|
|
||||||
|
Pseudo Code Example:
|
||||||
|
```
|
||||||
|
password = "supersecretpassword"
|
||||||
|
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
|
||||||
|
salt = "PZVbYpvAnZut2SS6JNJytDm9"
|
||||||
|
|
||||||
|
secret_string = password + salt
|
||||||
|
secret_hash = binary_sha256(secret_string)
|
||||||
|
secret = base64_encode(secret_hash)
|
||||||
|
|
||||||
|
auth_response_string = secret + challenge
|
||||||
|
auth_response_hash = binary_sha256(auth_response_string)
|
||||||
|
auth_response = base64_encode(auth_response_hash)
|
||||||
|
```
|
11
docs/partials/requestsHeader.md
Normal file
11
docs/partials/requestsHeader.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Requests
|
||||||
|
Requests are sent by the client and require at least the following two fields:
|
||||||
|
- `request-type` _String_: String name of the request type.
|
||||||
|
- `message-id` _String_: Client defined identifier for the message, will be echoed in the response.
|
||||||
|
|
||||||
|
Once a request is sent, the server will return a JSON response with at least the following fields:
|
||||||
|
- `message-id` _String_: The client defined identifier specified in the request.
|
||||||
|
- `status` _String_: Response status, will be one of the following: `ok`, `error`
|
||||||
|
- `error` _String_: An error message accompanying an `error` status.
|
||||||
|
|
||||||
|
Additional information may be required/returned depending on the request type. See below for more information.
|
2
docs/partials/typedefsHeader.md
Normal file
2
docs/partials/typedefsHeader.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Typedefs
|
||||||
|
These are complex types, such as `Source` and `Scene`, which are used as arguments or return values in multiple requests and/or events.
|
112
docs/protocol.hbs
Normal file
112
docs/protocol.hbs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{{#read "partials/introduction.md"}}{{/read}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{#read "partials/typedefsHeader.md"}}{{/read}}
|
||||||
|
|
||||||
|
{{#each typedefs}}
|
||||||
|
## {{typedefs.0.name}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each properties}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{#read "partials/eventsHeader.md"}}{{/read}}
|
||||||
|
|
||||||
|
{{#each events}}
|
||||||
|
## {{capitalizeAll @key}}
|
||||||
|
|
||||||
|
{{#each this}}
|
||||||
|
### {{name}}
|
||||||
|
|
||||||
|
{{#if deprecated}}
|
||||||
|
- **⚠️ Deprecated. {{deprecated}} ⚠️**
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#eq since "unreleased"}}
|
||||||
|
- Unreleased
|
||||||
|
{{else}}
|
||||||
|
- Added in v{{since}}
|
||||||
|
{{/eq}}
|
||||||
|
|
||||||
|
{{{description}}}
|
||||||
|
|
||||||
|
**Response Items:**
|
||||||
|
|
||||||
|
{{#if returns.length}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each returns}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
_No additional response items._
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{#read "partials/requestsHeader.md"}}{{/read}}
|
||||||
|
|
||||||
|
{{#each requests}}
|
||||||
|
## {{capitalizeAll @key}}
|
||||||
|
|
||||||
|
{{#each this}}
|
||||||
|
### {{name}}
|
||||||
|
|
||||||
|
{{#if deprecated}}
|
||||||
|
- **⚠️ Deprecated. {{deprecated}} ⚠️**
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#eq since "unreleased"}}
|
||||||
|
- Unreleased
|
||||||
|
{{else}}
|
||||||
|
- Added in v{{since}}
|
||||||
|
{{/eq}}
|
||||||
|
|
||||||
|
{{{description}}}
|
||||||
|
|
||||||
|
**Request Fields:**
|
||||||
|
|
||||||
|
{{#if params.length}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each params}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
_No specified parameters._
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
**Response Items:**
|
||||||
|
|
||||||
|
{{#if returns.length}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each returns}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
_No additional response items._
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
#include "settings-dialog.h"
|
|
||||||
#include "ui_settings-dialog.h"
|
|
||||||
#include "Config.h"
|
|
||||||
|
|
||||||
#define CHANGE_ME "changeme"
|
|
||||||
|
|
||||||
SettingsDialog::SettingsDialog(QWidget *parent) :
|
|
||||||
QDialog(parent),
|
|
||||||
ui(new Ui::SettingsDialog)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
|
|
||||||
connect(ui->authRequired, &QCheckBox::stateChanged, this, &SettingsDialog::AuthCheckboxChanged);
|
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::FormAccepted);
|
|
||||||
|
|
||||||
AuthCheckboxChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDialog::showEvent(QShowEvent *event) {
|
|
||||||
ui->authRequired->setChecked(Config::Current()->AuthRequired);
|
|
||||||
ui->password->setText(CHANGE_ME);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDialog::ToggleShowHide() {
|
|
||||||
if (!isVisible()) {
|
|
||||||
setVisible(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDialog::AuthCheckboxChanged() {
|
|
||||||
if (ui->authRequired->isChecked()) {
|
|
||||||
ui->password->setEnabled(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ui->password->setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsDialog::FormAccepted() {
|
|
||||||
if (ui->authRequired->isChecked()) {
|
|
||||||
if (ui->password->text() != CHANGE_ME) {
|
|
||||||
QByteArray pwd = ui->password->text().toLocal8Bit();
|
|
||||||
const char *new_password = pwd;
|
|
||||||
|
|
||||||
blog(LOG_INFO, "new password : %s", new_password);
|
|
||||||
Config::Current()->SetPassword(new_password);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(Config::Current()->Secret, "") != 0) {
|
|
||||||
Config::Current()->AuthRequired = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Config::Current()->AuthRequired = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Config::Current()->AuthRequired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_frontend_save();
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsDialog::~SettingsDialog()
|
|
||||||
{
|
|
||||||
delete ui;
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>SettingsDialog</class>
|
|
||||||
<widget class="QDialog" name="SettingsDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>354</width>
|
|
||||||
<height>110</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Settings.DialogTitle</string>
|
|
||||||
</property>
|
|
||||||
<property name="sizeGripEnabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<property name="sizeConstraint">
|
|
||||||
<enum>QLayout::SetDefaultConstraint</enum>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<layout class="QFormLayout" name="formLayout">
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="lbl_password">
|
|
||||||
<property name="text">
|
|
||||||
<string>Settings.Password</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QLineEdit" name="password">
|
|
||||||
<property name="echoMode">
|
|
||||||
<enum>QLineEdit::Password</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QCheckBox" name="authRequired">
|
|
||||||
<property name="text">
|
|
||||||
<string>Settings.AuthRequired</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>SettingsDialog</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>248</x>
|
|
||||||
<y>254</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>157</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>SettingsDialog</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>316</x>
|
|
||||||
<y>260</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>286</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
68
installer/installer.iss
Normal file
68
installer/installer.iss
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "obs-websocket"
|
||||||
|
#define MyAppVersion "4.6.0"
|
||||||
|
#define MyAppPublisher "Stephane Lepin"
|
||||||
|
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
|
; Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={code:GetDirName}
|
||||||
|
DefaultGroupName={#MyAppName}
|
||||||
|
OutputBaseFilename=obs-websocket-Windows-Installer
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\LICENSE"; Flags: dontcopy
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||||
|
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
procedure InitializeWizard();
|
||||||
|
var
|
||||||
|
GPLText: AnsiString;
|
||||||
|
Page: TOutputMsgMemoWizardPage;
|
||||||
|
begin
|
||||||
|
ExtractTemporaryFile('LICENSE');
|
||||||
|
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
|
||||||
|
|
||||||
|
Page := CreateOutputMsgMemoPage(wpWelcome,
|
||||||
|
'License Information', 'Please review the license terms before installing obs-websocket',
|
||||||
|
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
|
||||||
|
String(GPLText)
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// credit where it's due :
|
||||||
|
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||||
|
function GetDirName(Value: string): string;
|
||||||
|
var
|
||||||
|
InstallPath: string;
|
||||||
|
begin
|
||||||
|
// initialize default path, which will be returned when the following registry
|
||||||
|
// key queries fail due to missing keys or for some different reason
|
||||||
|
Result := '{pf}\obs-studio';
|
||||||
|
// query the first registry value; if this succeeds, return the obtained value
|
||||||
|
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||||
|
Result := InstallPath
|
||||||
|
end;
|
||||||
|
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
obs-websocket
|
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <obs-module.h>
|
|
||||||
#include <obs-frontend-api.h>
|
|
||||||
#include <QAction>
|
|
||||||
|
|
||||||
#include "obs-websocket.h"
|
|
||||||
#include "WSEvents.h"
|
|
||||||
#include "WSServer.h"
|
|
||||||
#include "Config.h"
|
|
||||||
#include "forms/settings-dialog.h"
|
|
||||||
|
|
||||||
OBS_DECLARE_MODULE()
|
|
||||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
|
||||||
|
|
||||||
WSEvents *eventHandler;
|
|
||||||
WSServer *server;
|
|
||||||
SettingsDialog *settings_dialog;
|
|
||||||
|
|
||||||
bool obs_module_load(void)
|
|
||||||
{
|
|
||||||
blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
|
||||||
|
|
||||||
server = new WSServer(4444);
|
|
||||||
eventHandler = new WSEvents(server);
|
|
||||||
|
|
||||||
obs_frontend_add_save_callback(Config::OBSSaveCallback, Config::Current());
|
|
||||||
|
|
||||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("Menu.SettingsItem"));
|
|
||||||
|
|
||||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
|
||||||
settings_dialog = new SettingsDialog();
|
|
||||||
obs_frontend_pop_ui_translation();
|
|
||||||
|
|
||||||
auto menu_cb = [] {
|
|
||||||
settings_dialog->ToggleShowHide();
|
|
||||||
};
|
|
||||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void obs_module_unload()
|
|
||||||
{
|
|
||||||
blog(LOG_INFO, "[obs-websockets] goodbye !");
|
|
||||||
}
|
|
||||||
|
|
286
src/Config.cpp
Normal file
286
src/Config.cpp
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
|
#include <QtCore/QCryptographicHash>
|
||||||
|
#include <QtCore/QTime>
|
||||||
|
#include <QtWidgets/QSystemTrayIcon>
|
||||||
|
|
||||||
|
#define SECTION_NAME "WebsocketAPI"
|
||||||
|
#define PARAM_ENABLE "ServerEnabled"
|
||||||
|
#define PARAM_PORT "ServerPort"
|
||||||
|
#define PARAM_DEBUG "DebugEnabled"
|
||||||
|
#define PARAM_ALERT "AlertsEnabled"
|
||||||
|
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||||
|
#define PARAM_SECRET "AuthSecret"
|
||||||
|
#define PARAM_SALT "AuthSalt"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "WSServer.h"
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#define QT_TO_UTF8(str) str.toUtf8().constData()
|
||||||
|
|
||||||
|
Config::Config() :
|
||||||
|
ServerEnabled(true),
|
||||||
|
ServerPort(4444),
|
||||||
|
DebugEnabled(false),
|
||||||
|
AlertsEnabled(true),
|
||||||
|
AuthRequired(false),
|
||||||
|
Secret(""),
|
||||||
|
Salt(""),
|
||||||
|
SettingsLoaded(false)
|
||||||
|
{
|
||||||
|
qsrand(QTime::currentTime().msec());
|
||||||
|
|
||||||
|
SetDefaults();
|
||||||
|
SessionChallenge = GenerateSalt();
|
||||||
|
|
||||||
|
obs_frontend_add_event_callback(OnFrontendEvent, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::~Config()
|
||||||
|
{
|
||||||
|
obs_frontend_remove_event_callback(OnFrontendEvent, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::Load()
|
||||||
|
{
|
||||||
|
config_t* obsConfig = GetConfigStore();
|
||||||
|
|
||||||
|
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
|
||||||
|
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
|
||||||
|
|
||||||
|
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
|
||||||
|
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
|
||||||
|
|
||||||
|
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||||
|
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
|
||||||
|
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::Save()
|
||||||
|
{
|
||||||
|
config_t* obsConfig = GetConfigStore();
|
||||||
|
|
||||||
|
config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||||
|
config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||||
|
|
||||||
|
config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||||
|
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
|
||||||
|
|
||||||
|
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||||
|
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
|
||||||
|
QT_TO_UTF8(Secret));
|
||||||
|
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
|
||||||
|
QT_TO_UTF8(Salt));
|
||||||
|
|
||||||
|
config_save(obsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::SetDefaults()
|
||||||
|
{
|
||||||
|
// OBS Config defaults
|
||||||
|
config_t* obsConfig = GetConfigStore();
|
||||||
|
if (obsConfig) {
|
||||||
|
config_set_default_bool(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||||
|
config_set_default_uint(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_PORT, ServerPort);
|
||||||
|
|
||||||
|
config_set_default_bool(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||||
|
config_set_default_bool(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_ALERT, AlertsEnabled);
|
||||||
|
|
||||||
|
config_set_default_bool(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||||
|
config_set_default_string(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_SECRET, QT_TO_UTF8(Secret));
|
||||||
|
config_set_default_string(obsConfig,
|
||||||
|
SECTION_NAME, PARAM_SALT, QT_TO_UTF8(Salt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config_t* Config::GetConfigStore()
|
||||||
|
{
|
||||||
|
return obs_frontend_get_profile_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Config::GenerateSalt()
|
||||||
|
{
|
||||||
|
// Generate 32 random chars
|
||||||
|
const size_t randomCount = 32;
|
||||||
|
QByteArray randomChars;
|
||||||
|
for (size_t i = 0; i < randomCount; i++) {
|
||||||
|
randomChars.append((char)qrand());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the 32 random chars to a base64 string
|
||||||
|
QString salt = randomChars.toBase64();
|
||||||
|
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Config::GenerateSecret(QString password, QString salt)
|
||||||
|
{
|
||||||
|
// Concatenate the password and the salt
|
||||||
|
QString passAndSalt = "";
|
||||||
|
passAndSalt += password;
|
||||||
|
passAndSalt += salt;
|
||||||
|
|
||||||
|
// Generate a SHA256 hash of the password and salt
|
||||||
|
auto challengeHash = QCryptographicHash::hash(
|
||||||
|
passAndSalt.toUtf8(),
|
||||||
|
QCryptographicHash::Algorithm::Sha256
|
||||||
|
);
|
||||||
|
|
||||||
|
// Encode SHA256 hash to Base64
|
||||||
|
QString challenge = challengeHash.toBase64();
|
||||||
|
|
||||||
|
return challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::SetPassword(QString password)
|
||||||
|
{
|
||||||
|
QString newSalt = GenerateSalt();
|
||||||
|
QString newChallenge = GenerateSecret(password, newSalt);
|
||||||
|
|
||||||
|
this->Salt = newSalt;
|
||||||
|
this->Secret = newChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Config::CheckAuth(QString response)
|
||||||
|
{
|
||||||
|
// Concatenate auth secret with the challenge sent to the user
|
||||||
|
QString challengeAndResponse = "";
|
||||||
|
challengeAndResponse += Secret;
|
||||||
|
challengeAndResponse += SessionChallenge;
|
||||||
|
|
||||||
|
// Generate a SHA256 hash of challengeAndResponse
|
||||||
|
auto hash = QCryptographicHash::hash(
|
||||||
|
challengeAndResponse.toUtf8(),
|
||||||
|
QCryptographicHash::Algorithm::Sha256
|
||||||
|
);
|
||||||
|
|
||||||
|
// Encode the SHA256 hash to Base64
|
||||||
|
QString expectedResponse = hash.toBase64();
|
||||||
|
|
||||||
|
bool authSuccess = false;
|
||||||
|
if (response == expectedResponse) {
|
||||||
|
SessionChallenge = GenerateSalt();
|
||||||
|
authSuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return authSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::OnFrontendEvent(enum obs_frontend_event event, void* param)
|
||||||
|
{
|
||||||
|
auto config = reinterpret_cast<Config*>(param);
|
||||||
|
|
||||||
|
if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
|
||||||
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
|
QString startMessage = QObject::tr("OBSWebsocket.ProfileChanged.Started");
|
||||||
|
QString stopMessage = QObject::tr("OBSWebsocket.ProfileChanged.Stopped");
|
||||||
|
QString restartMessage = QObject::tr("OBSWebsocket.ProfileChanged.Restarted");
|
||||||
|
obs_frontend_pop_ui_translation();
|
||||||
|
|
||||||
|
bool previousEnabled = config->ServerEnabled;
|
||||||
|
uint64_t previousPort = config->ServerPort;
|
||||||
|
|
||||||
|
config->SetDefaults();
|
||||||
|
config->Load();
|
||||||
|
|
||||||
|
if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort) {
|
||||||
|
auto server = GetServer();
|
||||||
|
server->stop();
|
||||||
|
|
||||||
|
if (config->ServerEnabled) {
|
||||||
|
server->start(config->ServerPort);
|
||||||
|
|
||||||
|
if (previousEnabled != config->ServerEnabled) {
|
||||||
|
Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information);
|
||||||
|
} else {
|
||||||
|
Utils::SysTrayNotify(restartMessage, QSystemTrayIcon::MessageIcon::Information);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Utils::SysTrayNotify(stopMessage, QSystemTrayIcon::MessageIcon::Information);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::MigrateFromGlobalSettings()
|
||||||
|
{
|
||||||
|
config_t* source = obs_frontend_get_global_config();
|
||||||
|
config_t* destination = obs_frontend_get_profile_config();
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) {
|
||||||
|
bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE);
|
||||||
|
config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) {
|
||||||
|
uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT);
|
||||||
|
config_set_uint(destination, SECTION_NAME, PARAM_PORT, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) {
|
||||||
|
bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG);
|
||||||
|
config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) {
|
||||||
|
bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT);
|
||||||
|
config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_ALERT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) {
|
||||||
|
bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||||
|
config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) {
|
||||||
|
const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET);
|
||||||
|
config_set_string(destination, SECTION_NAME, PARAM_SECRET, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_SECRET);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) {
|
||||||
|
const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT);
|
||||||
|
config_set_string(destination, SECTION_NAME, PARAM_SALT, value);
|
||||||
|
|
||||||
|
config_remove_value(source, SECTION_NAME, PARAM_SALT);
|
||||||
|
}
|
||||||
|
|
||||||
|
config_save(destination);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
obs-websocket
|
obs-websocket
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -16,35 +16,42 @@ You should have received a copy of the GNU General Public License along
|
|||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef CONFIG_H
|
#pragma once
|
||||||
#define CONFIG_H
|
|
||||||
|
|
||||||
#include <obs-module.h>
|
#include <obs-frontend-api.h>
|
||||||
#include <mbedtls/entropy.h>
|
#include <util/config-file.h>
|
||||||
#include <mbedtls/ctr_drbg.h>
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QSharedPointer>
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
public:
|
public:
|
||||||
Config();
|
Config();
|
||||||
~Config();
|
~Config();
|
||||||
void SetPassword(const char *password);
|
void Load();
|
||||||
bool CheckAuth(const char *userChallenge);
|
void Save();
|
||||||
const char* GenerateSalt();
|
void SetDefaults();
|
||||||
static const char* GenerateSecret(const char *password, const char *salt);
|
config_t* GetConfigStore();
|
||||||
static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *);
|
|
||||||
|
void MigrateFromGlobalSettings();
|
||||||
|
|
||||||
|
void SetPassword(QString password);
|
||||||
|
bool CheckAuth(QString userChallenge);
|
||||||
|
QString GenerateSalt();
|
||||||
|
static QString GenerateSecret(
|
||||||
|
QString password, QString salt);
|
||||||
|
|
||||||
|
bool ServerEnabled;
|
||||||
|
uint64_t ServerPort;
|
||||||
|
|
||||||
|
bool DebugEnabled;
|
||||||
|
bool AlertsEnabled;
|
||||||
|
|
||||||
bool AuthRequired;
|
bool AuthRequired;
|
||||||
const char *Secret;
|
QString Secret;
|
||||||
const char *Salt;
|
QString Salt;
|
||||||
const char *SessionChallenge;
|
QString SessionChallenge;
|
||||||
bool SettingsLoaded;
|
bool SettingsLoaded;
|
||||||
|
|
||||||
static Config* Current();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Config *_instance;
|
static void OnFrontendEvent(enum obs_frontend_event event, void* param);
|
||||||
mbedtls_entropy_context entropy;
|
|
||||||
mbedtls_ctr_drbg_context rng;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIG_H
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
obs-websocket
|
obs-websocket
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -16,21 +16,19 @@ You should have received a copy of the GNU General Public License along
|
|||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef UTILS_H
|
#include "ConnectionProperties.h"
|
||||||
#define UTILS_H
|
|
||||||
|
|
||||||
#include <obs-module.h>
|
ConnectionProperties::ConnectionProperties()
|
||||||
#include <obs-frontend-api.h>
|
: _authenticated(false)
|
||||||
|
|
||||||
class Utils
|
|
||||||
{
|
{
|
||||||
public:
|
}
|
||||||
static obs_data_array_t* GetSceneItems(obs_source_t *source);
|
|
||||||
static obs_data_t* GetSceneItemData(obs_scene_item *item);
|
|
||||||
static obs_sceneitem_t* GetSceneItemFromName(obs_source_t *source, const char* name);
|
|
||||||
|
|
||||||
static obs_data_array_t* GetScenes();
|
bool ConnectionProperties::isAuthenticated()
|
||||||
static obs_data_t* GetSceneData(obs_source *source);
|
{
|
||||||
};
|
return _authenticated.load();
|
||||||
|
}
|
||||||
|
|
||||||
#endif // UTILS_H
|
void ConnectionProperties::setAuthenticated(bool authenticated)
|
||||||
|
{
|
||||||
|
_authenticated.store(authenticated);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
obs-websocket
|
obs-websocket
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -16,9 +16,16 @@ You should have received a copy of the GNU General Public License along
|
|||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef OBSWEBSOCKET_H
|
#pragma once
|
||||||
#define OBSWEBSOCKET_H
|
|
||||||
|
|
||||||
#define OBS_WEBSOCKET_VERSION "0.3-alpha"
|
#include <atomic>
|
||||||
|
|
||||||
#endif // OBSWEBSOCKET_H
|
class ConnectionProperties
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit ConnectionProperties();
|
||||||
|
bool isAuthenticated();
|
||||||
|
void setAuthenticated(bool authenticated);
|
||||||
|
private:
|
||||||
|
std::atomic<bool> _authenticated;
|
||||||
|
};
|
777
src/Utils.cpp
Normal file
777
src/Utils.cpp
Normal file
@ -0,0 +1,777 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QtWidgets/QMainWindow>
|
||||||
|
#include <QtCore/QDir>
|
||||||
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
#include <obs.hpp>
|
||||||
|
#include "obs-websocket.h"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(OBSScene);
|
||||||
|
|
||||||
|
const QHash<obs_bounds_type, QString> boundTypeNames = {
|
||||||
|
{ OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH" },
|
||||||
|
{ OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER" },
|
||||||
|
{ OBS_BOUNDS_SCALE_OUTER, "OBS_BOUNDS_SCALE_OUTER" },
|
||||||
|
{ OBS_BOUNDS_SCALE_TO_WIDTH, "OBS_BOUNDS_SCALE_TO_WIDTH" },
|
||||||
|
{ OBS_BOUNDS_SCALE_TO_HEIGHT, "OBS_BOUNDS_SCALE_TO_HEIGHT" },
|
||||||
|
{ OBS_BOUNDS_MAX_ONLY, "OBS_BOUNDS_MAX_ONLY" },
|
||||||
|
{ OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE" },
|
||||||
|
};
|
||||||
|
|
||||||
|
QString getBoundsNameFromType(obs_bounds_type type) {
|
||||||
|
QString fallback = boundTypeNames.value(OBS_BOUNDS_NONE);
|
||||||
|
return boundTypeNames.value(type, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_bounds_type getBoundsTypeFromName(QString name) {
|
||||||
|
return boundTypeNames.key(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) {
|
||||||
|
obs_data_array_t* list = obs_data_array_create();
|
||||||
|
|
||||||
|
if (!strings || !key) {
|
||||||
|
return list; // empty list
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
char* value = nullptr;
|
||||||
|
|
||||||
|
do {
|
||||||
|
value = strings[index];
|
||||||
|
|
||||||
|
OBSDataAutoRelease item = obs_data_create();
|
||||||
|
obs_data_set_string(item, key, value);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
obs_data_array_push_back(list, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
} while (value != nullptr);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
|
||||||
|
obs_data_array_t* items = obs_data_array_create();
|
||||||
|
OBSScene scene = obs_scene_from_source(source);
|
||||||
|
|
||||||
|
if (!scene) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_scene_enum_items(scene, [](
|
||||||
|
obs_scene_t* scene,
|
||||||
|
obs_sceneitem_t* currentItem,
|
||||||
|
void* param)
|
||||||
|
{
|
||||||
|
obs_data_array_t* data = reinterpret_cast<obs_data_array_t*>(param);
|
||||||
|
|
||||||
|
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
|
||||||
|
obs_data_array_insert(data, 0, itemData);
|
||||||
|
return true;
|
||||||
|
}, items);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} `SceneItem` An OBS Scene Item.
|
||||||
|
* @property {Number} `cy`
|
||||||
|
* @property {Number} `cx`
|
||||||
|
* @property {String} `name` The name of this Scene Item.
|
||||||
|
* @property {int} `id` Scene item ID
|
||||||
|
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
|
||||||
|
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
|
||||||
|
* @property {Number} `source_cx`
|
||||||
|
* @property {Number} `source_cy`
|
||||||
|
* @property {String} `type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown"
|
||||||
|
* @property {Number} `volume`
|
||||||
|
* @property {Number} `x`
|
||||||
|
* @property {Number} `y`
|
||||||
|
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||||
|
* @property {Array<SceneItem> (optional)} `groupChildren` List of children (if this item is a group)
|
||||||
|
*/
|
||||||
|
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||||
|
if (!item) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 pos;
|
||||||
|
obs_sceneitem_get_pos(item, &pos);
|
||||||
|
|
||||||
|
vec2 scale;
|
||||||
|
obs_sceneitem_get_scale(item, &scale);
|
||||||
|
|
||||||
|
// obs_sceneitem_get_source doesn't increase the refcount
|
||||||
|
OBSSource itemSource = obs_sceneitem_get_source(item);
|
||||||
|
float item_width = float(obs_source_get_width(itemSource));
|
||||||
|
float item_height = float(obs_source_get_height(itemSource));
|
||||||
|
|
||||||
|
obs_data_t* data = obs_data_create();
|
||||||
|
obs_data_set_string(data, "name",
|
||||||
|
obs_source_get_name(itemSource));
|
||||||
|
obs_data_set_int(data, "id",
|
||||||
|
obs_sceneitem_get_id(item));
|
||||||
|
obs_data_set_string(data, "type",
|
||||||
|
obs_source_get_id(itemSource));
|
||||||
|
obs_data_set_double(data, "volume",
|
||||||
|
obs_source_get_volume(itemSource));
|
||||||
|
obs_data_set_double(data, "x", pos.x);
|
||||||
|
obs_data_set_double(data, "y", pos.y);
|
||||||
|
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||||
|
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||||
|
obs_data_set_double(data, "cx", item_width * scale.x);
|
||||||
|
obs_data_set_double(data, "cy", item_height * scale.y);
|
||||||
|
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||||
|
obs_data_set_bool(data, "locked", obs_sceneitem_locked(item));
|
||||||
|
|
||||||
|
obs_scene_t* parent = obs_sceneitem_get_scene(item);
|
||||||
|
if (parent) {
|
||||||
|
OBSSource parentSource = obs_scene_get_source(parent);
|
||||||
|
QString parentKind = obs_source_get_id(parentSource);
|
||||||
|
if (parentKind == "group") {
|
||||||
|
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_sceneitem_is_group(item)) {
|
||||||
|
OBSDataArrayAutoRelease children = obs_data_array_create();
|
||||||
|
obs_sceneitem_group_enum_items(item, [](obs_scene_t*, obs_sceneitem_t* currentItem, void* param) {
|
||||||
|
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
|
||||||
|
|
||||||
|
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
|
||||||
|
obs_data_array_push_back(items, itemData);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, children);
|
||||||
|
obs_data_set_array(data, "groupChildren", children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_source_t* source, obs_data_t* item) {
|
||||||
|
OBSSceneItem sceneItem;
|
||||||
|
if (obs_data_has_user_value(item, "id")) {
|
||||||
|
sceneItem = GetSceneItemFromId(source, obs_data_get_int(item, "id"));
|
||||||
|
if (obs_data_has_user_value(item, "name") &&
|
||||||
|
(QString)obs_source_get_name(obs_sceneitem_get_source(sceneItem)) !=
|
||||||
|
(QString)obs_data_get_string(item, "name")) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (obs_data_has_user_value(item, "name")) {
|
||||||
|
sceneItem = GetSceneItemFromName(source, obs_data_get_string(item, "name"));
|
||||||
|
}
|
||||||
|
return sceneItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name) {
|
||||||
|
struct current_search {
|
||||||
|
QString query;
|
||||||
|
obs_sceneitem_t* result;
|
||||||
|
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
||||||
|
};
|
||||||
|
|
||||||
|
current_search search;
|
||||||
|
search.query = name;
|
||||||
|
search.result = nullptr;
|
||||||
|
search.enumCallback = nullptr;
|
||||||
|
|
||||||
|
OBSScene scene = obs_scene_from_source(source);
|
||||||
|
if (!scene)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
search.enumCallback = [](
|
||||||
|
obs_scene_t* scene,
|
||||||
|
obs_sceneitem_t* currentItem,
|
||||||
|
void* param)
|
||||||
|
{
|
||||||
|
current_search* search = reinterpret_cast<current_search*>(param);
|
||||||
|
|
||||||
|
if (obs_sceneitem_is_group(currentItem)) {
|
||||||
|
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
|
||||||
|
if (search->result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString currentItemName =
|
||||||
|
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||||
|
|
||||||
|
if (currentItemName == search->query) {
|
||||||
|
search->result = currentItem;
|
||||||
|
obs_sceneitem_addref(search->result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
obs_scene_enum_items(scene, search.enumCallback, &search);
|
||||||
|
|
||||||
|
return search.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO refactor this to unify it with GetSceneItemFromName
|
||||||
|
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
|
||||||
|
struct current_search {
|
||||||
|
size_t query;
|
||||||
|
obs_sceneitem_t* result;
|
||||||
|
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
||||||
|
};
|
||||||
|
|
||||||
|
current_search search;
|
||||||
|
search.query = id;
|
||||||
|
search.result = nullptr;
|
||||||
|
search.enumCallback = nullptr;
|
||||||
|
|
||||||
|
OBSScene scene = obs_scene_from_source(source);
|
||||||
|
if (!scene)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
search.enumCallback = [](
|
||||||
|
obs_scene_t* scene,
|
||||||
|
obs_sceneitem_t* currentItem,
|
||||||
|
void* param)
|
||||||
|
{
|
||||||
|
current_search* search = reinterpret_cast<current_search*>(param);
|
||||||
|
|
||||||
|
if (obs_sceneitem_is_group(currentItem)) {
|
||||||
|
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, param);
|
||||||
|
if (search->result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_sceneitem_get_id(currentItem) == search->query) {
|
||||||
|
search->result = currentItem;
|
||||||
|
obs_sceneitem_addref(search->result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
obs_scene_enum_items(scene, search.enumCallback, &search);
|
||||||
|
|
||||||
|
return search.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::IsValidAlignment(const uint32_t alignment) {
|
||||||
|
switch (alignment) {
|
||||||
|
case OBS_ALIGN_CENTER:
|
||||||
|
case OBS_ALIGN_LEFT:
|
||||||
|
case OBS_ALIGN_RIGHT:
|
||||||
|
case OBS_ALIGN_TOP:
|
||||||
|
case OBS_ALIGN_BOTTOM:
|
||||||
|
case OBS_ALIGN_TOP | OBS_ALIGN_LEFT:
|
||||||
|
case OBS_ALIGN_TOP | OBS_ALIGN_RIGHT:
|
||||||
|
case OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT:
|
||||||
|
case OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_source_t* Utils::GetTransitionFromName(QString searchName) {
|
||||||
|
obs_source_t* foundTransition = nullptr;
|
||||||
|
|
||||||
|
obs_frontend_source_list transition_list = {};
|
||||||
|
obs_frontend_get_transitions(&transition_list);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||||
|
obs_source_t* transition = transition_list.sources.array[i];
|
||||||
|
QString transitionName = obs_source_get_name(transition);
|
||||||
|
|
||||||
|
if (transitionName == searchName) {
|
||||||
|
foundTransition = transition;
|
||||||
|
obs_source_addref(foundTransition);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_source_list_free(&transition_list);
|
||||||
|
return foundTransition;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_source_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
|
||||||
|
// Both obs_frontend_get_current_scene() and obs_get_source_by_name()
|
||||||
|
// do addref on the return source, so no need to use an OBSSource helper
|
||||||
|
obs_source_t* scene = nullptr;
|
||||||
|
|
||||||
|
if (sceneName.isEmpty() || sceneName.isNull())
|
||||||
|
scene = obs_frontend_get_current_scene();
|
||||||
|
else
|
||||||
|
scene = obs_get_source_by_name(sceneName.toUtf8());
|
||||||
|
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_data_array_t* Utils::GetScenes() {
|
||||||
|
obs_frontend_source_list sceneList = {};
|
||||||
|
obs_frontend_get_scenes(&sceneList);
|
||||||
|
|
||||||
|
obs_data_array_t* scenes = obs_data_array_create();
|
||||||
|
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||||
|
obs_source_t* scene = sceneList.sources.array[i];
|
||||||
|
OBSDataAutoRelease sceneData = GetSceneData(scene);
|
||||||
|
obs_data_array_push_back(scenes, sceneData);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_source_list_free(&sceneList);
|
||||||
|
return scenes;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_data_t* Utils::GetSceneData(obs_source_t* source) {
|
||||||
|
OBSDataArrayAutoRelease sceneItems = GetSceneItems(source);
|
||||||
|
|
||||||
|
obs_data_t* sceneData = obs_data_create();
|
||||||
|
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||||
|
obs_data_set_array(sceneData, "sources", sceneItems);
|
||||||
|
|
||||||
|
return sceneData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSpinBox* Utils::GetTransitionDurationControl() {
|
||||||
|
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
return window->findChild<QSpinBox*>("transitionDuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
int Utils::GetTransitionDuration() {
|
||||||
|
QSpinBox* control = GetTransitionDurationControl();
|
||||||
|
if (control)
|
||||||
|
return control->value();
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::SetTransitionDuration(int ms) {
|
||||||
|
QSpinBox* control = GetTransitionDurationControl();
|
||||||
|
if (control && ms >= 0)
|
||||||
|
control->setValue(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::SetTransitionByName(QString transitionName) {
|
||||||
|
OBSSourceAutoRelease transition = GetTransitionFromName(transitionName);
|
||||||
|
|
||||||
|
if (transition) {
|
||||||
|
obs_frontend_set_current_transition(transition);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton* Utils::GetPreviewModeButtonControl() {
|
||||||
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
return main->findChild<QPushButton*>("modeSwitch");
|
||||||
|
}
|
||||||
|
|
||||||
|
QLayout* Utils::GetPreviewLayout() {
|
||||||
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
return main->findChild<QLayout*>("previewLayout");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::TransitionToProgram() {
|
||||||
|
if (!obs_frontend_preview_program_mode_active())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
|
||||||
|
// then this won't work as expected
|
||||||
|
|
||||||
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
|
||||||
|
// The program options widget is the second item in the left-to-right layout
|
||||||
|
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
|
||||||
|
|
||||||
|
// The "Transition" button lies in the mainButtonLayout
|
||||||
|
// which is the first itemin the program options' layout
|
||||||
|
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
|
||||||
|
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
|
||||||
|
|
||||||
|
// Try to cast that widget into a button
|
||||||
|
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
|
||||||
|
|
||||||
|
// Perform a click on that button
|
||||||
|
transitionBtn->click();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Utils::OBSVersionString() {
|
||||||
|
uint32_t version = obs_get_version();
|
||||||
|
|
||||||
|
uint8_t major, minor, patch;
|
||||||
|
major = (version >> 24) & 0xFF;
|
||||||
|
minor = (version >> 16) & 0xFF;
|
||||||
|
patch = version & 0xFF;
|
||||||
|
|
||||||
|
QString result = QString("%1.%2.%3")
|
||||||
|
.arg(major).arg(minor).arg(patch);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSystemTrayIcon* Utils::GetTrayIcon() {
|
||||||
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
if (!main) return nullptr;
|
||||||
|
|
||||||
|
QList<QSystemTrayIcon*> trays = main->findChildren<QSystemTrayIcon*>();
|
||||||
|
return trays.isEmpty() ? nullptr : trays.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::SysTrayNotify(QString text,
|
||||||
|
QSystemTrayIcon::MessageIcon icon, QString title) {
|
||||||
|
if (!GetConfig()->AlertsEnabled ||
|
||||||
|
!QSystemTrayIcon::isSystemTrayAvailable() ||
|
||||||
|
!QSystemTrayIcon::supportsMessages())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSystemTrayIcon* trayIcon = GetTrayIcon();
|
||||||
|
if (trayIcon)
|
||||||
|
trayIcon->showMessage(title, text, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Utils::GetRecordingFolder() {
|
||||||
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
|
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||||
|
|
||||||
|
if (outputMode == "Advanced") {
|
||||||
|
// Advanced mode
|
||||||
|
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||||
|
} else {
|
||||||
|
// Simple mode
|
||||||
|
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::SetRecordingFolder(const char* path) {
|
||||||
|
QDir dir(path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
|
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||||
|
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||||
|
|
||||||
|
config_save(profile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Utils::ParseDataToQueryString(obs_data_t* data) {
|
||||||
|
if (!data)
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
QString query;
|
||||||
|
|
||||||
|
obs_data_item_t* item = obs_data_first(data);
|
||||||
|
if (item) {
|
||||||
|
bool isFirst = true;
|
||||||
|
do {
|
||||||
|
if (!obs_data_item_has_user_value(item))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!isFirst)
|
||||||
|
query += "&";
|
||||||
|
else
|
||||||
|
isFirst = false;
|
||||||
|
|
||||||
|
QString attrName = obs_data_item_get_name(item);
|
||||||
|
query += (attrName + "=");
|
||||||
|
|
||||||
|
switch (obs_data_item_gettype(item)) {
|
||||||
|
case OBS_DATA_BOOLEAN:
|
||||||
|
query += (obs_data_item_get_bool(item) ? "true" : "false");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_DATA_NUMBER:
|
||||||
|
switch (obs_data_item_numtype(item)) {
|
||||||
|
case OBS_DATA_NUM_DOUBLE:
|
||||||
|
query +=
|
||||||
|
QString::number(obs_data_item_get_double(item));
|
||||||
|
break;
|
||||||
|
case OBS_DATA_NUM_INT:
|
||||||
|
query +=
|
||||||
|
QString::number(obs_data_item_get_int(item));
|
||||||
|
break;
|
||||||
|
case OBS_DATA_NUM_INVALID:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_DATA_STRING:
|
||||||
|
query +=
|
||||||
|
QUrl::toPercentEncoding(
|
||||||
|
QString(obs_data_item_get_string(item)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
//other types are not supported
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (obs_data_item_next(&item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_hotkey_t* Utils::FindHotkeyByName(QString name) {
|
||||||
|
struct current_search {
|
||||||
|
QString query;
|
||||||
|
obs_hotkey_t* result;
|
||||||
|
};
|
||||||
|
|
||||||
|
current_search search;
|
||||||
|
search.query = name;
|
||||||
|
search.result = nullptr;
|
||||||
|
|
||||||
|
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
|
||||||
|
current_search* search = reinterpret_cast<current_search*>(data);
|
||||||
|
|
||||||
|
const char* hk_name = obs_hotkey_get_name(hotkey);
|
||||||
|
if (hk_name == search->query) {
|
||||||
|
search->result = hotkey;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, &search);
|
||||||
|
|
||||||
|
return search.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::ReplayBufferEnabled() {
|
||||||
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
|
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||||
|
|
||||||
|
if (outputMode == "Simple") {
|
||||||
|
return config_get_bool(profile, "SimpleOutput", "RecRB");
|
||||||
|
}
|
||||||
|
else if (outputMode == "Advanced") {
|
||||||
|
return config_get_bool(profile, "AdvOut", "RecRB");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utils::StartReplayBuffer() {
|
||||||
|
if (obs_frontend_replay_buffer_active())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!IsRPHotkeySet()) {
|
||||||
|
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
|
||||||
|
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
|
||||||
|
|
||||||
|
OBSData dummyBinding = obs_data_create();
|
||||||
|
obs_data_set_bool(dummyBinding, "control", true);
|
||||||
|
obs_data_set_bool(dummyBinding, "alt", true);
|
||||||
|
obs_data_set_bool(dummyBinding, "shift", true);
|
||||||
|
obs_data_set_bool(dummyBinding, "command", true);
|
||||||
|
obs_data_set_string(dummyBinding, "key", "OBS_KEY_0");
|
||||||
|
|
||||||
|
OBSDataArray rpSaveHotkey = obs_data_get_array(
|
||||||
|
outputHotkeys, "ReplayBuffer.Save");
|
||||||
|
obs_data_array_push_back(rpSaveHotkey, dummyBinding);
|
||||||
|
|
||||||
|
obs_hotkeys_load_output(rpOutput, outputHotkeys);
|
||||||
|
obs_frontend_replay_buffer_start();
|
||||||
|
|
||||||
|
obs_output_release(rpOutput);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obs_frontend_replay_buffer_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::IsRPHotkeySet() {
|
||||||
|
OBSOutputAutoRelease rpOutput = obs_frontend_get_replay_buffer_output();
|
||||||
|
OBSDataAutoRelease hotkeys = obs_hotkeys_save_output(rpOutput);
|
||||||
|
OBSDataArrayAutoRelease bindings = obs_data_get_array(hotkeys,
|
||||||
|
"ReplayBuffer.Save");
|
||||||
|
|
||||||
|
size_t count = obs_data_array_count(bindings);
|
||||||
|
return (count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Utils::GetFilenameFormatting() {
|
||||||
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
|
return config_get_string(profile, "Output", "FilenameFormatting");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::SetFilenameFormatting(const char* filenameFormatting) {
|
||||||
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
|
config_set_string(profile, "Output", "FilenameFormatting", filenameFormatting);
|
||||||
|
config_save(profile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} `SceneItemTransform`
|
||||||
|
* @property {int} `position.x` The x position of the scene item from the left.
|
||||||
|
* @property {int} `position.y` The y position of the scene item from the top.
|
||||||
|
* @property {int} `position.alignment` The point on the scene item that the item is manipulated from.
|
||||||
|
* @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment.
|
||||||
|
* @property {double} `scale.x` The x-scale factor of the scene item.
|
||||||
|
* @property {double} `scale.y` The y-scale factor of the scene item.
|
||||||
|
* @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling.
|
||||||
|
* @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling.
|
||||||
|
* @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling.
|
||||||
|
* @property {int} `crop.left` The number of pixels cropped off the left of the scene item before scaling.
|
||||||
|
* @property {bool} `visible` If the scene item is visible.
|
||||||
|
* @property {bool} `locked` If the scene item is locked in position.
|
||||||
|
* @property {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||||
|
* @property {int} `bounds.alignment` Alignment of the bounding box.
|
||||||
|
* @property {double} `bounds.x` Width of the bounding box.
|
||||||
|
* @property {double} `bounds.y` Height of the bounding box.
|
||||||
|
* @property {int} `sourceWidth` Base width (without scaling) of the source
|
||||||
|
* @property {int} `sourceHeight` Base source (without scaling) of the source
|
||||||
|
* @property {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
|
||||||
|
* @property {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
|
||||||
|
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||||
|
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
|
||||||
|
*/
|
||||||
|
obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
|
||||||
|
if (!sceneItem) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSource source = obs_sceneitem_get_source(sceneItem);
|
||||||
|
uint32_t baseSourceWidth = obs_source_get_width(source);
|
||||||
|
uint32_t baseSourceHeight = obs_source_get_height(source);
|
||||||
|
|
||||||
|
vec2 pos, scale, bounds;
|
||||||
|
obs_sceneitem_crop crop;
|
||||||
|
|
||||||
|
obs_sceneitem_get_pos(sceneItem, &pos);
|
||||||
|
obs_sceneitem_get_scale(sceneItem, &scale);
|
||||||
|
obs_sceneitem_get_crop(sceneItem, &crop);
|
||||||
|
obs_sceneitem_get_bounds(sceneItem, &bounds);
|
||||||
|
|
||||||
|
uint32_t alignment = obs_sceneitem_get_alignment(sceneItem);
|
||||||
|
float rotation = obs_sceneitem_get_rot(sceneItem);
|
||||||
|
bool isVisible = obs_sceneitem_visible(sceneItem);
|
||||||
|
bool isLocked = obs_sceneitem_locked(sceneItem);
|
||||||
|
|
||||||
|
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
|
||||||
|
uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem);
|
||||||
|
QString boundsTypeName = getBoundsNameFromType(boundsType);
|
||||||
|
|
||||||
|
OBSDataAutoRelease posData = obs_data_create();
|
||||||
|
obs_data_set_double(posData, "x", pos.x);
|
||||||
|
obs_data_set_double(posData, "y", pos.y);
|
||||||
|
obs_data_set_int(posData, "alignment", alignment);
|
||||||
|
|
||||||
|
OBSDataAutoRelease scaleData = obs_data_create();
|
||||||
|
obs_data_set_double(scaleData, "x", scale.x);
|
||||||
|
obs_data_set_double(scaleData, "y", scale.y);
|
||||||
|
|
||||||
|
OBSDataAutoRelease cropData = obs_data_create();
|
||||||
|
obs_data_set_int(cropData, "left", crop.left);
|
||||||
|
obs_data_set_int(cropData, "top", crop.top);
|
||||||
|
obs_data_set_int(cropData, "right", crop.right);
|
||||||
|
obs_data_set_int(cropData, "bottom", crop.bottom);
|
||||||
|
|
||||||
|
OBSDataAutoRelease boundsData = obs_data_create();
|
||||||
|
obs_data_set_string(boundsData, "type", boundsTypeName.toUtf8());
|
||||||
|
obs_data_set_int(boundsData, "alignment", boundsAlignment);
|
||||||
|
obs_data_set_double(boundsData, "x", bounds.x);
|
||||||
|
obs_data_set_double(boundsData, "y", bounds.y);
|
||||||
|
|
||||||
|
obs_data_t* data = obs_data_create();
|
||||||
|
obs_data_set_obj(data, "position", posData);
|
||||||
|
obs_data_set_double(data, "rotation", rotation);
|
||||||
|
obs_data_set_obj(data, "scale", scaleData);
|
||||||
|
obs_data_set_obj(data, "crop", cropData);
|
||||||
|
obs_data_set_bool(data, "visible", isVisible);
|
||||||
|
obs_data_set_bool(data, "locked", isLocked);
|
||||||
|
obs_data_set_obj(data, "bounds", boundsData);
|
||||||
|
|
||||||
|
obs_data_set_int(data, "sourceWidth", baseSourceWidth);
|
||||||
|
obs_data_set_int(data, "sourceHeight", baseSourceHeight);
|
||||||
|
obs_data_set_double(data, "width", baseSourceWidth * scale.x);
|
||||||
|
obs_data_set_double(data, "height", baseSourceHeight * scale.y);
|
||||||
|
|
||||||
|
obs_scene_t* parent = obs_sceneitem_get_scene(sceneItem);
|
||||||
|
if (parent) {
|
||||||
|
OBSSource parentSource = obs_scene_get_source(parent);
|
||||||
|
QString parentKind = obs_source_get_id(parentSource);
|
||||||
|
if (parentKind == "group") {
|
||||||
|
obs_data_set_string(data, "parentGroupName", obs_source_get_name(parentSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_sceneitem_is_group(sceneItem)) {
|
||||||
|
OBSDataArrayAutoRelease children = obs_data_array_create();
|
||||||
|
obs_sceneitem_group_enum_items(sceneItem, [](obs_scene_t*, obs_sceneitem_t* subItem, void* param) {
|
||||||
|
obs_data_array_t* items = reinterpret_cast<obs_data_array_t*>(param);
|
||||||
|
|
||||||
|
OBSDataAutoRelease itemData = GetSceneItemPropertiesData(subItem);
|
||||||
|
obs_data_array_push_back(items, itemData);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, children);
|
||||||
|
obs_data_set_array(data, "groupChildren", children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
|
||||||
|
{
|
||||||
|
struct enum_params {
|
||||||
|
obs_data_array_t* filters;
|
||||||
|
bool includeSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct enum_params enumParams;
|
||||||
|
|
||||||
|
enumParams.filters = obs_data_array_create();
|
||||||
|
enumParams.includeSettings = includeSettings;
|
||||||
|
|
||||||
|
obs_source_enum_filters(source, [](obs_source_t* parent, obs_source_t* child, void* param)
|
||||||
|
{
|
||||||
|
auto enumParams = reinterpret_cast<struct enum_params*>(param);
|
||||||
|
|
||||||
|
OBSDataAutoRelease filter = obs_data_create();
|
||||||
|
obs_data_set_string(filter, "type", obs_source_get_id(child));
|
||||||
|
obs_data_set_string(filter, "name", obs_source_get_name(child));
|
||||||
|
if (enumParams->includeSettings) {
|
||||||
|
obs_data_set_obj(filter, "settings", obs_source_get_settings(child));
|
||||||
|
}
|
||||||
|
obs_data_array_push_back(enumParams->filters, filter);
|
||||||
|
}, &enumParams);
|
||||||
|
|
||||||
|
return enumParams.filters;
|
||||||
|
}
|
85
src/Utils.h
Normal file
85
src/Utils.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtWidgets/QSpinBox>
|
||||||
|
#include <QtWidgets/QPushButton>
|
||||||
|
#include <QtWidgets/QLayout>
|
||||||
|
#include <QtWidgets/QListWidget>
|
||||||
|
#include <QtWidgets/QSystemTrayIcon>
|
||||||
|
|
||||||
|
#include <obs.hpp>
|
||||||
|
#include <obs-module.h>
|
||||||
|
#include <util/config-file.h>
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
public:
|
||||||
|
static obs_data_array_t* StringListToArray(char** strings, const char* key);
|
||||||
|
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||||
|
static obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
|
||||||
|
static obs_sceneitem_t* GetSceneItemFromName(
|
||||||
|
obs_source_t* source, QString name);
|
||||||
|
static obs_sceneitem_t* GetSceneItemFromId(obs_source_t* source, size_t id);
|
||||||
|
static obs_sceneitem_t* GetSceneItemFromItem(obs_source_t* source, obs_data_t* item);
|
||||||
|
static obs_source_t* GetTransitionFromName(QString transitionName);
|
||||||
|
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName);
|
||||||
|
static obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
|
||||||
|
|
||||||
|
static obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
|
||||||
|
|
||||||
|
static bool IsValidAlignment(const uint32_t alignment);
|
||||||
|
|
||||||
|
static obs_data_array_t* GetScenes();
|
||||||
|
static obs_data_t* GetSceneData(obs_source_t* source);
|
||||||
|
|
||||||
|
// TODO contribute a proper frontend API method for this to OBS and remove this hack
|
||||||
|
static QSpinBox* GetTransitionDurationControl();
|
||||||
|
static int GetTransitionDuration();
|
||||||
|
static void SetTransitionDuration(int ms);
|
||||||
|
|
||||||
|
static bool SetTransitionByName(QString transitionName);
|
||||||
|
|
||||||
|
static QPushButton* GetPreviewModeButtonControl();
|
||||||
|
static QLayout* GetPreviewLayout();
|
||||||
|
|
||||||
|
// TODO contribute a proper frontend API method for this to OBS and remove this hack
|
||||||
|
static void TransitionToProgram();
|
||||||
|
|
||||||
|
static QString OBSVersionString();
|
||||||
|
|
||||||
|
static QSystemTrayIcon* GetTrayIcon();
|
||||||
|
static void SysTrayNotify(
|
||||||
|
QString text,
|
||||||
|
QSystemTrayIcon::MessageIcon n,
|
||||||
|
QString title = QString("obs-websocket"));
|
||||||
|
|
||||||
|
static const char* GetRecordingFolder();
|
||||||
|
static bool SetRecordingFolder(const char* path);
|
||||||
|
|
||||||
|
static QString ParseDataToQueryString(obs_data_t* data);
|
||||||
|
static obs_hotkey_t* FindHotkeyByName(QString name);
|
||||||
|
static bool ReplayBufferEnabled();
|
||||||
|
static void StartReplayBuffer();
|
||||||
|
static bool IsRPHotkeySet();
|
||||||
|
static const char* GetFilenameFormatting();
|
||||||
|
static bool SetFilenameFormatting(const char* filenameFormatting);
|
||||||
|
};
|
1521
src/WSEvents.cpp
Normal file
1521
src/WSEvents.cpp
Normal file
File diff suppressed because it is too large
Load Diff
130
src/WSEvents.h
Normal file
130
src/WSEvents.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <obs.hpp>
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
#include <util/platform.h>
|
||||||
|
|
||||||
|
#include <QtWidgets/QListWidgetItem>
|
||||||
|
#include <QtCore/QSharedPointer>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
|
||||||
|
#include "WSServer.h"
|
||||||
|
|
||||||
|
class WSEvents : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WSEvents(WSServerPtr srv);
|
||||||
|
~WSEvents();
|
||||||
|
|
||||||
|
void connectSourceSignals(obs_source_t* source);
|
||||||
|
void disconnectSourceSignals(obs_source_t* source);
|
||||||
|
|
||||||
|
uint64_t GetStreamingTime();
|
||||||
|
const char* GetStreamingTimecode();
|
||||||
|
uint64_t GetRecordingTime();
|
||||||
|
const char* GetRecordingTimecode();
|
||||||
|
obs_data_t* GetStats();
|
||||||
|
|
||||||
|
bool HeartbeatIsActive;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void StreamStatus();
|
||||||
|
void Heartbeat();
|
||||||
|
void TransitionDurationChanged(int ms);
|
||||||
|
|
||||||
|
private:
|
||||||
|
WSServerPtr _srv;
|
||||||
|
QTimer streamStatusTimer;
|
||||||
|
QTimer heartbeatTimer;
|
||||||
|
os_cpu_usage_info_t* cpuUsageInfo;
|
||||||
|
|
||||||
|
bool pulse;
|
||||||
|
|
||||||
|
uint64_t _streamStarttime;
|
||||||
|
uint64_t _recStarttime;
|
||||||
|
|
||||||
|
uint64_t _lastBytesSent;
|
||||||
|
uint64_t _lastBytesSentTime;
|
||||||
|
|
||||||
|
void broadcastUpdate(const char* updateType,
|
||||||
|
obs_data_t* additionalFields);
|
||||||
|
|
||||||
|
void OnSceneChange();
|
||||||
|
void OnSceneListChange();
|
||||||
|
void OnSceneCollectionChange();
|
||||||
|
void OnSceneCollectionListChange();
|
||||||
|
|
||||||
|
void OnTransitionChange();
|
||||||
|
void OnTransitionListChange();
|
||||||
|
|
||||||
|
void OnProfileChange();
|
||||||
|
void OnProfileListChange();
|
||||||
|
|
||||||
|
void OnStreamStarting();
|
||||||
|
void OnStreamStarted();
|
||||||
|
void OnStreamStopping();
|
||||||
|
void OnStreamStopped();
|
||||||
|
|
||||||
|
void OnRecordingStarting();
|
||||||
|
void OnRecordingStarted();
|
||||||
|
void OnRecordingStopping();
|
||||||
|
void OnRecordingStopped();
|
||||||
|
|
||||||
|
void OnReplayStarting();
|
||||||
|
void OnReplayStarted();
|
||||||
|
void OnReplayStopping();
|
||||||
|
void OnReplayStopped();
|
||||||
|
|
||||||
|
void OnStudioModeSwitched(bool enabled);
|
||||||
|
void OnPreviewSceneChanged();
|
||||||
|
|
||||||
|
void OnExit();
|
||||||
|
|
||||||
|
static void FrontendEventHandler(
|
||||||
|
enum obs_frontend_event event, void* privateData);
|
||||||
|
|
||||||
|
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||||
|
|
||||||
|
static void OnSourceCreate(void* param, calldata_t* data);
|
||||||
|
static void OnSourceDestroy(void* param, calldata_t* data);
|
||||||
|
|
||||||
|
static void OnSourceVolumeChange(void* param, calldata_t* data);
|
||||||
|
static void OnSourceMuteStateChange(void* param, calldata_t* data);
|
||||||
|
static void OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data);
|
||||||
|
static void OnSourceAudioMixersChanged(void* param, calldata_t* data);
|
||||||
|
|
||||||
|
static void OnSourceRename(void* param, calldata_t* data);
|
||||||
|
|
||||||
|
static void OnSourceFilterAdded(void* param, calldata_t* data);
|
||||||
|
static void OnSourceFilterRemoved(void* param, calldata_t* data);
|
||||||
|
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
|
||||||
|
|
||||||
|
static void OnSceneReordered(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemTransform(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemSelected(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemDeselected(void* param, calldata_t* data);
|
||||||
|
};
|
227
src/WSRequestHandler.cpp
Normal file
227
src/WSRequestHandler.cpp
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
/**
|
||||||
|
* obs-websocket
|
||||||
|
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
* Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <obs-data.h>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
|
||||||
|
{ "GetVersion", WSRequestHandler::HandleGetVersion },
|
||||||
|
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
|
||||||
|
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
|
||||||
|
|
||||||
|
{ "GetStats", WSRequestHandler::HandleGetStats },
|
||||||
|
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
|
||||||
|
{ "GetVideoInfo", WSRequestHandler::HandleGetVideoInfo },
|
||||||
|
|
||||||
|
{ "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
|
||||||
|
{ "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
|
||||||
|
|
||||||
|
{ "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
|
||||||
|
{ "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
|
||||||
|
{ "GetSceneList", WSRequestHandler::HandleGetSceneList },
|
||||||
|
|
||||||
|
{ "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
|
||||||
|
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
|
||||||
|
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
|
||||||
|
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
|
||||||
|
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
|
||||||
|
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
|
||||||
|
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
|
||||||
|
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
|
||||||
|
{ "DeleteSceneItem", WSRequestHandler::HandleDeleteSceneItem },
|
||||||
|
{ "DuplicateSceneItem", WSRequestHandler::HandleDuplicateSceneItem },
|
||||||
|
{ "ReorderSceneItems", WSRequestHandler::HandleReorderSceneItems },
|
||||||
|
|
||||||
|
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
|
||||||
|
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
|
||||||
|
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
|
||||||
|
{ "StartStreaming", WSRequestHandler::HandleStartStreaming },
|
||||||
|
{ "StopStreaming", WSRequestHandler::HandleStopStreaming },
|
||||||
|
{ "StartRecording", WSRequestHandler::HandleStartRecording },
|
||||||
|
{ "StopRecording", WSRequestHandler::HandleStopRecording },
|
||||||
|
|
||||||
|
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
|
||||||
|
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
|
||||||
|
{ "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
|
||||||
|
{ "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
|
||||||
|
|
||||||
|
{ "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder },
|
||||||
|
{ "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
|
||||||
|
|
||||||
|
{ "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
|
||||||
|
{ "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
|
||||||
|
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
|
||||||
|
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
|
||||||
|
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
|
||||||
|
|
||||||
|
{ "SetVolume", WSRequestHandler::HandleSetVolume },
|
||||||
|
{ "GetVolume", WSRequestHandler::HandleGetVolume },
|
||||||
|
{ "ToggleMute", WSRequestHandler::HandleToggleMute },
|
||||||
|
{ "SetMute", WSRequestHandler::HandleSetMute },
|
||||||
|
{ "GetMute", WSRequestHandler::HandleGetMute },
|
||||||
|
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
|
||||||
|
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
|
||||||
|
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
|
||||||
|
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
|
||||||
|
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
|
||||||
|
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
|
||||||
|
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
|
||||||
|
{ "TakeSourceScreenshot", WSRequestHandler::HandleTakeSourceScreenshot },
|
||||||
|
|
||||||
|
{ "GetSourceFilters", WSRequestHandler::HandleGetSourceFilters },
|
||||||
|
{ "AddFilterToSource", WSRequestHandler::HandleAddFilterToSource },
|
||||||
|
{ "RemoveFilterFromSource", WSRequestHandler::HandleRemoveFilterFromSource },
|
||||||
|
{ "ReorderSourceFilter", WSRequestHandler::HandleReorderSourceFilter },
|
||||||
|
{ "MoveSourceFilter", WSRequestHandler::HandleMoveSourceFilter },
|
||||||
|
{ "SetSourceFilterSettings", WSRequestHandler::HandleSetSourceFilterSettings },
|
||||||
|
|
||||||
|
{ "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
|
||||||
|
{ "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
|
||||||
|
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
|
||||||
|
|
||||||
|
{ "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
|
||||||
|
{ "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
|
||||||
|
{ "ListProfiles", WSRequestHandler::HandleListProfiles },
|
||||||
|
|
||||||
|
{ "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
|
||||||
|
{ "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
|
||||||
|
{ "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
|
||||||
|
#if BUILD_CAPTIONS
|
||||||
|
{ "SendCaptions", WSRequestHandler::HandleSendCaptions },
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{ "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
|
||||||
|
{ "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
|
||||||
|
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
|
||||||
|
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
|
||||||
|
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
|
||||||
|
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
|
||||||
|
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
|
||||||
|
|
||||||
|
{ "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
|
||||||
|
{ "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
|
||||||
|
|
||||||
|
{ "SetTextFreetype2Properties", WSRequestHandler::HandleSetTextFreetype2Properties },
|
||||||
|
{ "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },
|
||||||
|
|
||||||
|
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
|
||||||
|
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
|
||||||
|
};
|
||||||
|
|
||||||
|
QSet<QString> WSRequestHandler::authNotRequired {
|
||||||
|
"GetVersion",
|
||||||
|
"GetAuthRequired",
|
||||||
|
"Authenticate"
|
||||||
|
};
|
||||||
|
|
||||||
|
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
|
||||||
|
_messageId(0),
|
||||||
|
_requestType(""),
|
||||||
|
data(nullptr),
|
||||||
|
_connProperties(connProperties)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WSRequestHandler::processIncomingMessage(std::string& textMessage) {
|
||||||
|
if (GetConfig()->DebugEnabled) {
|
||||||
|
blog(LOG_INFO, "Request >> '%s'", textMessage.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease responseData = processRequest(textMessage);
|
||||||
|
std::string response = obs_data_get_json(responseData);
|
||||||
|
|
||||||
|
if (GetConfig()->DebugEnabled) {
|
||||||
|
blog(LOG_INFO, "Response << '%s'", response.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerResponse WSRequestHandler::processRequest(std::string& textMessage){
|
||||||
|
std::string msgContainer(textMessage);
|
||||||
|
const char* msg = msgContainer.c_str();
|
||||||
|
|
||||||
|
data = obs_data_create_from_json(msg);
|
||||||
|
if (!data) {
|
||||||
|
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||||
|
return SendErrorResponse("invalid JSON payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasField("request-type") || !hasField("message-id")) {
|
||||||
|
return SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestType = obs_data_get_string(data, "request-type");
|
||||||
|
_messageId = obs_data_get_string(data, "message-id");
|
||||||
|
|
||||||
|
if (GetConfig()->AuthRequired
|
||||||
|
&& (!authNotRequired.contains(_requestType))
|
||||||
|
&& (!_connProperties.isAuthenticated()))
|
||||||
|
{
|
||||||
|
return SendErrorResponse("Not Authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerResponse (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
|
||||||
|
if (!handlerFunc) {
|
||||||
|
return SendErrorResponse("invalid request type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlerFunc(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
WSRequestHandler::~WSRequestHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerResponse WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
|
||||||
|
return SendResponse("ok", additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerResponse WSRequestHandler::SendErrorResponse(const char* errorMessage) {
|
||||||
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
|
obs_data_set_string(fields, "error", errorMessage);
|
||||||
|
|
||||||
|
return SendResponse("error", fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerResponse WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) {
|
||||||
|
return SendResponse("error", additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlerResponse WSRequestHandler::SendResponse(const char* status, obs_data_t* fields) {
|
||||||
|
obs_data_t* response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "message-id", _messageId);
|
||||||
|
obs_data_set_string(response, "status", status);
|
||||||
|
|
||||||
|
if (fields) {
|
||||||
|
obs_data_apply(response, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WSRequestHandler::hasField(QString name) {
|
||||||
|
if (!data || name.isEmpty() || name.isNull())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return obs_data_has_user_value(data, name.toUtf8());
|
||||||
|
}
|
163
src/WSRequestHandler.h
Normal file
163
src/WSRequestHandler.h
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtCore/QHash>
|
||||||
|
#include <QtCore/QSet>
|
||||||
|
#include <QtCore/QVariantHash>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QSharedPointer>
|
||||||
|
|
||||||
|
#include <obs.hpp>
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
|
#include "ConnectionProperties.h"
|
||||||
|
|
||||||
|
#include "obs-websocket.h"
|
||||||
|
|
||||||
|
typedef obs_data_t* HandlerResponse;
|
||||||
|
|
||||||
|
class WSRequestHandler : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WSRequestHandler(ConnectionProperties& connProperties);
|
||||||
|
~WSRequestHandler();
|
||||||
|
std::string processIncomingMessage(std::string& textMessage);
|
||||||
|
bool hasField(QString name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* _messageId;
|
||||||
|
const char* _requestType;
|
||||||
|
ConnectionProperties& _connProperties;
|
||||||
|
OBSDataAutoRelease data;
|
||||||
|
|
||||||
|
HandlerResponse processRequest(std::string& textMessage);
|
||||||
|
|
||||||
|
HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr);
|
||||||
|
HandlerResponse SendErrorResponse(const char* errorMessage);
|
||||||
|
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
|
||||||
|
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);
|
||||||
|
|
||||||
|
static QHash<QString, HandlerResponse(*)(WSRequestHandler*)> messageMap;
|
||||||
|
static QSet<QString> authNotRequired;
|
||||||
|
|
||||||
|
static HandlerResponse HandleGetVersion(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetAuthRequired(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleAuthenticate(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleGetStats(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetHeartbeat(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetVideoInfo(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetFilenameFormatting(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetFilenameFormatting(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetCurrentScene(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetCurrentScene(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSceneList(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetSceneItemRender(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSceneItemCrop(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSceneItemProperties(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSceneItemProperties(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleResetSceneItem(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleDuplicateSceneItem(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleDeleteSceneItem(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleReorderSceneItems(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleGetStreamingStatus(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStartStopStreaming(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStartStopRecording(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStartStreaming(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStopStreaming(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStartRecording(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStopRecording(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleStartStopReplayBuffer(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStartReplayBuffer(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleStopReplayBuffer(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSaveReplayBuffer(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetRecordingFolder(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetRecordingFolder(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleGetTransitionList(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetCurrentTransition(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetCurrentTransition(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetVolume(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetVolume(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleToggleMute(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetMute(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetMute(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSyncOffset(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSyncOffset(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSpecialSources(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSourcesList(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSourceTypesList(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetSourceSettings(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSourceSettings(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleTakeSourceScreenshot(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleGetSourceFilters(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleAddFilterToSource(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleRemoveFilterFromSource(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleReorderSourceFilter(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleMoveSourceFilter(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetSourceFilterSettings(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleListSceneCollections(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetCurrentProfile(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetCurrentProfile(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleListProfiles(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetStreamSettings(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetStreamSettings(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSaveStreamSettings(WSRequestHandler* req);
|
||||||
|
#if BUILD_CAPTIONS
|
||||||
|
static HandlerResponse HandleSendCaptions(WSRequestHandler * req);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetTransitionDuration(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetTransitionDuration(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleGetStudioModeStatus(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetPreviewScene(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleSetPreviewScene(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleTransitionToProgram(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleEnableStudioMode(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleDisableStudioMode(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleToggleStudioMode(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetTextFreetype2Properties(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetTextFreetype2Properties(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static HandlerResponse HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
||||||
|
static HandlerResponse HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
||||||
|
};
|
266
src/WSRequestHandler_General.cpp
Normal file
266
src/WSRequestHandler_General.cpp
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
#include "obs-websocket.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "WSEvents.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
#define CASE(x) case x: return #x;
|
||||||
|
const char *describe_output_format(int format) {
|
||||||
|
switch (format) {
|
||||||
|
default:
|
||||||
|
CASE(VIDEO_FORMAT_NONE)
|
||||||
|
CASE(VIDEO_FORMAT_I420)
|
||||||
|
CASE(VIDEO_FORMAT_NV12)
|
||||||
|
CASE(VIDEO_FORMAT_YVYU)
|
||||||
|
CASE(VIDEO_FORMAT_YUY2)
|
||||||
|
CASE(VIDEO_FORMAT_UYVY)
|
||||||
|
CASE(VIDEO_FORMAT_RGBA)
|
||||||
|
CASE(VIDEO_FORMAT_BGRA)
|
||||||
|
CASE(VIDEO_FORMAT_BGRX)
|
||||||
|
CASE(VIDEO_FORMAT_Y800)
|
||||||
|
CASE(VIDEO_FORMAT_I444)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *describe_color_space(int cs) {
|
||||||
|
switch (cs) {
|
||||||
|
default:
|
||||||
|
CASE(VIDEO_CS_DEFAULT)
|
||||||
|
CASE(VIDEO_CS_601)
|
||||||
|
CASE(VIDEO_CS_709)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *describe_color_range(int range) {
|
||||||
|
switch (range) {
|
||||||
|
default:
|
||||||
|
CASE(VIDEO_RANGE_DEFAULT)
|
||||||
|
CASE(VIDEO_RANGE_PARTIAL)
|
||||||
|
CASE(VIDEO_RANGE_FULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *describe_scale_type(int scale) {
|
||||||
|
switch (scale) {
|
||||||
|
default:
|
||||||
|
CASE(VIDEO_SCALE_DEFAULT)
|
||||||
|
CASE(VIDEO_SCALE_POINT)
|
||||||
|
CASE(VIDEO_SCALE_FAST_BILINEAR)
|
||||||
|
CASE(VIDEO_SCALE_BILINEAR)
|
||||||
|
CASE(VIDEO_SCALE_BICUBIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef CASE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest version of the plugin and the API.
|
||||||
|
*
|
||||||
|
* @return {double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.
|
||||||
|
* @return {String} `obs-websocket-version` obs-websocket plugin version.
|
||||||
|
* @return {String} `obs-studio-version` OBS Studio program version.
|
||||||
|
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetVersion
|
||||||
|
* @category general
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
|
||||||
|
QString obsVersion = Utils::OBSVersionString();
|
||||||
|
|
||||||
|
QList<QString> names = req->messageMap.keys();
|
||||||
|
names.sort(Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
|
||||||
|
QString requests;
|
||||||
|
requests += names.takeFirst();
|
||||||
|
for (QString reqName : names) {
|
||||||
|
requests += ("," + reqName);
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_double(data, "version", 1.1);
|
||||||
|
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
||||||
|
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
|
||||||
|
obs_data_set_string(data, "available-requests", requests.toUtf8());
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the client if authentication is required. If so, returns authentication parameters `challenge`
|
||||||
|
* and `salt` (see "Authentication" for more information).
|
||||||
|
*
|
||||||
|
* @return {boolean} `authRequired` Indicates whether authentication is required.
|
||||||
|
* @return {String (optional)} `challenge`
|
||||||
|
* @return {String (optional)} `salt`
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetAuthRequired
|
||||||
|
* @category general
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
||||||
|
bool authRequired = GetConfig()->AuthRequired;
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_bool(data, "authRequired", authRequired);
|
||||||
|
|
||||||
|
if (authRequired) {
|
||||||
|
auto config = GetConfig();
|
||||||
|
obs_data_set_string(data, "challenge",
|
||||||
|
config->SessionChallenge.toUtf8());
|
||||||
|
obs_data_set_string(data, "salt",
|
||||||
|
config->Salt.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to authenticate the client to the server.
|
||||||
|
*
|
||||||
|
* @param {String} `auth` Response to the auth challenge (see "Authentication" for more information).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name Authenticate
|
||||||
|
* @category general
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("auth")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->_connProperties.isAuthenticated()) {
|
||||||
|
return req->SendErrorResponse("already authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString auth = obs_data_get_string(req->data, "auth");
|
||||||
|
if (auth.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("auth not specified!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetConfig()->CheckAuth(auth) == false) {
|
||||||
|
return req->SendErrorResponse("Authentication Failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
req->_connProperties.setAuthenticated(true);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable sending of the Heartbeat event
|
||||||
|
*
|
||||||
|
* @param {boolean} `enable` Starts/Stops emitting heartbeat messages
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetHeartbeat
|
||||||
|
* @category general
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("enable")) {
|
||||||
|
return req->SendErrorResponse("Heartbeat <enable> parameter missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto events = GetEventsSystem();
|
||||||
|
events->HeartbeatIsActive = obs_data_get_bool(req->data, "enable");
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filename formatting string
|
||||||
|
*
|
||||||
|
* @param {String} `filename-formatting` Filename formatting string to set.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetFilenameFormatting
|
||||||
|
* @category general
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("filename-formatting")) {
|
||||||
|
return req->SendErrorResponse("<filename-formatting> parameter missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting");
|
||||||
|
if (filenameFormatting.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the filename formatting string
|
||||||
|
*
|
||||||
|
* @return {String} `filename-formatting` Current filename formatting string.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetFilenameFormatting
|
||||||
|
* @category general
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get OBS stats (almost the same info as provided in OBS' stats window)
|
||||||
|
*
|
||||||
|
* @return {OBSStats} `stats` OBS stats
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetStats
|
||||||
|
* @category general
|
||||||
|
* @since 4.6.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
|
||||||
|
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_obj(response, "stats", stats);
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get basic OBS video information
|
||||||
|
*
|
||||||
|
* @return {int} `baseWidth` Base (canvas) width
|
||||||
|
* @return {int} `baseHeight` Base (canvas) height
|
||||||
|
* @return {int} `outputWidth` Output width
|
||||||
|
* @return {int} `outputHeight` Output height
|
||||||
|
* @return {String} `scaleType` Scaling method used if output size differs from base size
|
||||||
|
* @return {double} `fps` Frames rendered per second
|
||||||
|
* @return {String} `videoFormat` Video color format
|
||||||
|
* @return {String} `colorSpace` Color space for YUV
|
||||||
|
* @return {String} `colorRange` Color range (full or partial)
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetVideoInfo
|
||||||
|
* @category general
|
||||||
|
* @since 4.6.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
|
||||||
|
obs_video_info ovi;
|
||||||
|
obs_get_video_info(&ovi);
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_int(response, "baseWidth", ovi.base_width);
|
||||||
|
obs_data_set_int(response, "baseHeight", ovi.base_height);
|
||||||
|
obs_data_set_int(response, "outputWidth", ovi.output_width);
|
||||||
|
obs_data_set_int(response, "outputHeight", ovi.output_height);
|
||||||
|
obs_data_set_double(response, "fps", (double)ovi.fps_num / ovi.fps_den);
|
||||||
|
obs_data_set_string(response, "videoFormat", describe_output_format(ovi.output_format));
|
||||||
|
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
|
||||||
|
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
|
||||||
|
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
65
src/WSRequestHandler_Profiles.cpp
Normal file
65
src/WSRequestHandler_Profiles.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the currently active profile.
|
||||||
|
*
|
||||||
|
* @param {String} `profile-name` Name of the desired profile.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetCurrentProfile
|
||||||
|
* @category profiles
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("profile-name")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString profileName = obs_data_get_string(req->data, "profile-name");
|
||||||
|
if (profileName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : check if profile exists
|
||||||
|
obs_frontend_set_current_profile(profileName.toUtf8());
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the current profile.
|
||||||
|
*
|
||||||
|
* @return {String} `profile-name` Name of the currently active profile.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetCurrentProfile
|
||||||
|
* @category profiles
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "profile-name", obs_frontend_get_current_profile());
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of available profiles.
|
||||||
|
*
|
||||||
|
* @return {Array<Object>} `profiles` List of available profiles.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ListProfiles
|
||||||
|
* @category profiles
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
|
||||||
|
char** profiles = obs_frontend_get_profiles();
|
||||||
|
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
|
||||||
|
bfree(profiles);
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_array(response, "profiles", list);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
105
src/WSRequestHandler_Recording.cpp
Normal file
105
src/WSRequestHandler_Recording.cpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle recording on or off.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartStopRecording
|
||||||
|
* @category recording
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_recording_active())
|
||||||
|
obs_frontend_recording_stop();
|
||||||
|
else
|
||||||
|
obs_frontend_recording_start();
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start recording.
|
||||||
|
* Will return an `error` if recording is already active.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartRecording
|
||||||
|
* @category recording
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_recording_active() == false) {
|
||||||
|
obs_frontend_recording_start();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("recording already active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop recording.
|
||||||
|
* Will return an `error` if recording is not active.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StopRecording
|
||||||
|
* @category recording
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_recording_active() == true) {
|
||||||
|
obs_frontend_recording_stop();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("recording not active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In the current profile, sets the recording folder of the Simple and Advanced
|
||||||
|
* output modes to the specified value.
|
||||||
|
*
|
||||||
|
* Please note: if `SetRecordingFolder` is called while a recording is
|
||||||
|
* in progress, the change won't be applied immediately and will be
|
||||||
|
* effective on the next recording.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {String} `rec-folder` Path of the recording folder.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetRecordingFolder
|
||||||
|
* @category recording
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("rec-folder")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* newRecFolder = obs_data_get_string(req->data, "rec-folder");
|
||||||
|
bool success = Utils::SetRecordingFolder(newRecFolder);
|
||||||
|
if (!success) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path of the current recording folder.
|
||||||
|
*
|
||||||
|
* @return {String} `rec-folder` Path of the recording folder.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetRecordingFolder
|
||||||
|
* @category recording
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
|
||||||
|
const char* recFolder = Utils::GetRecordingFolder();
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "rec-folder", recFolder);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
88
src/WSRequestHandler_ReplayBuffer.cpp
Normal file
88
src/WSRequestHandler_ReplayBuffer.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the Replay Buffer on/off.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartStopReplayBuffer
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_replay_buffer_active()) {
|
||||||
|
obs_frontend_replay_buffer_stop();
|
||||||
|
} else {
|
||||||
|
Utils::StartReplayBuffer();
|
||||||
|
}
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start recording into the Replay Buffer.
|
||||||
|
* Will return an `error` if the Replay Buffer is already active or if the
|
||||||
|
* "Save Replay Buffer" hotkey is not set in OBS' settings.
|
||||||
|
* Setting this hotkey is mandatory, even when triggering saves only
|
||||||
|
* through obs-websocket.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartReplayBuffer
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
|
||||||
|
if (!Utils::ReplayBufferEnabled()) {
|
||||||
|
return req->SendErrorResponse("replay buffer disabled in settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_frontend_replay_buffer_active() == true) {
|
||||||
|
return req->SendErrorResponse("replay buffer already active");
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::StartReplayBuffer();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop recording into the Replay Buffer.
|
||||||
|
* Will return an `error` if the Replay Buffer is not active.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StopReplayBuffer
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_replay_buffer_active() == true) {
|
||||||
|
obs_frontend_replay_buffer_stop();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("replay buffer not active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush and save the contents of the Replay Buffer to disk. This is
|
||||||
|
* basically the same as triggering the "Save Replay Buffer" hotkey.
|
||||||
|
* Will return an `error` if the Replay Buffer is not active.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SaveReplayBuffer
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
|
||||||
|
if (!obs_frontend_replay_buffer_active()) {
|
||||||
|
return req->SendErrorResponse("replay buffer not active");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
|
||||||
|
|
||||||
|
calldata_t cd = { 0 };
|
||||||
|
proc_handler_t* ph = obs_output_get_proc_handler(replayOutput);
|
||||||
|
proc_handler_call(ph, "save", &cd);
|
||||||
|
calldata_free(&cd);
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
68
src/WSRequestHandler_SceneCollections.cpp
Normal file
68
src/WSRequestHandler_SceneCollections.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the active scene collection.
|
||||||
|
*
|
||||||
|
* @param {String} `sc-name` Name of the desired scene collection.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetCurrentSceneCollection
|
||||||
|
* @category scene collections
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("sc-name")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneCollection = obs_data_get_string(req->data, "sc-name");
|
||||||
|
if (sceneCollection.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : Check if specified profile exists and if changing is allowed
|
||||||
|
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the current scene collection.
|
||||||
|
*
|
||||||
|
* @return {String} `sc-name` Name of the currently active scene collection.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetCurrentSceneCollection
|
||||||
|
* @category scene collections
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "sc-name",
|
||||||
|
obs_frontend_get_current_scene_collection());
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List available scene collections
|
||||||
|
*
|
||||||
|
* @return {Array<String>} `scene-collections` Scene collections list
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ListSceneCollections
|
||||||
|
* @category scene collections
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
|
||||||
|
char** sceneCollections = obs_frontend_get_scene_collections();
|
||||||
|
OBSDataArrayAutoRelease list =
|
||||||
|
Utils::StringListToArray(sceneCollections, "sc-name");
|
||||||
|
bfree(sceneCollections);
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_array(response, "scene-collections", list);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
606
src/WSRequestHandler_SceneItems.cpp
Normal file
606
src/WSRequestHandler_SceneItems.cpp
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the scene specific properties of the specified source item.
|
||||||
|
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
||||||
|
* @param {String} `item` The name of the source.
|
||||||
|
*
|
||||||
|
* @return {String} `name` The name of the source.
|
||||||
|
* @return {int} `position.x` The x position of the source from the left.
|
||||||
|
* @return {int} `position.y` The y position of the source from the top.
|
||||||
|
* @return {int} `position.alignment` The point on the source that the item is manipulated from.
|
||||||
|
* @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.
|
||||||
|
* @return {double} `scale.x` The x-scale factor of the source.
|
||||||
|
* @return {double} `scale.y` The y-scale factor of the source.
|
||||||
|
* @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling.
|
||||||
|
* @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling.
|
||||||
|
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
|
||||||
|
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
|
||||||
|
* @return {bool} `visible` If the source is visible.
|
||||||
|
* @return {bool} `locked` If the source's transform is locked.
|
||||||
|
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||||
|
* @return {int} `bounds.alignment` Alignment of the bounding box.
|
||||||
|
* @return {double} `bounds.x` Width of the bounding box.
|
||||||
|
* @return {double} `bounds.y` Height of the bounding box.
|
||||||
|
* @return {int} `sourceWidth` Base width (without scaling) of the source
|
||||||
|
* @return {int} `sourceHeight` Base source (without scaling) of the source
|
||||||
|
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
|
||||||
|
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
|
||||||
|
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||||
|
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetSceneItemProperties
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString itemName = obs_data_get_string(req->data, "item");
|
||||||
|
if (itemName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem =
|
||||||
|
Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
|
||||||
|
obs_data_set_string(data, "name", itemName.toUtf8());
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
|
||||||
|
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
||||||
|
* @param {String} `item` The name of the source.
|
||||||
|
* @param {int (optional)} `position.x` The new x position of the source.
|
||||||
|
* @param {int (optional)} `position.y` The new y position of the source.
|
||||||
|
* @param {int (optional)} `position.alignment` The new alignment of the source.
|
||||||
|
* @param {double (optional)} `rotation` The new clockwise rotation of the item in degrees.
|
||||||
|
* @param {double (optional)} `scale.x` The new x scale of the item.
|
||||||
|
* @param {double (optional)} `scale.y` The new y scale of the item.
|
||||||
|
* @param {int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.
|
||||||
|
* @param {int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.
|
||||||
|
* @param {int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling.
|
||||||
|
* @param {int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling.
|
||||||
|
* @param {bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.
|
||||||
|
* @param {bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.
|
||||||
|
* @param {String (optional)} `bounds.type` The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||||
|
* @param {int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
|
||||||
|
* @param {double (optional)} `bounds.x` The new width of the bounding box.
|
||||||
|
* @param {double (optional)} `bounds.y` The new height of the bounding box.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetSceneItemProperties
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString itemName = obs_data_get_string(req->data, "item");
|
||||||
|
if (itemName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem =
|
||||||
|
Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool badRequest = false;
|
||||||
|
OBSDataAutoRelease errorMessage = obs_data_create();
|
||||||
|
|
||||||
|
obs_sceneitem_defer_update_begin(sceneItem);
|
||||||
|
|
||||||
|
if (req->hasField("position")) {
|
||||||
|
vec2 oldPosition;
|
||||||
|
OBSDataAutoRelease positionError = obs_data_create();
|
||||||
|
obs_sceneitem_get_pos(sceneItem, &oldPosition);
|
||||||
|
OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position");
|
||||||
|
vec2 newPosition = oldPosition;
|
||||||
|
if (obs_data_has_user_value(reqPosition, "x")) {
|
||||||
|
newPosition.x = obs_data_get_int(reqPosition, "x");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqPosition, "y")) {
|
||||||
|
newPosition.y = obs_data_get_int(reqPosition, "y");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqPosition, "alignment")) {
|
||||||
|
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
|
||||||
|
if (Utils::IsValidAlignment(alignment)) {
|
||||||
|
obs_sceneitem_set_alignment(sceneItem, alignment);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
badRequest = true;
|
||||||
|
obs_data_set_string(positionError, "alignment", "invalid");
|
||||||
|
obs_data_set_obj(errorMessage, "position", positionError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obs_sceneitem_set_pos(sceneItem, &newPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("rotation")) {
|
||||||
|
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("scale")) {
|
||||||
|
vec2 oldScale;
|
||||||
|
obs_sceneitem_get_scale(sceneItem, &oldScale);
|
||||||
|
OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale");
|
||||||
|
vec2 newScale = oldScale;
|
||||||
|
if (obs_data_has_user_value(reqScale, "x")) {
|
||||||
|
newScale.x = obs_data_get_double(reqScale, "x");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqScale, "y")) {
|
||||||
|
newScale.y = obs_data_get_double(reqScale, "y");
|
||||||
|
}
|
||||||
|
obs_sceneitem_set_scale(sceneItem, &newScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("crop")) {
|
||||||
|
obs_sceneitem_crop oldCrop;
|
||||||
|
obs_sceneitem_get_crop(sceneItem, &oldCrop);
|
||||||
|
OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop");
|
||||||
|
obs_sceneitem_crop newCrop = oldCrop;
|
||||||
|
if (obs_data_has_user_value(reqCrop, "top")) {
|
||||||
|
newCrop.top = obs_data_get_int(reqCrop, "top");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqCrop, "right")) {
|
||||||
|
newCrop.right = obs_data_get_int(reqCrop, "right");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqCrop, "bottom")) {
|
||||||
|
newCrop.bottom = obs_data_get_int(reqCrop, "bottom");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqCrop, "left")) {
|
||||||
|
newCrop.left = obs_data_get_int(reqCrop, "left");
|
||||||
|
}
|
||||||
|
obs_sceneitem_set_crop(sceneItem, &newCrop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("visible")) {
|
||||||
|
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("locked")) {
|
||||||
|
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(req->data, "locked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("bounds")) {
|
||||||
|
bool badBounds = false;
|
||||||
|
OBSDataAutoRelease boundsError = obs_data_create();
|
||||||
|
OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
|
||||||
|
if (obs_data_has_user_value(reqBounds, "type")) {
|
||||||
|
QString newBoundsType = obs_data_get_string(reqBounds, "type");
|
||||||
|
if (newBoundsType == "OBS_BOUNDS_NONE") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE);
|
||||||
|
}
|
||||||
|
else if (newBoundsType == "OBS_BOUNDS_STRETCH") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_STRETCH);
|
||||||
|
}
|
||||||
|
else if (newBoundsType == "OBS_BOUNDS_SCALE_INNER") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_INNER);
|
||||||
|
}
|
||||||
|
else if (newBoundsType == "OBS_BOUNDS_SCALE_OUTER") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_OUTER);
|
||||||
|
}
|
||||||
|
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_WIDTH") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_WIDTH);
|
||||||
|
}
|
||||||
|
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_HEIGHT") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_HEIGHT);
|
||||||
|
}
|
||||||
|
else if (newBoundsType == "OBS_BOUNDS_MAX_ONLY") {
|
||||||
|
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_MAX_ONLY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
badRequest = badBounds = true;
|
||||||
|
obs_data_set_string(boundsError, "type", "invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec2 oldBounds;
|
||||||
|
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
|
||||||
|
vec2 newBounds = oldBounds;
|
||||||
|
if (obs_data_has_user_value(reqBounds, "x")) {
|
||||||
|
newBounds.x = obs_data_get_double(reqBounds, "x");
|
||||||
|
}
|
||||||
|
if (obs_data_has_user_value(reqBounds, "y")) {
|
||||||
|
newBounds.y = obs_data_get_double(reqBounds, "y");
|
||||||
|
}
|
||||||
|
obs_sceneitem_set_bounds(sceneItem, &newBounds);
|
||||||
|
if (obs_data_has_user_value(reqBounds, "alignment")) {
|
||||||
|
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
|
||||||
|
if (Utils::IsValidAlignment(bounds_alignment)) {
|
||||||
|
obs_sceneitem_set_bounds_alignment(sceneItem, bounds_alignment);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
badRequest = badBounds = true;
|
||||||
|
obs_data_set_string(boundsError, "alignment", "invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (badBounds) {
|
||||||
|
obs_data_set_obj(errorMessage, "bounds", boundsError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_defer_update_end(sceneItem);
|
||||||
|
|
||||||
|
if (badRequest) {
|
||||||
|
return req->SendErrorResponse(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset a scene item.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene.
|
||||||
|
* @param {String} `item` Name of the source item.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ResetSceneItem
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
||||||
|
// TODO: remove this request, or refactor it to ResetSource
|
||||||
|
|
||||||
|
if (!req->hasField("item")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* itemName = obs_data_get_string(req->data, "item");
|
||||||
|
if (!itemName) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||||
|
|
||||||
|
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
|
||||||
|
obs_source_update(sceneItemSource, settings);
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide a specified source item in a specified scene.
|
||||||
|
*
|
||||||
|
* @param {String} `source` Scene item name in the specified scene.
|
||||||
|
* @param {boolean} `render` true = shown ; false = hidden
|
||||||
|
* @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetSceneItemRender
|
||||||
|
* @category scene items
|
||||||
|
* @since 0.3
|
||||||
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("source") ||
|
||||||
|
!req->hasField("render"))
|
||||||
|
{
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* itemName = obs_data_get_string(req->data, "source");
|
||||||
|
bool isVisible = obs_data_get_bool(req->data, "render");
|
||||||
|
|
||||||
|
if (!itemName) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem =
|
||||||
|
Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_set_visible(sceneItem, isVisible);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the coordinates of a specified source item.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
|
||||||
|
* @param {String} `item` The name of the source item.
|
||||||
|
* @param {double} `x` X coordinate.
|
||||||
|
* @param {double} `y` Y coordinate.
|
||||||
|
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetSceneItemPosition
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.0.0
|
||||||
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item") ||
|
||||||
|
!req->hasField("x") || !req->hasField("y")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString itemName = obs_data_get_string(req->data, "item");
|
||||||
|
if (itemName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene could not be found");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 item_position = { 0 };
|
||||||
|
item_position.x = obs_data_get_double(req->data, "x");
|
||||||
|
item_position.y = obs_data_get_double(req->data, "y");
|
||||||
|
obs_sceneitem_set_pos(sceneItem, &item_position);
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the transform of the specified source item.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
|
||||||
|
* @param {String} `item` The name of the source item.
|
||||||
|
* @param {double} `x-scale` Width scale factor.
|
||||||
|
* @param {double} `y-scale` Height scale factor.
|
||||||
|
* @param {double} `rotation` Source item rotation (in degrees).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetSceneItemTransform
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.0.0
|
||||||
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item") ||
|
||||||
|
!req->hasField("x-scale") ||
|
||||||
|
!req->hasField("y-scale") ||
|
||||||
|
!req->hasField("rotation"))
|
||||||
|
{
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString itemName = obs_data_get_string(req->data, "item");
|
||||||
|
if (itemName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 scale;
|
||||||
|
scale.x = obs_data_get_double(req->data, "x-scale");
|
||||||
|
scale.y = obs_data_get_double(req->data, "y-scale");
|
||||||
|
float rotation = obs_data_get_double(req->data, "rotation");
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_defer_update_begin(sceneItem);
|
||||||
|
|
||||||
|
obs_sceneitem_set_scale(sceneItem, &scale);
|
||||||
|
obs_sceneitem_set_rot(sceneItem, rotation);
|
||||||
|
|
||||||
|
obs_sceneitem_defer_update_end(sceneItem);
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the crop coordinates of the specified source item.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
||||||
|
* @param {String} `item` The name of the source.
|
||||||
|
* @param {int} `top` Pixel position of the top of the source item.
|
||||||
|
* @param {int} `bottom` Pixel position of the bottom of the source item.
|
||||||
|
* @param {int} `left` Pixel position of the left of the source item.
|
||||||
|
* @param {int} `right` Pixel position of the right of the source item.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetSceneItemCrop
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.1.0
|
||||||
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString itemName = obs_data_get_string(req->data, "item");
|
||||||
|
if (itemName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct obs_sceneitem_crop crop = { 0 };
|
||||||
|
crop.top = obs_data_get_int(req->data, "top");
|
||||||
|
crop.bottom = obs_data_get_int(req->data, "bottom");
|
||||||
|
crop.left = obs_data_get_int(req->data, "left");
|
||||||
|
crop.right = obs_data_get_int(req->data, "right");
|
||||||
|
|
||||||
|
obs_sceneitem_set_crop(sceneItem, &crop);
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a scene item.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene.
|
||||||
|
* @param {Object} `item` item to delete (required)
|
||||||
|
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
|
||||||
|
* @param {int} `item.id` id of the scene item.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name DeleteSceneItem
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.5.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleDeleteSceneItem(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sceneName = obs_data_get_string(req->data, "scene");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
|
||||||
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("item with id/name combination not found in specified scene");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_remove(sceneItem);
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DuplicateSceneItemData {
|
||||||
|
obs_sceneitem_t *referenceItem;
|
||||||
|
obs_source_t *fromSource;
|
||||||
|
obs_sceneitem_t *newItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
|
||||||
|
DuplicateSceneItemData *data = (DuplicateSceneItemData *)_data;
|
||||||
|
data->newItem = obs_scene_add(scene, data->fromSource);
|
||||||
|
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicates a scene item.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
|
||||||
|
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
|
||||||
|
* @param {Object} `item` item to duplicate (required)
|
||||||
|
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
|
||||||
|
* @param {int} `item.id` id of the scene item.
|
||||||
|
*
|
||||||
|
* @return {String} `scene` Name of the scene where the new item was created
|
||||||
|
* @return {Object} `item` New item info
|
||||||
|
* @return {int} `item.id` New item ID
|
||||||
|
* @return {String} `item.name` New item name
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name DuplicateSceneItem
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.5.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("item")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* fromSceneName = obs_data_get_string(req->data, "fromScene");
|
||||||
|
OBSSourceAutoRelease fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
|
||||||
|
if (!fromScene) {
|
||||||
|
return req->SendErrorResponse("requested fromScene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* toSceneName = obs_data_get_string(req->data, "toScene");
|
||||||
|
OBSSourceAutoRelease toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
|
||||||
|
if (!toScene) {
|
||||||
|
return req->SendErrorResponse("requested toScene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
|
||||||
|
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item);
|
||||||
|
if (!referenceItem) {
|
||||||
|
return req->SendErrorResponse("item with id/name combination not found in specified scene");
|
||||||
|
}
|
||||||
|
|
||||||
|
DuplicateSceneItemData data;
|
||||||
|
data.fromSource = obs_sceneitem_get_source(referenceItem);
|
||||||
|
data.referenceItem = referenceItem;
|
||||||
|
|
||||||
|
obs_enter_graphics();
|
||||||
|
obs_scene_atomic_update(obs_scene_from_source(toScene), DuplicateSceneItem, &data);
|
||||||
|
obs_leave_graphics();
|
||||||
|
|
||||||
|
obs_sceneitem_t *newItem = data.newItem;
|
||||||
|
if (!newItem) {
|
||||||
|
return req->SendErrorResponse("Error duplicating scene item");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease itemData = obs_data_create();
|
||||||
|
obs_data_set_int(itemData, "id", obs_sceneitem_get_id(newItem));
|
||||||
|
obs_data_set_string(itemData, "name", obs_source_get_name(obs_sceneitem_get_source(newItem)));
|
||||||
|
|
||||||
|
OBSDataAutoRelease responseData = obs_data_create();
|
||||||
|
obs_data_set_obj(responseData, "item", itemData);
|
||||||
|
obs_data_set_string(responseData, "scene", obs_source_get_name(toScene));
|
||||||
|
|
||||||
|
return req->SendOKResponse(responseData);
|
||||||
|
}
|
141
src/WSRequestHandler_Scenes.cpp
Normal file
141
src/WSRequestHandler_Scenes.cpp
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} `Scene`
|
||||||
|
* @property {String} `name` Name of the currently active scene.
|
||||||
|
* @property {Array<SceneItem>} `sources` Ordered list of the current scene's source items.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to the specified scene.
|
||||||
|
*
|
||||||
|
* @param {String} `scene-name` Name of the scene to switch to.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetCurrentScene
|
||||||
|
* @category scenes
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("scene-name")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
obs_frontend_set_current_scene(source);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("requested scene does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current scene's name and source items.
|
||||||
|
*
|
||||||
|
* @return {String} `name` Name of the currently active scene.
|
||||||
|
* @return {Array<SceneItem>} `sources` Ordered list of the current scene's source items.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetCurrentScene
|
||||||
|
* @category scenes
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
||||||
|
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||||
|
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
|
||||||
|
obs_data_set_array(data, "sources", sceneItems);
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of scenes in the currently active profile.
|
||||||
|
*
|
||||||
|
* @return {String} `current-scene` Name of the currently active scene.
|
||||||
|
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetSceneList
|
||||||
|
* @category scenes
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
||||||
|
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||||
|
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_string(data, "current-scene",
|
||||||
|
obs_source_get_name(currentScene));
|
||||||
|
obs_data_set_array(data, "scenes", scenes);
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the order of scene items in the requested scene.
|
||||||
|
*
|
||||||
|
* @param {String (optional)} `scene` Name of the scene to reorder (defaults to current).
|
||||||
|
* @param {Array<Scene>} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene
|
||||||
|
* @param {int (optional)} `items[].id` Id of a specific scene item. Unique on a scene by scene basis.
|
||||||
|
* @param {String (optional)} `items[].name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ReorderSceneItems
|
||||||
|
* @category scenes
|
||||||
|
* @since 4.5.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleReorderSceneItems(WSRequestHandler* req) {
|
||||||
|
QString sceneName = obs_data_get_string(req->data, "scene");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("requested scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataArrayAutoRelease items = obs_data_get_array(req->data, "items");
|
||||||
|
if (!items) {
|
||||||
|
return req->SendErrorResponse("sceneItem order not specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count = obs_data_array_count(items);
|
||||||
|
|
||||||
|
std::vector<obs_sceneitem_t*> newOrder;
|
||||||
|
newOrder.reserve(count);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
OBSDataAutoRelease item = obs_data_array_item(items, i);
|
||||||
|
|
||||||
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
||||||
|
obs_sceneitem_release(sceneItem); // ref dec
|
||||||
|
|
||||||
|
if (!sceneItem) {
|
||||||
|
return req->SendErrorResponse("Invalid sceneItem id or name specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j <= i; ++j) {
|
||||||
|
if (sceneItem == newOrder[j]) {
|
||||||
|
return req->SendErrorResponse("Duplicate sceneItem in specified order");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newOrder.push_back(sceneItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = obs_scene_reorder_items(obs_scene_from_source(scene), newOrder.data(), count);
|
||||||
|
if (!success) {
|
||||||
|
return req->SendErrorResponse("Invalid sceneItem order");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& item: newOrder) {
|
||||||
|
obs_sceneitem_release(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
1495
src/WSRequestHandler_Sources.cpp
Normal file
1495
src/WSRequestHandler_Sources.cpp
Normal file
File diff suppressed because it is too large
Load Diff
318
src/WSRequestHandler_Streaming.cpp
Normal file
318
src/WSRequestHandler_Streaming.cpp
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
#include "obs-websocket.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "WSEvents.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
#define STREAM_SERVICE_ID "websocket_custom_service"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current streaming and recording status.
|
||||||
|
*
|
||||||
|
* @return {boolean} `streaming` Current streaming status.
|
||||||
|
* @return {boolean} `recording` Current recording status.
|
||||||
|
* @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming).
|
||||||
|
* @return {String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording).
|
||||||
|
* @return {boolean} `preview-only` Always false. Retrocompatibility with OBSRemote.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetStreamingStatus
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
|
||||||
|
auto events = GetEventsSystem();
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
|
||||||
|
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
|
||||||
|
obs_data_set_bool(data, "preview-only", false);
|
||||||
|
|
||||||
|
const char* tc = nullptr;
|
||||||
|
if (obs_frontend_streaming_active()) {
|
||||||
|
tc = events->GetStreamingTimecode();
|
||||||
|
obs_data_set_string(data, "stream-timecode", tc);
|
||||||
|
bfree((void*)tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_frontend_recording_active()) {
|
||||||
|
tc = events->GetRecordingTimecode();
|
||||||
|
obs_data_set_string(data, "rec-timecode", tc);
|
||||||
|
bfree((void*)tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle streaming on or off.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartStopStreaming
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_streaming_active())
|
||||||
|
return HandleStopStreaming(req);
|
||||||
|
else
|
||||||
|
return HandleStartStreaming(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start streaming.
|
||||||
|
* Will return an `error` if streaming is already active.
|
||||||
|
*
|
||||||
|
* @param {Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration.
|
||||||
|
* @param {String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream.
|
||||||
|
* @param {Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field.
|
||||||
|
* @param {Object (optional)} `stream.settings` Settings for the stream.
|
||||||
|
* @param {String (optional)} `stream.settings.server` The publish URL.
|
||||||
|
* @param {String (optional)} `stream.settings.key` The publish key of the stream.
|
||||||
|
* @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||||
|
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.
|
||||||
|
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartStreaming
|
||||||
|
* @category streaming
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_streaming_active() == false) {
|
||||||
|
OBSService configuredService = obs_frontend_get_streaming_service();
|
||||||
|
OBSService newService = nullptr;
|
||||||
|
|
||||||
|
// TODO: fix service memory leak
|
||||||
|
|
||||||
|
if (req->hasField("stream")) {
|
||||||
|
OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
|
||||||
|
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
|
||||||
|
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
|
||||||
|
|
||||||
|
OBSDataAutoRelease csHotkeys =
|
||||||
|
obs_hotkeys_save_service(configuredService);
|
||||||
|
|
||||||
|
QString currentType = obs_service_get_type(configuredService);
|
||||||
|
QString newType = obs_data_get_string(streamData, "type");
|
||||||
|
if (newType.isEmpty() || newType.isNull()) {
|
||||||
|
newType = currentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Supporting adding metadata parameters to key query string
|
||||||
|
QString query = Utils::ParseDataToQueryString(newMetadata);
|
||||||
|
if (!query.isEmpty()
|
||||||
|
&& obs_data_has_user_value(newSettings, "key"))
|
||||||
|
{
|
||||||
|
const char* key = obs_data_get_string(newSettings, "key");
|
||||||
|
int keylen = strlen(key);
|
||||||
|
|
||||||
|
bool hasQuestionMark = false;
|
||||||
|
for (int i = 0; i < keylen; i++) {
|
||||||
|
if (key[i] == '?') {
|
||||||
|
hasQuestionMark = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasQuestionMark) {
|
||||||
|
query.prepend('&');
|
||||||
|
} else {
|
||||||
|
query.prepend('?');
|
||||||
|
}
|
||||||
|
|
||||||
|
query.prepend(key);
|
||||||
|
obs_data_set_string(newSettings, "key", query.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newType == currentType) {
|
||||||
|
// Service type doesn't change: apply settings to current service
|
||||||
|
|
||||||
|
// By doing this, you can send a request to the websocket
|
||||||
|
// that only contains settings you want to change, instead of
|
||||||
|
// having to do a get and then change them
|
||||||
|
|
||||||
|
OBSDataAutoRelease currentSettings = obs_service_get_settings(configuredService);
|
||||||
|
OBSDataAutoRelease updatedSettings = obs_data_create();
|
||||||
|
|
||||||
|
obs_data_apply(updatedSettings, currentSettings); //first apply the existing settings
|
||||||
|
obs_data_apply(updatedSettings, newSettings); //then apply the settings from the request should they exist
|
||||||
|
|
||||||
|
newService = obs_service_create(
|
||||||
|
newType.toUtf8(), STREAM_SERVICE_ID,
|
||||||
|
updatedSettings, csHotkeys);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Service type changed: override service settings
|
||||||
|
newService = obs_service_create(
|
||||||
|
newType.toUtf8(), STREAM_SERVICE_ID,
|
||||||
|
newSettings, csHotkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_set_streaming_service(newService);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_streaming_start();
|
||||||
|
|
||||||
|
// Stream settings provided in StartStreaming are not persisted to disk
|
||||||
|
if (newService != nullptr) {
|
||||||
|
obs_frontend_set_streaming_service(configuredService);
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("streaming already active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop streaming.
|
||||||
|
* Will return an `error` if streaming is not active.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StopStreaming
|
||||||
|
* @category streaming
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
||||||
|
if (obs_frontend_streaming_active() == true) {
|
||||||
|
obs_frontend_streaming_stop();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
} else {
|
||||||
|
return req->SendErrorResponse("streaming not active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response. If 'type' is different than the current streaming service type, all settings are required. Returns the full settings of the stream (the same as GetStreamSettings).
|
||||||
|
*
|
||||||
|
* @param {String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`.
|
||||||
|
* @param {Object} `settings` The actual settings of the stream.
|
||||||
|
* @param {String (optional)} `settings.server` The publish URL.
|
||||||
|
* @param {String (optional)} `settings.key` The publish key.
|
||||||
|
* @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||||
|
* @param {String (optional)} `settings.username` The username for the streaming service.
|
||||||
|
* @param {String (optional)} `settings.password` The password for the streaming service.
|
||||||
|
* @param {boolean} `save` Persist the settings to disk.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetStreamSettings
|
||||||
|
* @category streaming
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
|
||||||
|
OBSService service = obs_frontend_get_streaming_service();
|
||||||
|
|
||||||
|
OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings");
|
||||||
|
if (!requestSettings) {
|
||||||
|
return req->SendErrorResponse("'settings' are required'");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString serviceType = obs_service_get_type(service);
|
||||||
|
QString requestedType = obs_data_get_string(req->data, "type");
|
||||||
|
|
||||||
|
if (requestedType != nullptr && requestedType != serviceType) {
|
||||||
|
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
|
||||||
|
service = obs_service_create(
|
||||||
|
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
|
||||||
|
} else {
|
||||||
|
// If type isn't changing, we should overlay the settings we got
|
||||||
|
// to the existing settings. By doing so, you can send a request that
|
||||||
|
// only contains the settings you want to change, instead of having to
|
||||||
|
// do a get and then change them
|
||||||
|
|
||||||
|
OBSDataAutoRelease existingSettings = obs_service_get_settings(service);
|
||||||
|
OBSDataAutoRelease newSettings = obs_data_create();
|
||||||
|
|
||||||
|
// Apply existing settings
|
||||||
|
obs_data_apply(newSettings, existingSettings);
|
||||||
|
// Then apply the settings from the request
|
||||||
|
obs_data_apply(newSettings, requestSettings);
|
||||||
|
|
||||||
|
obs_service_update(service, newSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
//if save is specified we should immediately save the streaming service
|
||||||
|
if (obs_data_get_bool(req->data, "save")) {
|
||||||
|
obs_frontend_save_streaming_service();
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "type", requestedType.toUtf8());
|
||||||
|
obs_data_set_obj(response, "settings", serviceSettings);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current streaming server settings.
|
||||||
|
*
|
||||||
|
* @return {String} `type` The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'.
|
||||||
|
* @return {Object} `settings` Stream settings object.
|
||||||
|
* @return {String} `settings.server` The publish URL.
|
||||||
|
* @return {String} `settings.key` The publish key of the stream.
|
||||||
|
* @return {boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||||
|
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`.
|
||||||
|
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetStreamSettings
|
||||||
|
* @category streaming
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
|
||||||
|
OBSService service = obs_frontend_get_streaming_service();
|
||||||
|
|
||||||
|
const char* serviceType = obs_service_get_type(service);
|
||||||
|
OBSDataAutoRelease settings = obs_service_get_settings(service);
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "type", serviceType);
|
||||||
|
obs_data_set_obj(response, "settings", settings);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current streaming server settings to disk.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SaveStreamSettings
|
||||||
|
* @category streaming
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
|
||||||
|
obs_frontend_save_streaming_service();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the provided text as embedded CEA-608 caption data.
|
||||||
|
* As of OBS Studio 23.1, captions are not yet available on Linux.
|
||||||
|
*
|
||||||
|
* @param {String} `text` Captions text
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SendCaptions
|
||||||
|
* @category streaming
|
||||||
|
* @since 4.6.0
|
||||||
|
*/
|
||||||
|
#if BUILD_CAPTIONS
|
||||||
|
HandlerResponse WSRequestHandler::HandleSendCaptions(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("text")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
||||||
|
if (output) {
|
||||||
|
const char* caption = obs_data_get_string(req->data, "text");
|
||||||
|
obs_output_output_caption_text1(output, caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
165
src/WSRequestHandler_StudioMode.cpp
Normal file
165
src/WSRequestHandler_StudioMode.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if Studio Mode is currently enabled.
|
||||||
|
*
|
||||||
|
* @return {boolean} `studio-mode` Indicates if Studio Mode is enabled.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetStudioModeStatus
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
|
||||||
|
bool previewActive = obs_frontend_preview_program_mode_active();
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_bool(response, "studio-mode", previewActive);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the currently previewed scene and its list of sources.
|
||||||
|
* Will return an `error` if Studio Mode is not enabled.
|
||||||
|
*
|
||||||
|
* @return {String} `name` The name of the active preview scene.
|
||||||
|
* @return {Array<SceneItem>} `sources`
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetPreviewScene
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
||||||
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
|
return req->SendErrorResponse("studio mode not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
|
||||||
|
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene);
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
|
obs_data_set_string(data, "name", obs_source_get_name(scene));
|
||||||
|
obs_data_set_array(data, "sources", sceneItems);
|
||||||
|
|
||||||
|
return req->SendOKResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active preview scene.
|
||||||
|
* Will return an `error` if Studio Mode is not enabled.
|
||||||
|
*
|
||||||
|
* @param {String} `scene-name` The name of the scene to preview.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetPreviewScene
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
|
||||||
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
|
return req->SendErrorResponse("studio mode not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req->hasField("scene-name")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* scene_name = obs_data_get_string(req->data, "scene-name");
|
||||||
|
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||||
|
if (!scene) {
|
||||||
|
return req->SendErrorResponse("specified scene doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_set_current_preview_scene(scene);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transitions the currently previewed scene to the main output.
|
||||||
|
* Will return an `error` if Studio Mode is not enabled.
|
||||||
|
*
|
||||||
|
* @param {Object (optional)} `with-transition` Change the active transition before switching scenes. Defaults to the active transition.
|
||||||
|
* @param {String} `with-transition.name` Name of the transition.
|
||||||
|
* @param {int (optional)} `with-transition.duration` Transition duration (in milliseconds).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name TransitionToProgram
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
|
||||||
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
|
return req->SendErrorResponse("studio mode not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req->hasField("with-transition")) {
|
||||||
|
OBSDataAutoRelease transitionInfo =
|
||||||
|
obs_data_get_obj(req->data, "with-transition");
|
||||||
|
|
||||||
|
if (obs_data_has_user_value(transitionInfo, "name")) {
|
||||||
|
QString transitionName =
|
||||||
|
obs_data_get_string(transitionInfo, "name");
|
||||||
|
if (transitionName.isEmpty()) {
|
||||||
|
return req->SendErrorResponse("invalid request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = Utils::SetTransitionByName(transitionName);
|
||||||
|
if (!success) {
|
||||||
|
return req->SendErrorResponse("specified transition doesn't exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_data_has_user_value(transitionInfo, "duration")) {
|
||||||
|
int transitionDuration =
|
||||||
|
obs_data_get_int(transitionInfo, "duration");
|
||||||
|
Utils::SetTransitionDuration(transitionDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::TransitionToProgram();
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables Studio Mode.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name EnableStudioMode
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
|
||||||
|
obs_frontend_set_preview_program_mode(true);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables Studio Mode.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name DisableStudioMode
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
|
||||||
|
obs_frontend_set_preview_program_mode(false);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles Studio Mode.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ToggleStudioMode
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
|
||||||
|
bool previewProgramMode = obs_frontend_preview_program_mode_active();
|
||||||
|
obs_frontend_set_preview_program_mode(!previewProgramMode);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
122
src/WSRequestHandler_Transitions.cpp
Normal file
122
src/WSRequestHandler_Transitions.cpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all transitions available in the frontend's dropdown menu.
|
||||||
|
*
|
||||||
|
* @return {String} `current-transition` Name of the currently active transition.
|
||||||
|
* @return {Array<Object>} `transitions` List of transitions.
|
||||||
|
* @return {String} `transitions.*.name` Name of the transition.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetTransitionList
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
|
||||||
|
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||||
|
obs_frontend_source_list transitionList = {};
|
||||||
|
obs_frontend_get_transitions(&transitionList);
|
||||||
|
|
||||||
|
OBSDataArrayAutoRelease transitions = obs_data_array_create();
|
||||||
|
for (size_t i = 0; i < transitionList.sources.num; i++) {
|
||||||
|
OBSSource transition = transitionList.sources.array[i];
|
||||||
|
|
||||||
|
OBSDataAutoRelease obj = obs_data_create();
|
||||||
|
obs_data_set_string(obj, "name", obs_source_get_name(transition));
|
||||||
|
obs_data_array_push_back(transitions, obj);
|
||||||
|
}
|
||||||
|
obs_frontend_source_list_free(&transitionList);
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "current-transition",
|
||||||
|
obs_source_get_name(currentTransition));
|
||||||
|
obs_data_set_array(response, "transitions", transitions);
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the currently selected transition in the frontend's dropdown menu.
|
||||||
|
*
|
||||||
|
* @return {String} `name` Name of the selected transition.
|
||||||
|
* @return {int (optional)} `duration` Transition duration (in milliseconds) if supported by the transition.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetCurrentTransition
|
||||||
|
* @category transitions
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
|
||||||
|
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_string(response, "name",
|
||||||
|
obs_source_get_name(currentTransition));
|
||||||
|
|
||||||
|
if (!obs_transition_fixed(currentTransition))
|
||||||
|
obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
|
||||||
|
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active transition.
|
||||||
|
*
|
||||||
|
* @param {String} `transition-name` The name of the transition.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetCurrentTransition
|
||||||
|
* @category transitions
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("transition-name")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString name = obs_data_get_string(req->data, "transition-name");
|
||||||
|
bool success = Utils::SetTransitionByName(name);
|
||||||
|
if (!success) {
|
||||||
|
return req->SendErrorResponse("requested transition does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the duration of the currently selected transition if supported.
|
||||||
|
*
|
||||||
|
* @param {int} `duration` Desired duration of the transition (in milliseconds).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetTransitionDuration
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
|
||||||
|
if (!req->hasField("duration")) {
|
||||||
|
return req->SendErrorResponse("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
int ms = obs_data_get_int(req->data, "duration");
|
||||||
|
Utils::SetTransitionDuration(ms);
|
||||||
|
return req->SendOKResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the duration of the currently selected transition if supported.
|
||||||
|
*
|
||||||
|
* @return {int} `transition-duration` Duration of the current transition (in milliseconds).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetTransitionDuration
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
HandlerResponse WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_int(response, "transition-duration", Utils::GetTransitionDuration());
|
||||||
|
return req->SendOKResponse(response);
|
||||||
|
}
|
214
src/WSServer.cpp
Normal file
214
src/WSServer.cpp
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtWidgets/QMainWindow>
|
||||||
|
#include <QtWidgets/QMessageBox>
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
#include <util/platform.h>
|
||||||
|
|
||||||
|
#include "WSServer.h"
|
||||||
|
#include "obs-websocket.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
QT_USE_NAMESPACE
|
||||||
|
|
||||||
|
using websocketpp::lib::placeholders::_1;
|
||||||
|
using websocketpp::lib::placeholders::_2;
|
||||||
|
using websocketpp::lib::bind;
|
||||||
|
|
||||||
|
WSServer::WSServer()
|
||||||
|
: QObject(nullptr),
|
||||||
|
_connections(),
|
||||||
|
_clMutex(QMutex::Recursive)
|
||||||
|
{
|
||||||
|
_server.init_asio();
|
||||||
|
#ifndef _WIN32
|
||||||
|
_server.set_reuse_addr(true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_server.set_open_handler(bind(&WSServer::onOpen, this, ::_1));
|
||||||
|
_server.set_close_handler(bind(&WSServer::onClose, this, ::_1));
|
||||||
|
_server.set_message_handler(bind(&WSServer::onMessage, this, ::_1, ::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
WSServer::~WSServer()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::start(quint16 port)
|
||||||
|
{
|
||||||
|
if (_server.is_listening() && port == _serverPort) {
|
||||||
|
blog(LOG_INFO, "WSServer::start: server already on this port. no restart needed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_server.is_listening()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
_server.reset();
|
||||||
|
|
||||||
|
_serverPort = port;
|
||||||
|
|
||||||
|
websocketpp::lib::error_code errorCode;
|
||||||
|
_server.listen(_serverPort, errorCode);
|
||||||
|
|
||||||
|
if (errorCode) {
|
||||||
|
std::string errorCodeMessage = errorCode.message();
|
||||||
|
blog(LOG_INFO, "server: listen failed: %s", errorCodeMessage.c_str());
|
||||||
|
|
||||||
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
|
QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title");
|
||||||
|
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort);
|
||||||
|
obs_frontend_pop_ui_translation();
|
||||||
|
|
||||||
|
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
|
||||||
|
QMessageBox::warning(mainWindow, errorTitle, errorMessage);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_server.start_accept();
|
||||||
|
|
||||||
|
QtConcurrent::run([=]() {
|
||||||
|
blog(LOG_INFO, "io thread started");
|
||||||
|
_server.run();
|
||||||
|
blog(LOG_INFO, "io thread exited");
|
||||||
|
});
|
||||||
|
|
||||||
|
blog(LOG_INFO, "server started successfully on port %d", _serverPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::stop()
|
||||||
|
{
|
||||||
|
if (!_server.is_listening()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_server.stop_listening();
|
||||||
|
for (connection_hdl hdl : _connections) {
|
||||||
|
_server.close(hdl, websocketpp::close::status::going_away, "Server stopping");
|
||||||
|
}
|
||||||
|
_connections.clear();
|
||||||
|
_connectionProperties.clear();
|
||||||
|
|
||||||
|
_threadPool.waitForDone();
|
||||||
|
|
||||||
|
while (!_server.stopped()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
blog(LOG_INFO, "server stopped successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::broadcast(std::string message)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&_clMutex);
|
||||||
|
for (connection_hdl hdl : _connections) {
|
||||||
|
if (GetConfig()->AuthRequired) {
|
||||||
|
bool authenticated = _connectionProperties[hdl].isAuthenticated();
|
||||||
|
if (!authenticated) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_server.send(hdl, message, websocketpp::frame::opcode::text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onOpen(connection_hdl hdl)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&_clMutex);
|
||||||
|
_connections.insert(hdl);
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
QString clientIp = getRemoteEndpoint(hdl);
|
||||||
|
notifyConnection(clientIp);
|
||||||
|
blog(LOG_INFO, "new client connection from %s", clientIp.toUtf8().constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
|
||||||
|
{
|
||||||
|
auto opcode = message->get_opcode();
|
||||||
|
if (opcode != websocketpp::frame::opcode::text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QtConcurrent::run(&_threadPool, [=]() {
|
||||||
|
std::string payload = message->get_payload();
|
||||||
|
|
||||||
|
QMutexLocker locker(&_clMutex);
|
||||||
|
ConnectionProperties& connProperties = _connectionProperties[hdl];
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
WSRequestHandler handler(connProperties);
|
||||||
|
std::string response = handler.processIncomingMessage(payload);
|
||||||
|
|
||||||
|
_server.send(hdl, response, websocketpp::frame::opcode::text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onClose(connection_hdl hdl)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&_clMutex);
|
||||||
|
_connections.erase(hdl);
|
||||||
|
_connectionProperties.erase(hdl);
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
auto conn = _server.get_con_from_hdl(hdl);
|
||||||
|
auto localCloseCode = conn->get_local_close_code();
|
||||||
|
|
||||||
|
if (localCloseCode != websocketpp::close::status::going_away) {
|
||||||
|
QString clientIp = getRemoteEndpoint(hdl);
|
||||||
|
notifyDisconnection(clientIp);
|
||||||
|
blog(LOG_INFO, "client %s disconnected", clientIp.toUtf8().constData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WSServer::getRemoteEndpoint(connection_hdl hdl)
|
||||||
|
{
|
||||||
|
auto conn = _server.get_con_from_hdl(hdl);
|
||||||
|
return QString::fromStdString(conn->get_remote_endpoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::notifyConnection(QString clientIp)
|
||||||
|
{
|
||||||
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
|
QString title = tr("OBSWebsocket.NotifyConnect.Title");
|
||||||
|
QString msg = tr("OBSWebsocket.NotifyConnect.Message").arg(clientIp);
|
||||||
|
obs_frontend_pop_ui_translation();
|
||||||
|
|
||||||
|
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::notifyDisconnection(QString clientIp)
|
||||||
|
{
|
||||||
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
|
QString title = tr("OBSWebsocket.NotifyDisconnect.Title");
|
||||||
|
QString msg = tr("OBSWebsocket.NotifyDisconnect.Message").arg(clientIp);
|
||||||
|
obs_frontend_pop_ui_translation();
|
||||||
|
|
||||||
|
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
|
||||||
|
}
|
72
src/WSServer.h
Normal file
72
src/WSServer.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QMutex>
|
||||||
|
#include <QtCore/QSharedPointer>
|
||||||
|
#include <QtCore/QVariantHash>
|
||||||
|
#include <QtCore/QThreadPool>
|
||||||
|
|
||||||
|
#include <websocketpp/config/asio_no_tls.hpp>
|
||||||
|
#include <websocketpp/server.hpp>
|
||||||
|
|
||||||
|
#include "ConnectionProperties.h"
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||||
|
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||||
|
|
||||||
|
using websocketpp::connection_hdl;
|
||||||
|
|
||||||
|
typedef websocketpp::server<websocketpp::config::asio> server;
|
||||||
|
|
||||||
|
class WSServer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WSServer();
|
||||||
|
virtual ~WSServer();
|
||||||
|
void start(quint16 port);
|
||||||
|
void stop();
|
||||||
|
void broadcast(std::string message);
|
||||||
|
QThreadPool* threadPool() {
|
||||||
|
return &_threadPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onOpen(connection_hdl hdl);
|
||||||
|
void onMessage(connection_hdl hdl, server::message_ptr message);
|
||||||
|
void onClose(connection_hdl hdl);
|
||||||
|
|
||||||
|
QString getRemoteEndpoint(connection_hdl hdl);
|
||||||
|
void notifyConnection(QString clientIp);
|
||||||
|
void notifyDisconnection(QString clientIp);
|
||||||
|
|
||||||
|
server _server;
|
||||||
|
quint16 _serverPort;
|
||||||
|
std::set<connection_hdl, std::owner_less<connection_hdl>> _connections;
|
||||||
|
std::map<connection_hdl, ConnectionProperties, std::owner_less<connection_hdl>> _connectionProperties;
|
||||||
|
QMutex _clMutex;
|
||||||
|
QThreadPool _threadPool;
|
||||||
|
};
|
106
src/forms/settings-dialog.cpp
Normal file
106
src/forms/settings-dialog.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
|
#include "../obs-websocket.h"
|
||||||
|
#include "../Config.h"
|
||||||
|
#include "../WSServer.h"
|
||||||
|
#include "settings-dialog.h"
|
||||||
|
|
||||||
|
#define CHANGE_ME "changeme"
|
||||||
|
|
||||||
|
SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||||
|
QDialog(parent, Qt::Dialog),
|
||||||
|
ui(new Ui::SettingsDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui->authRequired, &QCheckBox::stateChanged,
|
||||||
|
this, &SettingsDialog::AuthCheckboxChanged);
|
||||||
|
connect(ui->buttonBox, &QDialogButtonBox::accepted,
|
||||||
|
this, &SettingsDialog::FormAccepted);
|
||||||
|
|
||||||
|
|
||||||
|
AuthCheckboxChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::showEvent(QShowEvent* event) {
|
||||||
|
auto conf = GetConfig();
|
||||||
|
|
||||||
|
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||||
|
ui->serverPort->setValue(conf->ServerPort);
|
||||||
|
|
||||||
|
ui->debugEnabled->setChecked(conf->DebugEnabled);
|
||||||
|
ui->alertsEnabled->setChecked(conf->AlertsEnabled);
|
||||||
|
|
||||||
|
ui->authRequired->setChecked(conf->AuthRequired);
|
||||||
|
ui->password->setText(CHANGE_ME);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::ToggleShowHide() {
|
||||||
|
if (!isVisible())
|
||||||
|
setVisible(true);
|
||||||
|
else
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::AuthCheckboxChanged() {
|
||||||
|
if (ui->authRequired->isChecked())
|
||||||
|
ui->password->setEnabled(true);
|
||||||
|
else
|
||||||
|
ui->password->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::FormAccepted() {
|
||||||
|
auto conf = GetConfig();
|
||||||
|
|
||||||
|
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||||
|
conf->ServerPort = ui->serverPort->value();
|
||||||
|
|
||||||
|
conf->DebugEnabled = ui->debugEnabled->isChecked();
|
||||||
|
conf->AlertsEnabled = ui->alertsEnabled->isChecked();
|
||||||
|
|
||||||
|
if (ui->authRequired->isChecked()) {
|
||||||
|
if (ui->password->text() != CHANGE_ME) {
|
||||||
|
conf->SetPassword(ui->password->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GetConfig()->Secret.isEmpty())
|
||||||
|
conf->AuthRequired = true;
|
||||||
|
else
|
||||||
|
conf->AuthRequired = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conf->AuthRequired = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf->Save();
|
||||||
|
|
||||||
|
auto server = GetServer();
|
||||||
|
if (conf->ServerEnabled) {
|
||||||
|
server->start(conf->ServerPort);
|
||||||
|
} else {
|
||||||
|
server->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDialog::~SettingsDialog() {
|
||||||
|
delete ui;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
obs-websocket
|
obs-websocket
|
||||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -16,14 +16,11 @@ You should have received a copy of the GNU General Public License along
|
|||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SETTINGSDIALOG_H
|
#pragma once
|
||||||
#define SETTINGSDIALOG_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
|
|
||||||
namespace Ui {
|
#include "ui_settings-dialog.h"
|
||||||
class SettingsDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SettingsDialog : public QDialog
|
class SettingsDialog : public QDialog
|
||||||
{
|
{
|
||||||
@ -42,5 +39,3 @@ private Q_SLOTS:
|
|||||||
private:
|
private:
|
||||||
Ui::SettingsDialog* ui;
|
Ui::SettingsDialog* ui;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGSDIALOG_H
|
|
151
src/forms/settings-dialog.ui
Normal file
151
src/forms/settings-dialog.ui
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>SettingsDialog</class>
|
||||||
|
<widget class="QDialog" name="SettingsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>407</width>
|
||||||
|
<height>195</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>OBSWebsocket.Settings.DialogTitle</string>
|
||||||
|
</property>
|
||||||
|
<property name="sizeGripEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetDefaultConstraint</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QCheckBox" name="authRequired">
|
||||||
|
<property name="text">
|
||||||
|
<string>OBSWebsocket.Settings.AuthRequired</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="lbl_password">
|
||||||
|
<property name="text">
|
||||||
|
<string>OBSWebsocket.Settings.Password</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="password">
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::Password</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QCheckBox" name="serverEnabled">
|
||||||
|
<property name="text">
|
||||||
|
<string>OBSWebsocket.Settings.ServerEnable</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="lbl_serverPort">
|
||||||
|
<property name="text">
|
||||||
|
<string>OBSWebsocket.Settings.ServerPort</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QSpinBox" name="serverPort">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1024</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>65535</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>4444</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QCheckBox" name="alertsEnabled">
|
||||||
|
<property name="text">
|
||||||
|
<string>OBSWebsocket.Settings.AlertsEnable</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QCheckBox" name="debugEnabled">
|
||||||
|
<property name="text">
|
||||||
|
<string>OBSWebsocket.Settings.DebugEnable</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>SettingsDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>294</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>314</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>SettingsDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>300</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>314</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
104
src/obs-websocket.cpp
Normal file
104
src/obs-websocket.cpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <obs-module.h>
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtWidgets/QAction>
|
||||||
|
#include <QtWidgets/QMainWindow>
|
||||||
|
|
||||||
|
#include "obs-websocket.h"
|
||||||
|
#include "WSServer.h"
|
||||||
|
#include "WSEvents.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "forms/settings-dialog.h"
|
||||||
|
|
||||||
|
void ___source_dummy_addref(obs_source_t*) {}
|
||||||
|
void ___sceneitem_dummy_addref(obs_sceneitem_t*) {}
|
||||||
|
void ___data_dummy_addref(obs_data_t*) {}
|
||||||
|
void ___data_array_dummy_addref(obs_data_array_t*) {}
|
||||||
|
void ___output_dummy_addref(obs_output_t*) {}
|
||||||
|
|
||||||
|
OBS_DECLARE_MODULE()
|
||||||
|
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||||
|
|
||||||
|
ConfigPtr _config;
|
||||||
|
WSServerPtr _server;
|
||||||
|
WSEventsPtr _eventsSystem;
|
||||||
|
|
||||||
|
bool obs_module_load(void) {
|
||||||
|
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||||
|
blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s",
|
||||||
|
QT_VERSION_STR, qVersion());
|
||||||
|
|
||||||
|
// Core setup
|
||||||
|
_config = ConfigPtr(new Config());
|
||||||
|
_config->MigrateFromGlobalSettings(); // TODO remove this on the next minor jump
|
||||||
|
_config->Load();
|
||||||
|
|
||||||
|
_server = WSServerPtr(new WSServer());
|
||||||
|
_eventsSystem = WSEventsPtr(new WSEvents(_server));
|
||||||
|
|
||||||
|
if (_config->ServerEnabled) {
|
||||||
|
_server->start(_config->ServerPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI setup
|
||||||
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
|
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
SettingsDialog* settingsDialog = new SettingsDialog(mainWindow);
|
||||||
|
obs_frontend_pop_ui_translation();
|
||||||
|
|
||||||
|
const char* menuActionText =
|
||||||
|
obs_module_text("OBSWebsocket.Settings.DialogTitle");
|
||||||
|
QAction* menuAction =
|
||||||
|
(QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
|
||||||
|
QObject::connect(menuAction, &QAction::triggered, [settingsDialog] {
|
||||||
|
// The settings dialog belongs to the main window. Should be ok
|
||||||
|
// to pass the pointer to this QAction belonging to the main window
|
||||||
|
settingsDialog->ToggleShowHide();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Loading finished
|
||||||
|
blog(LOG_INFO, "module loaded!");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void obs_module_unload() {
|
||||||
|
_server->stop();
|
||||||
|
|
||||||
|
_eventsSystem.reset();
|
||||||
|
_server.reset();
|
||||||
|
_config.reset();
|
||||||
|
|
||||||
|
blog(LOG_INFO, "goodbye!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigPtr GetConfig() {
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
|
||||||
|
WSServerPtr GetServer() {
|
||||||
|
return _server;
|
||||||
|
}
|
||||||
|
|
||||||
|
WSEventsPtr GetEventsSystem() {
|
||||||
|
return _eventsSystem;
|
||||||
|
}
|
56
src/obs-websocket.h
Normal file
56
src/obs-websocket.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <obs.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
void ___source_dummy_addref(obs_source_t*);
|
||||||
|
void ___sceneitem_dummy_addref(obs_sceneitem_t*);
|
||||||
|
void ___data_dummy_addref(obs_data_t*);
|
||||||
|
void ___data_array_dummy_addref(obs_data_array_t*);
|
||||||
|
void ___output_dummy_addref(obs_output_t*);
|
||||||
|
|
||||||
|
using OBSSourceAutoRelease =
|
||||||
|
OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
|
||||||
|
using OBSSceneItemAutoRelease =
|
||||||
|
OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>;
|
||||||
|
using OBSDataAutoRelease =
|
||||||
|
OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>;
|
||||||
|
using OBSDataArrayAutoRelease =
|
||||||
|
OBSRef<obs_data_array_t*, ___data_array_dummy_addref, obs_data_array_release>;
|
||||||
|
using OBSOutputAutoRelease =
|
||||||
|
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
|
||||||
|
|
||||||
|
class Config;
|
||||||
|
typedef std::shared_ptr<Config> ConfigPtr;
|
||||||
|
|
||||||
|
class WSServer;
|
||||||
|
typedef std::shared_ptr<WSServer> WSServerPtr;
|
||||||
|
|
||||||
|
class WSEvents;
|
||||||
|
typedef std::shared_ptr<WSEvents> WSEventsPtr;
|
||||||
|
|
||||||
|
ConfigPtr GetConfig();
|
||||||
|
WSServerPtr GetServer();
|
||||||
|
WSEventsPtr GetEventsSystem();
|
||||||
|
|
||||||
|
#define OBS_WEBSOCKET_VERSION "4.6.0"
|
||||||
|
|
||||||
|
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
Reference in New Issue
Block a user