Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
691aa9c899 | ||
|
|
0dd419a5a3 | ||
|
|
1269c46f56 | ||
|
|
f8badd51c5 | ||
|
|
ed8bf5af9e | ||
|
|
b2521de84e | ||
|
|
165b5e6638 | ||
|
|
e742466409 | ||
|
|
201bf5b447 | ||
|
|
7bed913a1e | ||
|
|
e067fd8fa2 | ||
|
|
ba7d5feb81 | ||
|
|
4a9e7ad2a1 | ||
|
|
4b56a44a47 | ||
|
|
bc98b43de2 | ||
|
|
16b3df5279 | ||
|
|
d202f2f6e3 | ||
|
|
142474eac8 | ||
|
|
acc89dba39 | ||
|
|
8cd366d2cd | ||
|
|
2d67c1b22b | ||
|
|
77b0ef55c9 | ||
|
|
d76f404a4c | ||
|
|
d0b45a019f | ||
|
|
0728e999eb | ||
|
|
f325e9a206 | ||
|
|
49b03e65b2 | ||
|
|
2bc18e2f02 | ||
|
|
dadb292676 | ||
|
|
2c9902ce77 | ||
|
|
6d36219e88 | ||
|
|
470862dbe0 | ||
|
|
f6017824ee | ||
|
|
adeb77cf65 | ||
|
|
9bc080815a | ||
|
|
d03eebb5ba | ||
|
|
ca453d8059 | ||
|
|
cb8126d201 | ||
|
|
1626f30bb6 | ||
|
|
47b5ec2678 | ||
|
|
ccd032834d | ||
|
|
c7ffd51cec | ||
|
|
38f8c50d7f | ||
|
|
fd653b87af | ||
|
|
9bf02e477b | ||
|
|
4440c668f1 | ||
|
|
63d5417a2c | ||
|
|
28d9a0faf2 | ||
|
|
04845df09c | ||
|
|
85df511a23 | ||
|
|
4ecb8bfede | ||
|
|
6008865a41 | ||
|
|
02926b2c6b | ||
|
|
49c5fd308e | ||
|
|
7eeff3bc35 | ||
|
|
6e99889681 | ||
|
|
8cbd09fe9a | ||
|
|
edc703c534 | ||
|
|
f85d545c17 | ||
|
|
0830173e59 | ||
|
|
0e21e3a9ed | ||
|
|
6356427454 | ||
|
|
4dc6220f28 | ||
|
|
03fcfaff28 | ||
|
|
d5c96f77cb | ||
|
|
bb6f2f0c0d | ||
|
|
8d54ed9316 | ||
|
|
44123c74d9 | ||
|
|
fdf2548fe3 | ||
|
|
bee72bd854 | ||
|
|
8ec8f32935 | ||
|
|
1d33feec8b | ||
|
|
80af6a9636 | ||
|
|
94eb4dd8a0 | ||
|
|
d3ce55cbd6 | ||
|
|
0b942caa98 | ||
|
|
afc6289a56 | ||
|
|
79684598ab | ||
|
|
b901f2f506 | ||
|
|
14294009f0 | ||
|
|
adbebd23c8 | ||
|
|
3973e13706 | ||
|
|
e56e68dd6f | ||
|
|
c3fcff8d29 | ||
|
|
0152f48886 | ||
|
|
bbd2e37067 | ||
|
|
8377b5062a | ||
|
|
086f1af974 | ||
|
|
652e989927 | ||
|
|
5480e15595 | ||
|
|
375670beb4 | ||
|
|
fa164f0e02 | ||
|
|
a9ede7a904 | ||
|
|
fa32d157ca | ||
|
|
10839f88d8 | ||
|
|
8fa909dea8 | ||
|
|
94250624a8 | ||
|
|
6f264441ab | ||
|
|
03cf463182 | ||
|
|
3e29501b23 | ||
|
|
600b882929 | ||
|
|
be69af925a | ||
|
|
d14ee640c5 | ||
|
|
ac6c688054 | ||
|
|
8f8e86ab4f | ||
|
|
ab1d6bfa90 | ||
|
|
e038601b37 | ||
|
|
008beb9444 | ||
|
|
835d9c9ef6 | ||
|
|
e29e5bd650 | ||
|
|
fecdbec31c | ||
|
|
66fdb607d8 | ||
|
|
6d4f192cbe | ||
|
|
4a59af7c20 | ||
|
|
156b363049 | ||
|
|
c68540c169 | ||
|
|
f89372e265 | ||
|
|
c134dffec2 | ||
|
|
c2d4527d7e | ||
|
|
2071b89d18 | ||
|
|
46902afdc4 | ||
|
|
d23e640977 | ||
|
|
9c18ab4142 | ||
|
|
22e270c4df | ||
|
|
16bd288e70 | ||
|
|
39643153df | ||
|
|
2cf9fdfd40 | ||
|
|
f5226562ae | ||
|
|
32ad5d2f8a | ||
|
|
245f47f660 | ||
|
|
17e3fd07f9 | ||
|
|
cdbe480863 | ||
|
|
189ad76f38 | ||
|
|
d8528fdc81 | ||
|
|
c257a8cab0 | ||
|
|
2912a3a5cb | ||
|
|
75f1d4d392 | ||
|
|
a6159cc785 | ||
|
|
38affb4981 | ||
|
|
e64090d88f | ||
|
|
ef60aeeead | ||
|
|
25112b2c27 | ||
|
|
c0b1326dbd | ||
|
|
f77e533dd6 | ||
|
|
4b001f9366 | ||
|
|
41eadce89a | ||
|
|
46194a5aa0 | ||
|
|
3630880454 | ||
|
|
a59ffacd3a | ||
|
|
03817bcfd7 | ||
|
|
2c4751bef9 | ||
|
|
7caa778ff8 | ||
|
|
3977d342ba | ||
|
|
4e83421104 | ||
|
|
adafacbf81 | ||
|
|
053893005b | ||
|
|
838af88d64 | ||
|
|
573ed07b4f | ||
|
|
aad8673ff0 | ||
|
|
b6a58ea8ab | ||
|
|
8c7bf18b65 | ||
|
|
3823c95582 | ||
|
|
ad0f3dca30 | ||
|
|
5248bbe9c3 | ||
|
|
1262a7b41f | ||
|
|
85611bcab5 | ||
|
|
46b5d9522d | ||
|
|
6dc9ee569b | ||
|
|
9916f64065 | ||
|
|
0ad6b13fae | ||
|
|
5bbdb76d21 | ||
|
|
cbf42b1e52 | ||
|
|
72867d855d | ||
|
|
7d5b984157 | ||
|
|
a3d5771b14 | ||
|
|
1338b4fb85 | ||
|
|
379d965769 | ||
|
|
d7bc73250d | ||
|
|
349daef47f | ||
|
|
138658c635 | ||
|
|
e058839faf | ||
|
|
d4d5146f66 | ||
|
|
85f4838067 | ||
|
|
f23108a3ba | ||
|
|
1623fc34cf | ||
|
|
d244c9bd1f | ||
|
|
e53e997cd4 | ||
|
|
5c646ef3d2 | ||
|
|
f01d36d4a0 | ||
|
|
37fc7e2cb4 | ||
|
|
ea68353328 | ||
|
|
da4bfb851c | ||
|
|
6a1f85975f | ||
|
|
700552bd75 | ||
|
|
5750b99af3 | ||
|
|
cc35e6b3a4 | ||
|
|
3b4800eb78 | ||
|
|
a2855ef522 | ||
|
|
935406b75b | ||
|
|
e933c5de60 | ||
|
|
a23a5491ae | ||
|
|
1ad8f51b7a | ||
|
|
4d1619346d | ||
|
|
df8c273cb9 | ||
|
|
6eca97b4aa | ||
|
|
39f8398b10 | ||
|
|
e32a356cb5 | ||
|
|
e0c556da10 | ||
|
|
a3f9e07cf7 | ||
|
|
ff40e55cd1 | ||
|
|
e548bda73a | ||
|
|
cf11b06f3e | ||
|
|
a799c39aff | ||
|
|
ff230b3433 | ||
|
|
a9f4db52b5 | ||
|
|
071e2a764a | ||
|
|
e050c914bb | ||
|
|
cd9ef74f3f | ||
|
|
644687a50c | ||
|
|
e7132e3371 | ||
|
|
7223ab3be5 | ||
|
|
99952281cb | ||
|
|
9c518dfeb6 | ||
|
|
28e2f13e87 | ||
|
|
4a9e109de5 | ||
|
|
f5b56ca67f | ||
|
|
b7ad4d3562 | ||
|
|
94a8caa5df | ||
|
|
b650ac0ca3 | ||
|
|
9dda594519 | ||
|
|
0ea6248773 | ||
|
|
fee003b824 | ||
|
|
cd415cc639 | ||
|
|
10ceac2ce3 | ||
|
|
86a0ff46ee | ||
|
|
3887ad0c4c | ||
|
|
58f534fcc8 | ||
|
|
f8e14006c1 | ||
|
|
a959a346b0 | ||
|
|
65459b3f4e | ||
|
|
2216899204 | ||
|
|
5fb4887c67 | ||
|
|
6ff2add9dd | ||
|
|
c963c1254d | ||
|
|
8afce40e81 | ||
|
|
e44504bc48 | ||
|
|
11b79dda5b | ||
|
|
8cee8b679c | ||
|
|
1cb9360d83 | ||
|
|
297f5ad7e6 | ||
|
|
3f2a011d7b | ||
|
|
dd0c36bb20 | ||
|
|
aff1bdfe89 | ||
|
|
00533f5d3b | ||
|
|
eff682bc05 | ||
|
|
16a210efa2 | ||
|
|
99a2a4ca1a | ||
|
|
67af68fcf1 | ||
|
|
b96e2d1bdf | ||
|
|
2bb0ce086f | ||
|
|
9bc518dc68 | ||
|
|
f60433ff3f | ||
|
|
b4493707ca | ||
|
|
0fe10d8654 | ||
|
|
87d9590f3b | ||
|
|
eeff9b74da | ||
|
|
ceebd19c0e | ||
|
|
00e0566dd4 | ||
|
|
acd4a04076 | ||
|
|
ac0aa29578 | ||
|
|
9a053d9ed8 | ||
|
|
9124a1fee5 | ||
|
|
858e7c7440 | ||
|
|
e5669e2a30 | ||
|
|
431b32b680 | ||
|
|
7dce11b4c4 | ||
|
|
9fca3a5984 | ||
|
|
733d5c38f3 | ||
|
|
3834b5a604 | ||
|
|
07e36029d0 | ||
|
|
689221f10e | ||
|
|
60dacdf05f | ||
|
|
1614bb0609 | ||
|
|
a386867dd0 | ||
|
|
e177d6eaea | ||
|
|
464492529e | ||
|
|
b950882846 | ||
|
|
63808c9e9c | ||
|
|
76d138d64d | ||
|
|
1232644268 | ||
|
|
2e776baec9 | ||
|
|
8fb4829aef | ||
|
|
fcbc844587 | ||
|
|
aecb1163e3 | ||
|
|
0e51a49898 | ||
|
|
adce543c45 | ||
|
|
83a32f96e6 | ||
|
|
6c72f0331b | ||
|
|
92f1fbf33b | ||
|
|
424fe2c2f0 | ||
|
|
f5d629c62c | ||
|
|
19fa975b6d | ||
|
|
a4951309d3 | ||
|
|
3ec53c29d4 | ||
|
|
3fcaafc42b | ||
|
|
5318acc427 | ||
|
|
daa4fc80eb | ||
|
|
c094d74d8e | ||
|
|
5418936816 | ||
|
|
c3b7679268 | ||
|
|
d12b83d1ef | ||
|
|
32b78d1a4c | ||
|
|
71f66fe8c0 | ||
|
|
d054bbe35f | ||
|
|
bd1c4e3cc9 | ||
|
|
928b765700 | ||
|
|
45acc28384 | ||
|
|
4e65abf6b1 | ||
|
|
d818f8a7ce | ||
|
|
c0850ce9d2 | ||
|
|
c3941df7e9 | ||
|
|
b1b46b3ec0 | ||
|
|
dff6679a90 | ||
|
|
4ab21aed0e | ||
|
|
d67a40f155 | ||
|
|
b506917382 | ||
|
|
944f7422b3 | ||
|
|
4843c046d7 | ||
|
|
61df4e7e61 | ||
|
|
48558316d1 | ||
|
|
5903b358ee | ||
|
|
6881098ca2 | ||
|
|
4d783649f5 | ||
|
|
2b40224b2b | ||
|
|
e4d6be3bb3 | ||
|
|
6c0b1550fd | ||
|
|
967fbe8072 | ||
|
|
c7dd7a2c21 | ||
|
|
49ed0e43e9 | ||
|
|
6bae985e2c | ||
|
|
39d7d9e213 | ||
|
|
eb3501e090 | ||
|
|
83566ad4e1 | ||
|
|
19e78d5eb6 | ||
|
|
0a2fdbf0c0 | ||
|
|
91f95f13ae | ||
|
|
ebf46042ba | ||
|
|
8547cee1b9 | ||
|
|
56729469bf | ||
|
|
e619d31622 | ||
|
|
370cc76dbe | ||
|
|
1ac2f9e923 | ||
|
|
8caf360671 | ||
|
|
59b1dcfd13 | ||
|
|
34a115706e | ||
|
|
b6c029df38 | ||
|
|
31ab333f5b | ||
|
|
0582655954 | ||
|
|
c8f61b0b87 | ||
|
|
a2c4d440db | ||
|
|
b64ad64524 | ||
|
|
e9e6f1deb9 | ||
|
|
6f381362dd | ||
|
|
5d1bed4d25 | ||
|
|
9b9104b502 | ||
|
|
facbc01a9c | ||
|
|
3ba0e35032 | ||
|
|
958b391afa | ||
|
|
678c8054d7 | ||
|
|
d90657b05a | ||
|
|
9e56547435 | ||
|
|
bcb4d0a905 | ||
|
|
225b66cb06 | ||
|
|
cd1ff0da97 | ||
|
|
1d1e553191 | ||
|
|
e10691e2b7 | ||
|
|
9e20efff4b | ||
|
|
9e32c1da3d | ||
|
|
39b2621e30 | ||
|
|
4e44136f51 | ||
|
|
62bd695138 | ||
|
|
0b9454896c | ||
|
|
51c1c6db33 | ||
|
|
0c1fc66be8 | ||
|
|
ee67ff56c7 | ||
|
|
ea1a2f3cd0 | ||
|
|
25ca131da1 | ||
|
|
05f6dd86b1 | ||
|
|
2b052fef1e | ||
|
|
d6aa64b816 | ||
|
|
b3664d0e2d | ||
|
|
34eb2ccd31 | ||
|
|
0d32b8f06f | ||
|
|
73316ab5b8 | ||
|
|
69ed345191 | ||
|
|
78188527a9 | ||
|
|
2d57c3f7c1 | ||
|
|
4bfa6d270d | ||
|
|
85e6eb9eb6 | ||
|
|
83593a0ea7 | ||
|
|
dd6dbdf1aa | ||
|
|
6a2c3d7b28 | ||
|
|
15161ede06 | ||
|
|
ee370ec978 | ||
|
|
e844b5df65 | ||
|
|
087e38c9d7 | ||
|
|
2bf39aeb2e | ||
|
|
ccc85772d3 | ||
|
|
9d2f7704c7 | ||
|
|
6e20c126c3 | ||
|
|
9b149a1eb5 | ||
|
|
6010182592 | ||
|
|
868288e70f | ||
|
|
d73c0ee319 | ||
|
|
5cf9ea74f4 | ||
|
|
2ab705efdf | ||
|
|
1f4865c468 | ||
|
|
8388076b71 | ||
|
|
7a37d11aea | ||
|
|
cdd5802a72 | ||
|
|
951b7bcfb0 | ||
|
|
4cb060f496 | ||
|
|
c5da9e9bf7 | ||
|
|
386516ade9 | ||
|
|
211f90307f | ||
|
|
d6558c3619 | ||
|
|
bb265a51fd | ||
|
|
4bc8934b1b | ||
|
|
1bc15a1ba3 | ||
|
|
5026a6fbcf | ||
|
|
628ce0f12e | ||
|
|
8d05e8e670 | ||
|
|
2b11f12763 | ||
|
|
3536bf06ba | ||
|
|
88c0e358aa | ||
|
|
7b5d9cde2e | ||
|
|
c5fc72cc2c | ||
|
|
ab503875cb | ||
|
|
a9c4417434 | ||
|
|
679c0a2b4c | ||
|
|
5c1d2de9e5 | ||
|
|
1442495c9e | ||
|
|
22ce2edfa3 | ||
|
|
69026cbcd6 | ||
|
|
76f765ce5f | ||
|
|
b8ba716f34 | ||
|
|
131d115761 | ||
|
|
9b9f4f7603 | ||
|
|
c32f2d3f50 | ||
|
|
c2d3028668 | ||
|
|
338523e216 | ||
|
|
54bf701495 | ||
|
|
c1bf40d825 | ||
|
|
905e3eaf73 | ||
|
|
900a4cca14 | ||
|
|
a44e9c87d7 | ||
|
|
7b9bbba3fd | ||
|
|
fc6591f26f | ||
|
|
f50d1bd488 | ||
|
|
0c386ef337 | ||
|
|
245613162a | ||
|
|
d2eb674bb5 | ||
|
|
d5af136da2 | ||
|
|
ff8cb20cae | ||
|
|
3e48df9756 | ||
|
|
fb04850ae8 | ||
|
|
f62a405979 | ||
|
|
58180ae3fa | ||
|
|
e416dd5f78 | ||
|
|
e37c22b259 | ||
|
|
238d17020b | ||
|
|
96f11a4a69 | ||
|
|
5fcfa84766 | ||
|
|
04ecd45f7c | ||
|
|
dd9ad9728a | ||
|
|
6a3e1d8a88 | ||
|
|
d2e0cc779c | ||
|
|
ee4c1defa0 | ||
|
|
49417e7bc9 | ||
|
|
e301f2af16 | ||
|
|
0df5135f0b | ||
|
|
ab7ccc7732 | ||
|
|
02c6a12a82 | ||
|
|
ed1efdd102 | ||
|
|
e51701ef00 | ||
|
|
2b0fb3844b | ||
|
|
323613e4db | ||
|
|
80f9118c0d | ||
|
|
074a31606c | ||
|
|
3bb78db2da | ||
|
|
e5fc439da2 | ||
|
|
87ad97c00f | ||
|
|
ce21c398b5 | ||
|
|
ed56d26b1c | ||
|
|
08a2312099 | ||
|
|
619e936c39 | ||
|
|
75e88ee31e | ||
|
|
2e7abf0eb2 | ||
|
|
6941f006b3 | ||
|
|
e0d03baf1c | ||
|
|
08ef392fca | ||
|
|
b7d7b9a058 | ||
|
|
a7fc2a65e0 | ||
|
|
a707b21446 | ||
|
|
0e8980da95 | ||
|
|
ff991dd617 | ||
|
|
a8c1451a28 | ||
|
|
9198960cc7 | ||
|
|
b8478bfedc | ||
|
|
0f443af355 | ||
|
|
e63ce4d101 | ||
|
|
e57f0cd723 | ||
|
|
4fb3f7fa97 | ||
|
|
5759c122ed | ||
|
|
55ac533718 | ||
|
|
282b3d6545 | ||
|
|
eb41536749 | ||
|
|
0728959ebe | ||
|
|
b118f80b52 | ||
|
|
25073803ba | ||
|
|
1046cfe9a0 | ||
|
|
2250cba35c | ||
|
|
1e24731b60 | ||
|
|
034ee52eec | ||
|
|
13a1a4d97b | ||
|
|
f22d05a34e | ||
|
|
0d3149a696 | ||
|
|
11e5028020 | ||
|
|
2b96bb1c77 | ||
|
|
f843acf12f | ||
|
|
05d3567489 | ||
|
|
25c4ca01de | ||
|
|
ac66ffe302 | ||
|
|
f0b4e2e2ad | ||
|
|
d72930877a | ||
|
|
4d45c538cc | ||
|
|
f368076aaf | ||
|
|
61c5b958f2 | ||
|
|
334788c26f | ||
|
|
12ad168e13 | ||
|
|
48fbec02e4 | ||
|
|
ba453f9405 | ||
|
|
00491d3bce | ||
|
|
98f73928fa | ||
|
|
3dccec3739 | ||
|
|
5e40c9f9ad | ||
|
|
5bcc452c9f | ||
|
|
5bf218d930 | ||
|
|
5df330f608 | ||
|
|
bd9e2e3215 | ||
|
|
d1ecc894b3 | ||
|
|
5a2d5af887 | ||
|
|
45895fa8bf | ||
|
|
fa7710ce44 | ||
|
|
cd7d560e63 | ||
|
|
fcf0ec98e5 | ||
|
|
7691d2c0eb | ||
|
|
686418997b | ||
|
|
f4b6fd709d | ||
|
|
8126173128 | ||
|
|
5e4ba088be | ||
|
|
6682e64519 | ||
|
|
2140cc7820 | ||
|
|
7bafa39960 | ||
|
|
37379220b4 | ||
|
|
db6906d7a4 | ||
|
|
b262ae47e1 | ||
|
|
797b1a0b71 | ||
|
|
77955f6bd6 | ||
|
|
17a9fa39f5 | ||
|
|
f6032969c3 | ||
|
|
686696a6d9 | ||
|
|
c629acfa4b | ||
|
|
02ede90d77 | ||
|
|
84cb37f6f3 | ||
|
|
46526603cc | ||
|
|
80c6d488cf | ||
|
|
6ea23d94cb | ||
|
|
29c8382825 | ||
|
|
b83811cc62 | ||
|
|
47a590e92a | ||
|
|
e2ab307da7 | ||
|
|
a83274ab28 | ||
|
|
da8a210fcc | ||
|
|
0f8ba42975 | ||
|
|
35a2c1111d | ||
|
|
d263482f7e | ||
|
|
19beac795b | ||
|
|
e61e96b352 | ||
|
|
4b8691b943 | ||
|
|
487f987c25 | ||
|
|
a69d7eaede | ||
|
|
a87969d5b8 | ||
|
|
8f25081bbb | ||
|
|
6a36e73463 | ||
|
|
d69688678d | ||
|
|
56c589f042 | ||
|
|
df2b67ac99 | ||
|
|
0473233775 | ||
|
|
57b12d7a54 | ||
|
|
6c6d7b73c1 | ||
|
|
10dcdc31d8 | ||
|
|
7c725a4d80 | ||
|
|
4640ec86ed | ||
|
|
03cabd0506 | ||
|
|
879c2f368c | ||
|
|
fc73e7a3b2 | ||
|
|
b9bd1c8c44 | ||
|
|
b5ce7a8ff7 | ||
|
|
51e915b598 | ||
|
|
0addca3876 | ||
|
|
09349ebdef | ||
|
|
e93d031f86 | ||
|
|
c491132c09 | ||
|
|
0fef5a389d | ||
|
|
0047bfe51f | ||
|
|
994a60c8a4 | ||
|
|
50d53cd826 | ||
|
|
1d6906e5e7 | ||
|
|
2c8f2ee408 | ||
|
|
4810451e6a | ||
|
|
60801e3553 | ||
|
|
9bdd92aeb3 | ||
|
|
835c9c1166 | ||
|
|
9da143ed81 | ||
|
|
f7a032a918 | ||
|
|
eacdda384a | ||
|
|
1f13fd691b | ||
|
|
73de0877a1 | ||
|
|
616c7673a4 | ||
|
|
489e60d2b1 | ||
|
|
af2a2728f8 | ||
|
|
91f012ee9f | ||
|
|
c4bc77db05 | ||
|
|
6579ea681e | ||
|
|
07b6aa099b | ||
|
|
e10a296305 | ||
|
|
c42fe94b46 | ||
|
|
1d0743cda6 | ||
|
|
0f6f105ec1 | ||
|
|
578ad81b8a | ||
|
|
3cbdba78d6 | ||
|
|
1de7592e86 | ||
|
|
6a9891adee | ||
|
|
29700e6f18 | ||
|
|
7426e3e78c | ||
|
|
724a11ae4f | ||
|
|
03d08a2c65 | ||
|
|
f24e68d0d9 | ||
|
|
a07e94ca74 | ||
|
|
14a384436c | ||
|
|
6be5aa6507 | ||
|
|
97d17f505b | ||
|
|
c2ed8c5517 | ||
|
|
ac299287e4 | ||
|
|
ec5aec2601 | ||
|
|
59033ff8d7 | ||
|
|
88edc7f263 | ||
|
|
19882a9373 | ||
|
|
620385501f | ||
|
|
d0eb864814 | ||
|
|
1920735efa | ||
|
|
7370fa5c94 | ||
|
|
a875a65a7f | ||
|
|
88414cd3db | ||
|
|
00aff53e35 | ||
|
|
62561ac423 | ||
|
|
d26cef7ef4 | ||
|
|
527fb16428 | ||
|
|
0f7ba20e65 | ||
|
|
94e9bc35b1 | ||
|
|
b3fd9e51d7 | ||
|
|
bef2456bea | ||
|
|
25f65c3596 | ||
|
|
8260624a41 | ||
|
|
7ab1c43588 | ||
|
|
7d0477204b | ||
|
|
8f1dda44b9 | ||
|
|
ee03f6b866 | ||
|
|
9e41537554 | ||
|
|
8baa2e9067 | ||
|
|
556db88ff7 | ||
|
|
9063331d38 | ||
|
|
594906c121 | ||
|
|
357a907838 | ||
|
|
6bc142a07b | ||
|
|
cd6e08956c | ||
|
|
9937b67509 | ||
|
|
c478d641c6 | ||
|
|
1702f37d3e | ||
|
|
e1920ddb01 | ||
|
|
9b5b93dc31 | ||
|
|
cd4c5bbc56 | ||
|
|
47bae92dd5 | ||
|
|
a5bbca55a2 | ||
|
|
a4f5c4f211 | ||
|
|
9c61e5725f | ||
|
|
8c4f2e46f7 | ||
|
|
76253a9583 | ||
|
|
5653215f68 | ||
|
|
1b9e9575f1 | ||
|
|
dfde770274 | ||
|
|
1737eae39b | ||
|
|
fb32ef3629 | ||
|
|
5734a0a0b7 | ||
|
|
758a888a53 | ||
|
|
ed0045afec | ||
|
|
de5fb7a19b | ||
|
|
e2bebbcc61 | ||
|
|
24fd69e7dc | ||
|
|
16b1f9164f | ||
|
|
51a62ce1d8 | ||
|
|
23b68b2d15 | ||
|
|
7ee449c0e4 | ||
|
|
2891beef5f | ||
|
|
da47a6ae46 | ||
|
|
96145baa7a | ||
|
|
ab31ab393a | ||
|
|
38ee17a852 | ||
|
|
78be18ed89 | ||
|
|
5fd8a7c587 | ||
|
|
79a075e338 | ||
|
|
5c3a6de2b7 | ||
|
|
5a77571d6f | ||
|
|
1e6a380f99 | ||
|
|
8e00552c61 | ||
|
|
fabd01ca9e | ||
|
|
ab2efcf05a | ||
|
|
dfaa4005bb | ||
|
|
a64a29ce07 | ||
|
|
68f21fa8e5 | ||
|
|
d9dbb2bcee | ||
|
|
782ac4c362 | ||
|
|
ff68114fe3 | ||
|
|
cbf48bdf0a | ||
|
|
fcb68bc54f | ||
|
|
a591380d60 | ||
|
|
822dfc433b | ||
|
|
17ecd52755 | ||
|
|
6fd35deaa4 | ||
|
|
d80c2e35ca | ||
|
|
7ed6c559e0 | ||
|
|
e5b1eda3a1 | ||
|
|
3120cd785b | ||
|
|
b036e01afc | ||
|
|
01e379eefe | ||
|
|
17a00a1604 | ||
|
|
59660840e4 | ||
|
|
2775ca09c7 | ||
|
|
8c8fea0d4b | ||
|
|
7f23c4a508 | ||
|
|
fda5c8610c | ||
|
|
049034e5a9 | ||
|
|
6ae3beb2a5 | ||
|
|
13853449d2 | ||
|
|
2679ba75af | ||
|
|
561080568c | ||
|
|
4edf1e2cf5 | ||
|
|
e7998c3559 | ||
|
|
8f674d6a3d | ||
|
|
f1a9fd2b4f | ||
|
|
46f75f0676 | ||
|
|
d3b62e38fc | ||
|
|
c8ebdc0a88 | ||
|
|
238eb816d0 | ||
|
|
0f054bb96f | ||
|
|
711996b478 | ||
|
|
a43fe609d3 | ||
|
|
2712922ce5 | ||
|
|
49b685b3ec | ||
|
|
302606564c | ||
|
|
1fc1b5bc60 | ||
|
|
f260d75372 | ||
|
|
a03a50d238 | ||
|
|
928284990b | ||
|
|
03046988c7 | ||
|
|
c860433065 | ||
|
|
5d66c17bc1 | ||
|
|
16b5df7dd1 | ||
|
|
589c025951 | ||
|
|
7fbfe94b71 | ||
|
|
27bb8fd737 | ||
|
|
cc285d5488 | ||
|
|
c8522de476 | ||
|
|
94f2e39367 | ||
|
|
bd83af8bf4 | ||
|
|
420eebf831 | ||
|
|
e0691dcd97 | ||
|
|
24442c29bb | ||
|
|
871315c2bc | ||
|
|
7b116b936c | ||
|
|
9aa84c03d7 | ||
|
|
471325a358 | ||
|
|
978ab18cee | ||
|
|
ea0c16e334 | ||
|
|
dd0ff298d5 | ||
|
|
eca0b30728 | ||
|
|
9ade4b5ae0 | ||
|
|
4f8c5189e0 | ||
|
|
0f2d54e8c0 |
@@ -0,0 +1,21 @@
|
||||
BasedOnStyle: Chromium
|
||||
AlignAfterOpenBracket: true
|
||||
AlignOperands: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
ColumnLimit: 160
|
||||
ContinuationIndentWidth: 4
|
||||
DerivePointerAlignment: false
|
||||
IndentWidth: 4
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerBindsToType: false
|
||||
PointerAlignment: Right
|
||||
SpacesInContainerLiterals: true
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
name: Build IPAs
|
||||
name: Build VoidLink IPA
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "noWaterMark" ]
|
||||
branches: [ "feat/frame_pacing_metal" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build IPAs
|
||||
name: Build VoidLink IPA
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
@@ -16,30 +16,15 @@ jobs:
|
||||
- name: Checkout submodules
|
||||
run: "git submodule update --init --recursive"
|
||||
|
||||
- name: Build FullScreen Version
|
||||
- name: Build VoidLink
|
||||
run: |
|
||||
plutil -replace UIRequiresFullScreen -bool true Limelight/Limelight-Info.plist
|
||||
xcodebuild -verbose -scheme Moonlight-ZWM -sdk iphoneos -configuration AppStoreDistribution archive -archivePath $PWD/build/MoonlightFullScreen.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
|
||||
mkdir -p ./build/IPA_FullScreen && mkdir -p ./build/IPA_FullScreen/Payload
|
||||
cp -r ./build/MoonlightFullScreen.xcarchive/Products/Applications/Moonlight-ZWM.app ./build/IPA_FullScreen/Payload/Moonlight-ZWM.app
|
||||
cd ./build/IPA_FullScreen && zip -r ../../build/Moonlight-ZWM_FullScreen.ipa ./Payload/* && cd ../..
|
||||
|
||||
- name: Upload FullScreen IPA to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Moonlight-ZWM_FullScreen.ipa
|
||||
path: build/Moonlight-ZWM_FullScreen.ipa
|
||||
xcodebuild -verbose -scheme VoidLink -sdk iphoneos -configuration Debug archive -archivePath $PWD/build/VoidLink.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
|
||||
mkdir -p ./build/IPA && mkdir -p ./build/IPA/Payload
|
||||
cp -r ./build/VoidLink.xcarchive/Products/Applications/VoidLink.app ./build/IPA/Payload/VoidLink.app
|
||||
cd ./build/IPA && zip -r ../../build/VoidLink.ipa ./Payload/* && cd ../..
|
||||
|
||||
- name: Build StageManager Version
|
||||
run: |
|
||||
plutil -replace UIRequiresFullScreen -bool false Limelight/Limelight-Info.plist
|
||||
xcodebuild -verbose -scheme Moonlight-ZWM -sdk iphoneos -configuration AppStoreDistribution archive -archivePath $PWD/build/MoonlightStageManager.xcarchive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO"
|
||||
mkdir -p ./build/IPA_StageManager && mkdir -p ./build/IPA_StageManager/Payload
|
||||
cp -r ./build/MoonlightStageManager.xcarchive/Products/Applications/Moonlight-ZWM.app ./build/IPA_StageManager/Payload/Moonlight-ZWM.app
|
||||
cd ./build/IPA_StageManager && zip -r ../../build/Moonlight-ZWM_StageManager.ipa ./Payload/* && cd ../..
|
||||
|
||||
- name: Upload StageManager IPA to Artifacts
|
||||
- name: Upload VoidLink IPA to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Moonlight-ZWM_StageManager.ipa
|
||||
path: build/Moonlight-ZWM_StageManager.ipa
|
||||
name: VoidLink.ipa
|
||||
path: build/VoidLink.ipa
|
||||
@@ -1,6 +1,9 @@
|
||||
**/xcuserdata/
|
||||
**/Moonlight.xcscmblueprint
|
||||
**/xcschemes/*.xcscheme
|
||||
.DS_Store
|
||||
Build
|
||||
DerivedData
|
||||
*.xccheckout
|
||||
.github
|
||||
README.md
|
||||
@@ -1,6 +1,9 @@
|
||||
[submodule "moonlight-common/moonlight-common-c"]
|
||||
path = moonlight-common/moonlight-common-c
|
||||
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||
url=https://github.com/TrueZhuangJia/voidlink-c.git
|
||||
[submodule "X1Kit"]
|
||||
path = X1Kit
|
||||
url = https://github.com/cgutman/X1Kit.git
|
||||
[submodule "third_party/imgui"]
|
||||
path = third_party/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// dear imgui: Renderer Backend for Metal
|
||||
// This needs to be used along with a Platform Backend (e.g. OSX)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// Learn about Dear ImGui:
|
||||
// - FAQ https://dearimgui.com/faq
|
||||
// - Getting Started https://dearimgui.com/getting-started
|
||||
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
|
||||
// - Introduction, links and more at the top of imgui.cpp
|
||||
|
||||
#pragma once
|
||||
#include "imgui.h" // IMGUI_IMPL_API
|
||||
#ifndef IMGUI_DISABLE
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ObjC API
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
@class MTLRenderPassDescriptor;
|
||||
@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder;
|
||||
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id<MTLDevice> device);
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor);
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData,
|
||||
id<MTLCommandBuffer> commandBuffer,
|
||||
id<MTLRenderCommandEncoder> commandEncoder);
|
||||
|
||||
// Called by Init/NewFrame/Shutdown
|
||||
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device);
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
|
||||
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// C++ API
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file
|
||||
// More info about using Metal from C++: https://developer.apple.com/metal/cpp/
|
||||
|
||||
#ifdef IMGUI_IMPL_METAL_CPP
|
||||
#include <Metal/Metal.hpp>
|
||||
#ifndef __OBJC__
|
||||
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device);
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor);
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
|
||||
MTL::CommandBuffer* commandBuffer,
|
||||
MTL::RenderCommandEncoder* commandEncoder);
|
||||
|
||||
// Called by Init/NewFrame/Shutdown
|
||||
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device);
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
|
||||
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
@@ -0,0 +1,657 @@
|
||||
// dear imgui: Renderer Backend for Metal
|
||||
// This needs to be used along with a Platform Backend (e.g. OSX)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// Learn about Dear ImGui:
|
||||
// - FAQ https://dearimgui.com/faq
|
||||
// - Getting Started https://dearimgui.com/getting-started
|
||||
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
|
||||
// - Introduction, links and more at the top of imgui.cpp
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture().
|
||||
// 2025-02-03: Metal: Crash fix. (#8367)
|
||||
// 2024-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419).
|
||||
// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'.
|
||||
// 2022-07-05: Metal: Add dispatch synchronization.
|
||||
// 2022-06-30: Metal: Use __bridge for ARC based systems.
|
||||
// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler.
|
||||
// 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
|
||||
// 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code).
|
||||
// 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file.
|
||||
// 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464)
|
||||
// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
|
||||
// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer.
|
||||
// 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst.
|
||||
// 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||
// 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||
// 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
|
||||
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
|
||||
// 2018-07-05: Metal: Added new Metal backend implementation.
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DISABLE
|
||||
#include "imgui_impl_metal.h"
|
||||
#import <time.h>
|
||||
#import <Metal/Metal.h>
|
||||
|
||||
#pragma mark - Support classes
|
||||
|
||||
// A wrapper around a MTLBuffer object that knows the last time it was reused
|
||||
@interface MetalBuffer : NSObject
|
||||
@property (nonatomic, strong) id<MTLBuffer> buffer;
|
||||
@property (nonatomic, assign) double lastReuseTime;
|
||||
- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer;
|
||||
@end
|
||||
|
||||
// An object that encapsulates the data necessary to uniquely identify a
|
||||
// render pipeline state. These are used as cache keys.
|
||||
@interface FramebufferDescriptor : NSObject<NSCopying>
|
||||
@property (nonatomic, assign) unsigned long sampleCount;
|
||||
@property (nonatomic, assign) MTLPixelFormat colorPixelFormat;
|
||||
@property (nonatomic, assign) MTLPixelFormat depthPixelFormat;
|
||||
@property (nonatomic, assign) MTLPixelFormat stencilPixelFormat;
|
||||
- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor;
|
||||
@end
|
||||
|
||||
@interface MetalTexture : NSObject
|
||||
@property (nonatomic, strong) id<MTLTexture> metalTexture;
|
||||
- (instancetype)initWithTexture:(id<MTLTexture>)metalTexture;
|
||||
@end
|
||||
|
||||
// A singleton that stores long-lived objects that are needed by the Metal
|
||||
// renderer backend. Stores the render pipeline state cache and the default
|
||||
// font texture, and manages the reusable buffer cache.
|
||||
@interface MetalContext : NSObject
|
||||
@property (nonatomic, strong) id<MTLDevice> device;
|
||||
@property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState;
|
||||
@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient
|
||||
@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
|
||||
@property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache;
|
||||
@property (nonatomic, assign) double lastBufferCachePurge;
|
||||
- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device;
|
||||
- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device;
|
||||
@end
|
||||
|
||||
struct ImGui_ImplMetal_Data
|
||||
{
|
||||
MetalContext* SharedMetalContext;
|
||||
|
||||
ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; }
|
||||
static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); }
|
||||
|
||||
// XXX Work around missing clock symbols on iOS. The NSDate version is the method this function used previously.
|
||||
#if defined(CLOCK_UPTIME_RAW)
|
||||
static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); }
|
||||
#else
|
||||
static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)[NSDate date].timeIntervalSince1970; }
|
||||
#endif
|
||||
|
||||
#ifdef IMGUI_IMPL_METAL_CPP
|
||||
|
||||
#pragma mark - Dear ImGui Metal C++ Backend API
|
||||
|
||||
bool ImGui_ImplMetal_Init(MTL::Device* device)
|
||||
{
|
||||
return ImGui_ImplMetal_Init((__bridge id<MTLDevice>)(device));
|
||||
}
|
||||
|
||||
void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor)
|
||||
{
|
||||
ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor));
|
||||
}
|
||||
|
||||
void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
|
||||
MTL::CommandBuffer* commandBuffer,
|
||||
MTL::RenderCommandEncoder* commandEncoder)
|
||||
{
|
||||
ImGui_ImplMetal_RenderDrawData(draw_data,
|
||||
(__bridge id<MTLCommandBuffer>)(commandBuffer),
|
||||
(__bridge id<MTLRenderCommandEncoder>)(commandEncoder));
|
||||
|
||||
}
|
||||
|
||||
bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device)
|
||||
{
|
||||
return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device));
|
||||
}
|
||||
|
||||
#endif // #ifdef IMGUI_IMPL_METAL_CPP
|
||||
|
||||
#pragma mark - Dear ImGui Metal Backend API
|
||||
|
||||
bool ImGui_ImplMetal_Init(id<MTLDevice> device)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_metal";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
|
||||
|
||||
bd->SharedMetalContext = [[MetalContext alloc] init];
|
||||
bd->SharedMetalContext.device = device;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplMetal_Shutdown()
|
||||
{
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
IM_UNUSED(bd);
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGui_ImplMetal_DestroyDeviceObjects();
|
||||
ImGui_ImplMetal_DestroyBackendData();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
|
||||
}
|
||||
|
||||
void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor)
|
||||
{
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?");
|
||||
#ifdef IMGUI_IMPL_METAL_CPP
|
||||
bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease];
|
||||
#else
|
||||
bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
|
||||
#endif
|
||||
if (bd->SharedMetalContext.depthStencilState == nil)
|
||||
ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device);
|
||||
}
|
||||
|
||||
static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer,
|
||||
id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState,
|
||||
MetalBuffer* vertexBuffer, size_t vertexBufferOffset)
|
||||
{
|
||||
IM_UNUSED(commandBuffer);
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
[commandEncoder setCullMode:MTLCullModeNone];
|
||||
[commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState];
|
||||
|
||||
// Setup viewport, orthographic projection matrix
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to
|
||||
// draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
|
||||
MTLViewport viewport =
|
||||
{
|
||||
.originX = 0.0,
|
||||
.originY = 0.0,
|
||||
.width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x),
|
||||
.height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y),
|
||||
.znear = 0.0,
|
||||
.zfar = 1.0
|
||||
};
|
||||
[commandEncoder setViewport:viewport];
|
||||
|
||||
float L = draw_data->DisplayPos.x;
|
||||
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
|
||||
float T = draw_data->DisplayPos.y;
|
||||
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
|
||||
float N = (float)viewport.znear;
|
||||
float F = (float)viewport.zfar;
|
||||
const float ortho_projection[4][4] =
|
||||
{
|
||||
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
|
||||
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
|
||||
{ 0.0f, 0.0f, 1/(F-N), 0.0f },
|
||||
{ (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f },
|
||||
};
|
||||
[commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1];
|
||||
|
||||
[commandEncoder setRenderPipelineState:renderPipelineState];
|
||||
|
||||
[commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0];
|
||||
[commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0];
|
||||
}
|
||||
|
||||
// Metal Render function.
|
||||
void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
|
||||
{
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
MetalContext* ctx = bd->SharedMetalContext;
|
||||
|
||||
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
|
||||
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
|
||||
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
|
||||
if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0)
|
||||
return;
|
||||
|
||||
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
|
||||
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
|
||||
if (draw_data->Textures != nullptr)
|
||||
for (ImTextureData* tex : *draw_data->Textures)
|
||||
if (tex->Status != ImTextureStatus_OK)
|
||||
ImGui_ImplMetal_UpdateTexture(tex);
|
||||
|
||||
// Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame
|
||||
// The hit rate for this cache should be very near 100%.
|
||||
id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor];
|
||||
if (renderPipelineState == nil)
|
||||
{
|
||||
// No luck; make a new render pipeline state
|
||||
renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device];
|
||||
|
||||
// Cache render pipeline state for later reuse
|
||||
ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState;
|
||||
}
|
||||
|
||||
size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert);
|
||||
size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx);
|
||||
MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
|
||||
MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
|
||||
|
||||
ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0);
|
||||
|
||||
// Will project scissor/clipping rectangles into framebuffer space
|
||||
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
|
||||
ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
|
||||
|
||||
// Render command lists
|
||||
size_t vertexBufferOffset = 0;
|
||||
size_t indexBufferOffset = 0;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++)
|
||||
{
|
||||
const ImDrawList* draw_list = draw_data->CmdLists[n];
|
||||
|
||||
memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
|
||||
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback)
|
||||
{
|
||||
// User callback, registered via ImDrawList::AddCallback()
|
||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
|
||||
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
|
||||
ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset);
|
||||
else
|
||||
pcmd->UserCallback(draw_list, pcmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Project scissor/clipping rectangles into framebuffer space
|
||||
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
|
||||
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
|
||||
|
||||
// Clamp to viewport as setScissorRect() won't accept values that are off bounds
|
||||
if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
|
||||
if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
|
||||
if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; }
|
||||
if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; }
|
||||
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
||||
continue;
|
||||
if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this
|
||||
continue;
|
||||
|
||||
// Apply scissor/clipping rectangle
|
||||
MTLScissorRect scissorRect =
|
||||
{
|
||||
.x = NSUInteger(clip_min.x),
|
||||
.y = NSUInteger(clip_min.y),
|
||||
.width = NSUInteger(clip_max.x - clip_min.x),
|
||||
.height = NSUInteger(clip_max.y - clip_min.y)
|
||||
};
|
||||
[commandEncoder setScissorRect:scissorRect];
|
||||
|
||||
// Bind texture, Draw
|
||||
if (ImTextureID tex_id = pcmd->GetTexID())
|
||||
[commandEncoder setFragmentTexture:(__bridge id<MTLTexture>)(void*)(intptr_t)(tex_id) atIndex:0];
|
||||
|
||||
[commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0];
|
||||
[commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
||||
indexCount:pcmd->ElemCount
|
||||
indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
|
||||
indexBuffer:indexBuffer.buffer
|
||||
indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)];
|
||||
}
|
||||
}
|
||||
|
||||
vertexBufferOffset += (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert);
|
||||
indexBufferOffset += (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx);
|
||||
}
|
||||
|
||||
MetalContext* sharedMetalContext = bd->SharedMetalContext;
|
||||
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@synchronized(sharedMetalContext.bufferCache)
|
||||
{
|
||||
[sharedMetalContext.bufferCache addObject:vertexBuffer];
|
||||
[sharedMetalContext.bufferCache addObject:indexBuffer];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex)
|
||||
{
|
||||
MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData);
|
||||
if (backend_tex == nullptr)
|
||||
return;
|
||||
IM_ASSERT(backend_tex.metalTexture == (__bridge id<MTLTexture>)(void*)(intptr_t)tex->TexID);
|
||||
backend_tex.metalTexture = nil;
|
||||
|
||||
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
|
||||
tex->SetTexID(ImTextureID_Invalid);
|
||||
tex->SetStatus(ImTextureStatus_Destroyed);
|
||||
tex->BackendUserData = nullptr;
|
||||
}
|
||||
|
||||
void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex)
|
||||
{
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
if (tex->Status == ImTextureStatus_WantCreate)
|
||||
{
|
||||
// Create and upload new texture to graphics system
|
||||
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
|
||||
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
|
||||
|
||||
// We are retrieving and uploading the font atlas as a 4-channels RGBA texture here.
|
||||
// In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth.
|
||||
// However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures.
|
||||
// You can make that change in your implementation.
|
||||
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
|
||||
width:(NSUInteger)tex->Width
|
||||
height:(NSUInteger)tex->Height
|
||||
mipmapped:NO];
|
||||
textureDescriptor.usage = MTLTextureUsageShaderRead;
|
||||
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
#else
|
||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||
#endif
|
||||
id <MTLTexture> texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor];
|
||||
[texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4];
|
||||
MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture];
|
||||
|
||||
// Store identifiers
|
||||
tex->SetTexID((ImTextureID)(intptr_t)texture);
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
tex->BackendUserData = (__bridge_retained void*)(backend_tex);
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantUpdates)
|
||||
{
|
||||
// Update selected blocks. We only ever write to textures regions which have never been used before!
|
||||
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
|
||||
MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData);
|
||||
for (ImTextureRect& r : tex->Updates)
|
||||
{
|
||||
[backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h)
|
||||
mipmapLevel:0
|
||||
withBytes:tex->GetPixelsAt(r.x, r.y)
|
||||
bytesPerRow:(NSUInteger)tex->Width * 4];
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
|
||||
{
|
||||
ImGui_ImplMetal_DestroyTexture(tex);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
|
||||
{
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
|
||||
depthStencilDescriptor.depthWriteEnabled = NO;
|
||||
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
|
||||
bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
|
||||
#ifdef IMGUI_IMPL_METAL_CPP
|
||||
[depthStencilDescriptor release];
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplMetal_DestroyDeviceObjects()
|
||||
{
|
||||
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
|
||||
|
||||
// Destroy all textures
|
||||
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
||||
if (tex->RefCount == 1)
|
||||
ImGui_ImplMetal_DestroyTexture(tex);
|
||||
|
||||
[bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - MetalBuffer implementation
|
||||
|
||||
@implementation MetalBuffer
|
||||
- (instancetype)initWithBuffer:(id<MTLBuffer>)buffer
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
_buffer = buffer;
|
||||
_lastReuseTime = GetMachAbsoluteTimeInSeconds();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
#pragma mark - FramebufferDescriptor implementation
|
||||
|
||||
@implementation FramebufferDescriptor
|
||||
- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
_sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount;
|
||||
_colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat;
|
||||
_depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat;
|
||||
_stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nonnull id)copyWithZone:(nullable NSZone*)zone
|
||||
{
|
||||
FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init];
|
||||
copy.sampleCount = self.sampleCount;
|
||||
copy.colorPixelFormat = self.colorPixelFormat;
|
||||
copy.depthPixelFormat = self.depthPixelFormat;
|
||||
copy.stencilPixelFormat = self.stencilPixelFormat;
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
NSUInteger sc = _sampleCount & 0x3;
|
||||
NSUInteger cf = _colorPixelFormat & 0x3FF;
|
||||
NSUInteger df = _depthPixelFormat & 0x3FF;
|
||||
NSUInteger sf = _stencilPixelFormat & 0x3FF;
|
||||
NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc;
|
||||
return hash;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
FramebufferDescriptor* other = object;
|
||||
if (![other isKindOfClass:[FramebufferDescriptor class]])
|
||||
return NO;
|
||||
return other.sampleCount == self.sampleCount &&
|
||||
other.colorPixelFormat == self.colorPixelFormat &&
|
||||
other.depthPixelFormat == self.depthPixelFormat &&
|
||||
other.stencilPixelFormat == self.stencilPixelFormat;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - MetalTexture implementation
|
||||
|
||||
@implementation MetalTexture
|
||||
- (instancetype)initWithTexture:(id<MTLTexture>)metalTexture
|
||||
{
|
||||
if ((self = [super init]))
|
||||
self.metalTexture = metalTexture;
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - MetalContext implementation
|
||||
|
||||
@implementation MetalContext
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
self.renderPipelineStateCache = [NSMutableDictionary dictionary];
|
||||
self.bufferCache = [NSMutableArray array];
|
||||
_lastBufferCachePurge = GetMachAbsoluteTimeInSeconds();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device
|
||||
{
|
||||
uint64_t now = GetMachAbsoluteTimeInSeconds();
|
||||
|
||||
@synchronized(self.bufferCache)
|
||||
{
|
||||
// Purge old buffers that haven't been useful for a while
|
||||
if (now - self.lastBufferCachePurge > 1.0)
|
||||
{
|
||||
NSMutableArray* survivors = [NSMutableArray array];
|
||||
for (MetalBuffer* candidate in self.bufferCache)
|
||||
if (candidate.lastReuseTime > self.lastBufferCachePurge)
|
||||
[survivors addObject:candidate];
|
||||
self.bufferCache = [survivors mutableCopy];
|
||||
self.lastBufferCachePurge = now;
|
||||
}
|
||||
|
||||
// See if we have a buffer we can reuse
|
||||
MetalBuffer* bestCandidate = nil;
|
||||
for (MetalBuffer* candidate in self.bufferCache)
|
||||
if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime))
|
||||
bestCandidate = candidate;
|
||||
|
||||
if (bestCandidate != nil)
|
||||
{
|
||||
[self.bufferCache removeObject:bestCandidate];
|
||||
bestCandidate.lastReuseTime = now;
|
||||
return bestCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
// No luck; make a new buffer
|
||||
id<MTLBuffer> backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared];
|
||||
return [[MetalBuffer alloc] initWithBuffer:backing];
|
||||
}
|
||||
|
||||
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
|
||||
- (id<MTLRenderPipelineState>)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id<MTLDevice>)device
|
||||
{
|
||||
NSError* error = nil;
|
||||
|
||||
NSString* shaderSource = @""
|
||||
"#include <metal_stdlib>\n"
|
||||
"using namespace metal;\n"
|
||||
"\n"
|
||||
"struct Uniforms {\n"
|
||||
" float4x4 projectionMatrix;\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"struct VertexIn {\n"
|
||||
" float2 position [[attribute(0)]];\n"
|
||||
" float2 texCoords [[attribute(1)]];\n"
|
||||
" uchar4 color [[attribute(2)]];\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"struct VertexOut {\n"
|
||||
" float4 position [[position]];\n"
|
||||
" float2 texCoords;\n"
|
||||
" float4 color;\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n"
|
||||
" constant Uniforms &uniforms [[buffer(1)]]) {\n"
|
||||
" VertexOut out;\n"
|
||||
" out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n"
|
||||
" out.texCoords = in.texCoords;\n"
|
||||
" out.color = float4(in.color) / float4(255.0);\n"
|
||||
" return out;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"fragment half4 fragment_main(VertexOut in [[stage_in]],\n"
|
||||
" texture2d<half, access::sample> texture [[texture(0)]]) {\n"
|
||||
" constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
|
||||
" half4 texColor = texture.sample(linearSampler, in.texCoords);\n"
|
||||
" return half4(in.color) * texColor;\n"
|
||||
"}\n";
|
||||
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error];
|
||||
if (library == nil)
|
||||
{
|
||||
NSLog(@"Error: failed to create Metal library: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
|
||||
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
|
||||
|
||||
if (vertexFunction == nil || fragmentFunction == nil)
|
||||
{
|
||||
NSLog(@"Error: failed to find Metal shader functions in library: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
|
||||
vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos);
|
||||
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position
|
||||
vertexDescriptor.attributes[0].bufferIndex = 0;
|
||||
vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv);
|
||||
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords
|
||||
vertexDescriptor.attributes[1].bufferIndex = 0;
|
||||
vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col);
|
||||
vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color
|
||||
vertexDescriptor.attributes[2].bufferIndex = 0;
|
||||
vertexDescriptor.layouts[0].stepRate = 1;
|
||||
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
|
||||
vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert);
|
||||
|
||||
MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
pipelineDescriptor.vertexFunction = vertexFunction;
|
||||
pipelineDescriptor.fragmentFunction = fragmentFunction;
|
||||
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
|
||||
pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount;
|
||||
pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat;
|
||||
pipelineDescriptor.colorAttachments[0].blendingEnabled = YES;
|
||||
pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
|
||||
pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
|
||||
pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||
pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
|
||||
pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
|
||||
pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
||||
pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat;
|
||||
pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat;
|
||||
|
||||
id<MTLRenderPipelineState> renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
|
||||
if (error != nil)
|
||||
NSLog(@"Error: failed to create Metal pipeline state: %@", error);
|
||||
|
||||
return renderPipelineState;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
|
||||
<device id="ipad11_0rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -12,7 +12,7 @@
|
||||
<objects>
|
||||
<viewController id="wb7-af-jn8" customClass="MainFrameViewController" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="black" dataMode="prototypes" id="TZj-Lc-M9d" customClass="AppCollectionView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1120"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1136"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.3333333432674408" green="0.3333333432674408" blue="0.3333333432674408" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="f7l-kG-hJc">
|
||||
@@ -601,6 +601,25 @@ ICAgICAgICAgICAgICA
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Player 1 Motion Mode" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0bl-Hh-8ot">
|
||||
<rect key="frame" x="20" y="2154" width="164" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="WQX-ct-rnt" userLabel="Motion Mode Selector">
|
||||
<rect key="frame" x="16" y="2183" width="459" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Auto"/>
|
||||
<segment title="Device Motion"/>
|
||||
<segment title="Controller Motion"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="viewFlipsideBackgroundColor"/>
|
||||
</view>
|
||||
@@ -622,6 +641,7 @@ ICAgICAgICAgICAgICA
|
||||
<outlet property="largerStickLR1Selector" destination="sIU-PH-H7V" id="YWg-40-eKd"/>
|
||||
<outlet property="liftStreamViewForKeyboardSelector" destination="hm3-EV-h6d" id="fHG-J1-eQL"/>
|
||||
<outlet property="localMousePointerModeSelector" destination="4FU-iP-c6T" id="4Gz-ao-xH2"/>
|
||||
<outlet property="motionModeSelector" destination="WQX-ct-rnt" id="1N8-yA-54x"/>
|
||||
<outlet property="mousePointerVelocityFactorSlider" destination="Ei2-FJ-9oC" id="1T3-QA-Jua"/>
|
||||
<outlet property="mousePointerVelocityFactorUILabel" destination="oPM-G6-u5O" id="Eco-5u-PRR"/>
|
||||
<outlet property="multiControllerSelector" destination="KlC-fG-wEi" id="giM-F7-So5"/>
|
||||
@@ -664,7 +684,7 @@ ICAgICAgICAgICAgICA
|
||||
<viewControllerLayoutGuide type="bottom" id="YRO-Lg-RCg"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="hGs-BR-t4b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
@@ -704,7 +724,7 @@ ICAgICAgICAgICAgICA
|
||||
<viewControllerLayoutGuide type="bottom" id="vu8-3s-HlY"/>
|
||||
</layoutGuides>
|
||||
<view key="view" multipleTouchEnabled="YES" contentMode="scaleToFill" id="SMn-1j-R3I">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" red="0.3333357871" green="0.33332890269999998" blue="0.33333355190000002" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
</view>
|
||||
@@ -726,7 +746,7 @@ ICAgICAgICAgICAgICA
|
||||
<viewControllerLayoutGuide type="bottom" id="YZj-An-sqS"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="le1-tU-NEp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="N0D-dj-EuF">
|
||||
@@ -753,7 +773,7 @@ ICAgICAgICAgICAgICA
|
||||
<viewControllerLayoutGuide type="bottom" id="eaS-aj-VtA"/>
|
||||
</layoutGuides>
|
||||
<view key="view" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="Acw-S4-f8a">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view alpha="0.75" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fZU-G0-fgM" userLabel="Toolbar Root View" customClass="ToolBarContainerView">
|
||||
@@ -971,11 +991,11 @@ ICAgICAgICAgICAgICA
|
||||
<viewControllerLayoutGuide type="bottom" id="8ja-wQ-jbJ"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="PlJ-LJ-d1p">
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1194"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView opaque="NO" clearsContextBeforeDrawing="NO" alpha="0.5" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="BA3-hd-bDp">
|
||||
<rect key="frame" x="0.0" y="44" width="834" height="1150"/>
|
||||
<rect key="frame" x="0.0" y="44" width="834" height="1166"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableView>
|
||||
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="62V-DN-FUk">
|
||||
@@ -1036,13 +1056,13 @@ ICAgICAgICAgICAgICA
|
||||
<image name="SquaresArrowUp" width="40" height="40"/>
|
||||
<image name="Trash" width="40" height="40"/>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="tintColor">
|
||||
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="viewFlipsideBackgroundColor">
|
||||
<color red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="DL0-L5-LOv">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="DL0-L5-LOv">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -570,7 +570,7 @@ ICAgICAgICAgICAgIA
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Local Mouse Pointer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VGn-Oc-mKc">
|
||||
<rect key="frame" x="22" y="2007" width="165" height="21"/>
|
||||
<rect key="frame" x="20" y="2008" width="165" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -604,6 +604,24 @@ ICAgICAgICAgICAgIA
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Player 1 Motion Mode" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c8T-vY-LOX">
|
||||
<rect key="frame" x="22" y="2075" width="174" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.95132106540000005" green="0.97490358349999995" blue="0.99987185000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="6Lj-ss-Tj8" userLabel="Motion Mode Selector">
|
||||
<rect key="frame" x="16" y="2104" width="450" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="Auto"/>
|
||||
<segment title="Device Motion"/>
|
||||
<segment title="Controller Motion"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FqF-bM-afQ">
|
||||
<rect key="frame" x="17" y="14" width="219" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
@@ -617,6 +635,23 @@ ICAgICAgICAgICAgIA
|
||||
<action selector="backToStreamingButtonTapped:" destination="rYd-e6-cQU" eventType="touchUpInside" id="9P0-Yw-T4W"/>
|
||||
</connections>
|
||||
</button>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="dcP-yB-6Un" userLabel="PiP Selector">
|
||||
<rect key="frame" x="18" y="2102" width="459" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="No"/>
|
||||
<segment title="Yes"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enable PiP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ELc-8r-KSB">
|
||||
<rect key="frame" x="20" y="2075" width="86" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
@@ -646,6 +681,7 @@ ICAgICAgICAgICAgIA
|
||||
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
|
||||
<outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/>
|
||||
<outlet property="oscTapExlusionAreaSizeSlider" destination="TSt-Hj-uTR" id="h9l-ap-AVd"/>
|
||||
<outlet property="pipSelector" destination="dcP-yB-6Un" id="rrB-dR-2Bq"/>
|
||||
<outlet property="pointerVelocityModeDividerSlider" destination="xet-C9-l5y" id="GnQ-xU-e0N"/>
|
||||
<outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/>
|
||||
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/>
|
||||
@@ -665,6 +701,7 @@ ICAgICAgICAgICAgIA
|
||||
<outlet property="touchPointerVelocityFactorUILabel" destination="c6f-bJ-Vji" id="uVM-o6-JPk"/>
|
||||
<outlet property="unlockDisplayOrientationSelector" destination="R9O-Tw-UD4" id="0ak-pc-eva"/>
|
||||
<outlet property="yuv444Selector" destination="vwT-tg-RfT" id="69G-bN-kJF"/>
|
||||
<outlet property="motionModeSelector" destination="6Lj-ss-Tj8" id="RLW-3I-hvl"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="RWc-Km-KG5" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// ComputerScrollView.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 9/30/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface ComputerScrollView : UIScrollView
|
||||
|
||||
@end
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// ComputerScrollView.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 9/30/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ComputerScrollView.h"
|
||||
|
||||
@implementation ComputerScrollView
|
||||
|
||||
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
|
||||
if ([view isKindOfClass:[UIButton class]]) {
|
||||
return YES;
|
||||
}
|
||||
return [super touchesShouldCancelInContentView:view];
|
||||
}
|
||||
|
||||
/*
|
||||
// Only override drawRect: if you perform custom drawing.
|
||||
// An empty implementation adversely affects performance during animation.
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
// Drawing code
|
||||
}
|
||||
*/
|
||||
|
||||
@end
|
||||
@@ -1,64 +0,0 @@
|
||||
//
|
||||
// DataManager.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/28/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "TemporaryHost.h"
|
||||
#import "TemporaryApp.h"
|
||||
#import "TemporarySettings.h"
|
||||
|
||||
@interface DataManager : NSObject
|
||||
|
||||
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
|
||||
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
|
||||
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
|
||||
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
|
||||
oscTapExlusionAreaSize:(CGFloat)oscTapExlusionAreaSize
|
||||
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
|
||||
largerStickLR1:(BOOL)largerStickLR1
|
||||
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
enableYUV444:(BOOL)enableYUV444
|
||||
useFramePacing:(BOOL)useFramePacing
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
// absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
touchMode:(NSInteger)touchMode
|
||||
statsOverlayLevel:(NSInteger)statsOverlayLevel
|
||||
statsOverlayEnabled:(BOOL)statsOverlayEnabled
|
||||
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
|
||||
resolutionSelected:(NSInteger)resolutionSelected
|
||||
externalDisplayMode:(NSInteger)externalDisplayMode
|
||||
localMousePointerMode:(NSInteger)localMousePointerMode;
|
||||
|
||||
- (NSArray*) getHosts;
|
||||
- (void) updateHost:(TemporaryHost*)host;
|
||||
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
|
||||
- (void) removeHost:(TemporaryHost*)host;
|
||||
- (void) removeApp:(TemporaryApp*)app;
|
||||
- (Settings*) retrieveSettings;
|
||||
- (void) saveData;
|
||||
- (TemporarySettings*) getSettings;
|
||||
|
||||
- (void) updateUniqueId:(NSString*)uniqueId;
|
||||
- (NSString*) getUniqueId;
|
||||
|
||||
@end
|
||||
@@ -1,65 +0,0 @@
|
||||
//
|
||||
// TemporarySettings.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/1/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Settings+CoreDataClass.h"
|
||||
#define RELATIVE_TOUCH 0
|
||||
#define REGULAR_NATIVE_TOUCH 1
|
||||
#define PURE_NATIVE_TOUCH 2
|
||||
#define ABSOLUTE_TOUCH 3
|
||||
#define OSC_TOOL_FINGERS 4
|
||||
|
||||
|
||||
@interface TemporarySettings : NSObject
|
||||
|
||||
@property (nonatomic, retain) Settings * parent;
|
||||
|
||||
@property (nonatomic, retain) NSNumber * bitrate;
|
||||
@property (nonatomic, retain) NSNumber * framerate;
|
||||
@property (nonatomic, retain) NSNumber * height;
|
||||
@property (nonatomic, retain) NSNumber * width;
|
||||
@property (nonatomic, retain) NSNumber * audioConfig;
|
||||
@property (nonatomic, retain) NSNumber * onscreenControls;
|
||||
@property (nonatomic, retain) NSNumber * keyboardToggleFingers;
|
||||
@property (nonatomic, retain) NSNumber * oscLayoutToolFingers;
|
||||
@property (nonatomic, retain) NSNumber * slideToSettingsScreenEdge;
|
||||
@property (nonatomic, retain) NSNumber * slideToSettingsDistance;
|
||||
@property (nonatomic, retain) NSNumber * oscTapExlusionAreaSize;
|
||||
@property (nonatomic, retain) NSNumber * touchPointerVelocityFactor;
|
||||
@property (nonatomic, retain) NSNumber * mousePointerVelocityFactor;
|
||||
@property (nonatomic, retain) NSNumber * pointerVelocityModeDivider;
|
||||
@property (nonatomic, retain) NSString * uniqueId;
|
||||
@property (nonatomic, retain) NSNumber * resolutionSelected;
|
||||
@property (nonatomic, retain) NSNumber * externalDisplayMode;
|
||||
@property (nonatomic, retain) NSNumber * localMousePointerMode;
|
||||
@property (nonatomic) enum {
|
||||
CODEC_PREF_AUTO,
|
||||
CODEC_PREF_H264,
|
||||
CODEC_PREF_HEVC,
|
||||
CODEC_PREF_AV1,
|
||||
} preferredCodec;
|
||||
@property (nonatomic) BOOL enableYUV444;
|
||||
@property (nonatomic) BOOL reverseMouseWheelDirection;
|
||||
@property (nonatomic) BOOL largerStickLR1;
|
||||
@property (nonatomic) BOOL useFramePacing;
|
||||
@property (nonatomic) BOOL multiController;
|
||||
@property (nonatomic) BOOL swapABXYButtons;
|
||||
@property (nonatomic) BOOL playAudioOnPC;
|
||||
@property (nonatomic) BOOL optimizeGames;
|
||||
@property (nonatomic) BOOL enableHdr;
|
||||
@property (nonatomic) BOOL btMouseSupport;
|
||||
// @property (nonatomic) BOOL absoluteTouchMode;
|
||||
@property (nonatomic, retain) NSNumber * touchMode;
|
||||
@property (nonatomic, retain) NSNumber * statsOverlayLevel;
|
||||
@property (nonatomic) BOOL statsOverlayEnabled;
|
||||
@property (nonatomic) BOOL liftStreamViewForKeyboard;
|
||||
@property (nonatomic) BOOL showKeyboardToolbar;
|
||||
@property (nonatomic) BOOL unlockDisplayOrientation;
|
||||
|
||||
- (id) initFromSettings:(Settings*)settings;
|
||||
|
||||
@end
|
||||
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,149 +0,0 @@
|
||||
//
|
||||
// AbsoluteTouchHandler.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 11/1/20.
|
||||
// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AbsoluteTouchHandler.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
// How long the fingers must be stationary to start a right click
|
||||
#define LONG_PRESS_ACTIVATION_DELAY 0.650f
|
||||
|
||||
// How far the finger can move before it cancels a right click
|
||||
#define LONG_PRESS_ACTIVATION_DELTA 0.01f
|
||||
|
||||
// How long the double tap deadzone stays in effect between touch up and touch down
|
||||
#define DOUBLE_TAP_DEAD_ZONE_DELAY 0.250f
|
||||
|
||||
// How far the finger can move before it can override the double tap deadzone
|
||||
#define DOUBLE_TAP_DEAD_ZONE_DELTA 0.025f
|
||||
|
||||
@implementation AbsoluteTouchHandler {
|
||||
StreamView* streamView;
|
||||
|
||||
NSTimer* longPressTimer;
|
||||
UITouch* lastTouchDown;
|
||||
CGPoint lastTouchDownLocation;
|
||||
UITouch* lastTouchUp;
|
||||
CGPoint lastTouchUpLocation;
|
||||
|
||||
// upper screen edge check
|
||||
bool touchPointSpawnedAtUpperScreenEdge;
|
||||
CGFloat slideGestureVerticalThreshold;
|
||||
CGFloat screenWidthWithThreshold;
|
||||
CGFloat EDGE_TOLERANCE;
|
||||
|
||||
}
|
||||
|
||||
- (id)initWithView:(StreamView*)view {
|
||||
self = [self init];
|
||||
self->streamView = view;
|
||||
|
||||
// upper screen check
|
||||
EDGE_TOLERANCE = 15.0;
|
||||
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
|
||||
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
|
||||
self->touchPointSpawnedAtUpperScreenEdge = false;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onLongPressStart:(NSTimer*)timer {
|
||||
// Raise the left click and start a right click
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
|
||||
CGPoint initialPoint = [[touches anyObject] locationInView:streamView];
|
||||
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
|
||||
self->touchPointSpawnedAtUpperScreenEdge = true;
|
||||
return; // we're done here. this touch event will not be sent to the remote PC.
|
||||
}
|
||||
|
||||
touchPointSpawnedAtUpperScreenEdge = false; // reset this flag immediately if we get a touch event passing the check above, this fixes irresponsive touch after closing the command tool menu.
|
||||
|
||||
// Ignore touch down events with more than one finger
|
||||
if ([[event allTouches] count] > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
UITouch* touch = [touches anyObject];
|
||||
CGPoint touchLocation = [touch locationInView:streamView];
|
||||
|
||||
// Don't reposition for finger down events within the deadzone. This makes double-clicking easier.
|
||||
if (touch.timestamp - lastTouchUp.timestamp > DOUBLE_TAP_DEAD_ZONE_DELAY ||
|
||||
sqrt(pow((touchLocation.x / streamView.bounds.size.width) - (lastTouchUpLocation.x / streamView.bounds.size.width), 2) +
|
||||
pow((touchLocation.y / streamView.bounds.size.height) - (lastTouchUpLocation.y / streamView.bounds.size.height), 2)) > DOUBLE_TAP_DEAD_ZONE_DELTA) {
|
||||
[streamView updateCursorLocation:touchLocation isMouse:NO];
|
||||
}
|
||||
|
||||
// Press the left button down
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
|
||||
|
||||
// Start the long press timer
|
||||
longPressTimer = [NSTimer scheduledTimerWithTimeInterval:LONG_PRESS_ACTIVATION_DELAY
|
||||
target:self
|
||||
selector:@selector(onLongPressStart:)
|
||||
userInfo:nil
|
||||
repeats:NO];
|
||||
|
||||
lastTouchDown = touch;
|
||||
lastTouchDownLocation = touchLocation;
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
|
||||
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC.
|
||||
|
||||
// Ignore touch move events with more than one finger
|
||||
if ([[event allTouches] count] > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
UITouch* touch = [touches anyObject];
|
||||
CGPoint touchLocation = [touch locationInView:streamView];
|
||||
|
||||
if (sqrt(pow((touchLocation.x / streamView.bounds.size.width) - (lastTouchDownLocation.x / streamView.bounds.size.width), 2) +
|
||||
pow((touchLocation.y / streamView.bounds.size.height) - (lastTouchDownLocation.y / streamView.bounds.size.height), 2)) > LONG_PRESS_ACTIVATION_DELTA) {
|
||||
// Moved too far since touch down. Cancel the long press timer.
|
||||
[longPressTimer invalidate];
|
||||
longPressTimer = nil;
|
||||
}
|
||||
|
||||
[streamView updateCursorLocation:[[touches anyObject] locationInView:streamView] isMouse:NO];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
|
||||
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC.
|
||||
|
||||
// Only fire this logic if all touches have ended
|
||||
if ([[event allTouches] count] == [touches count]) {
|
||||
// Cancel the long press timer
|
||||
[longPressTimer invalidate];
|
||||
longPressTimer = nil;
|
||||
|
||||
// Left button up on finger up
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
|
||||
// Raise right button too in case we triggered a long press gesture
|
||||
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
|
||||
|
||||
// Remember this last touch for touch-down deadzoning
|
||||
lastTouchUp = [touches anyObject];
|
||||
lastTouchUpLocation = [lastTouchUp locationInView:streamView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
// Treat this as a normal touchesEnded event
|
||||
[self touchesEnded:touches withEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// OSCProfile.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Long Le on 12/22/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OnScreenButtonState.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
This object contains information pertaining to any of the user created, custom on screen controller layout configurations, or 'profiles.' The object contains a 'name' property for easy reference, as well as an 'isSelected' property which is used to determine whether this particular custom OSC layout should show on screen during game stream view. Only one 'OSCProfile' is set to 'isSelected' at any given time. The object also contains an array of 'OnScreenButtonStates' which provides information that allows us to move and hide/unhide each of the 19 on screen buttons. Note that the 'buttonStates' property should contain an NSMutableArray of ENCODED 'OnScreenButtonState' objects. This allows us to save the 'OSCProfile' object to NSUserDefaults.
|
||||
Additionally the 'OSCProfile' object adopts encoding and decoding protocols so that we can encode the object before saving it to NSUserDefaults. By saving this object to NSUserDefaults we allow the user to save and load their custom on screen controller button layouts between app launches
|
||||
*/
|
||||
@interface OSCProfile : NSObject <NSCoding, NSSecureCoding>
|
||||
|
||||
@property NSString *name;
|
||||
@property NSMutableArray <OnScreenButtonState *> *buttonStates;
|
||||
@property BOOL isSelected;
|
||||
|
||||
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected;
|
||||
|
||||
+ (BOOL) supportsSecureCoding;
|
||||
- (id) initWithCoder:(NSCoder*)decoder;
|
||||
- (void) encodeWithCoder:(NSCoder*)encoder;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// OSCProfile.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Long Le on 12/22/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OSCProfile.h"
|
||||
|
||||
@implementation OSCProfile
|
||||
|
||||
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected {
|
||||
if ((self = [self init])) {
|
||||
self.name = name;
|
||||
self.buttonStates = buttonStates;
|
||||
self.isSelected = isSelected;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL) supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) encodeWithCoder:(NSCoder*)encoder {
|
||||
[encoder encodeObject:self.name forKey:@"name"];
|
||||
[encoder encodeObject:self.buttonStates forKey:@"buttonStates"];
|
||||
[encoder encodeBool:self.isSelected forKey:@"isSelected"];
|
||||
}
|
||||
|
||||
- (id) initWithCoder:(NSCoder*)decoder {
|
||||
if (self = [super init]) {
|
||||
self.name = [decoder decodeObjectForKey:@"name"];
|
||||
self.buttonStates = [decoder decodeObjectForKey:@"buttonStates"];
|
||||
self.isSelected = [decoder decodeBoolForKey:@"isSelected"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,333 +0,0 @@
|
||||
//
|
||||
// OSCProfilesManager.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Long Le on 1/1/23.
|
||||
// Copyright © 2023 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OSCProfilesManager.h"
|
||||
#import "OnScreenButtonState.h"
|
||||
#import "Moonlight-Swift.h"
|
||||
#import "LayoutOnScreenControlsViewController.h"
|
||||
#import "OnScreenControls.h"
|
||||
|
||||
@implementation OSCProfilesManager
|
||||
|
||||
static NSMutableSet *OnScreenWidgetViews;
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
+ (OSCProfilesManager *) sharedManager {
|
||||
static OSCProfilesManager *_sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
_sharedManager = [[self alloc] init];
|
||||
});
|
||||
return _sharedManager;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Class Helper Methods
|
||||
|
||||
|
||||
+ (void) setOnScreenWidgetViewsSet:(NSMutableSet* )set{
|
||||
OnScreenWidgetViews = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the profile whose 'name' property has a value that is equal to the 'name' passed into the method
|
||||
*/
|
||||
- (OSCProfile *) OSCProfileWithName:(NSString*)name {
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
|
||||
for (OSCProfile *profile in profiles) {
|
||||
|
||||
if ([profile.name isEqualToString:name]) {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of encoded 'OSCProfile' objects from the array of decoded 'OSCProfile' objects passed into this method
|
||||
*/
|
||||
- (NSMutableArray *) encodedProfilesFromArray:(NSMutableArray *)profiles {
|
||||
|
||||
NSMutableArray *profilesEncoded = [[NSMutableArray alloc] init];
|
||||
/* encode each profile and add them back into an array */
|
||||
for (OSCProfile *profile in profiles) { //
|
||||
NSData *profileEncoded = [NSKeyedArchiver archivedDataWithRootObject:profile requiringSecureCoding:YES error:nil];
|
||||
[profilesEncoded addObject:profileEncoded];
|
||||
}
|
||||
|
||||
return profilesEncoded;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an 'OSCProfile' object of specific index for another in the 'OSCProfile' objects array stored in persistent storage
|
||||
*/
|
||||
-(void)deleteCurrentSelectedProfile{
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
NSInteger selectedIndex = [self getIndexOfSelectedProfile];
|
||||
|
||||
/* Remove the selected profile */
|
||||
if(selectedIndex != 0) [profiles removeObjectAtIndex:selectedIndex]; //deleting default profile (index 0) is not allowed
|
||||
else {
|
||||
NSLog(@"Default profile!"); // to be updated here.
|
||||
}
|
||||
|
||||
selectedIndex--;
|
||||
if(selectedIndex < 0) selectedIndex = 0;
|
||||
OSCProfile *newSelectedProfile = [profiles objectAtIndex:selectedIndex];
|
||||
newSelectedProfile.isSelected = YES;
|
||||
|
||||
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
|
||||
|
||||
/* Encode the array itself, NOT the objects in the array, which are already encoded. Save array to persistent storage */
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces one 'OSCProfile' object for another in the 'OSCProfile' objects array stored in persistent storage
|
||||
*/
|
||||
- (void) replaceProfile:(OSCProfile*)oldProfile withProfile:(OSCProfile*)newProfile {
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
|
||||
for (OSCProfile *profile in profiles) { // set all profiles' 'isSelected' property to NO since the new profile we're saving over the old profile with will be the 'selected' profile
|
||||
profile.isSelected = NO;
|
||||
}
|
||||
|
||||
/* Set the new profile as the selected one. The reasoning behind this is that this method is currently being used when the user saves over an existing profile with another profile that has the same name. The expected behavior is that the newly saved profile becomes the selected profile which will show on screen when they launch the game stream view */
|
||||
newProfile.isSelected = YES;
|
||||
|
||||
/* Remove the old profile from the array and insert the new profile into its place */
|
||||
int index = 0;
|
||||
for (int i = 0; i < profiles.count; i++) {
|
||||
|
||||
if ([[profiles[i] name] isEqualToString: oldProfile.name]) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
[profiles removeObjectAtIndex:index];
|
||||
[profiles insertObject:newProfile atIndex:index];
|
||||
|
||||
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
|
||||
|
||||
/* Encode the array itself, NOT the objects in the array, which are already encoded. Save array to persistent storage */
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Globally Accessible Methods
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (NSMutableArray *) getAllProfiles {
|
||||
|
||||
NSData *profilesArrayEncoded = [[NSUserDefaults standardUserDefaults] objectForKey: @"OSCProfiles"]; // Get the encoded array of encoded OSC profiles from persistent storage
|
||||
NSSet *classes = [NSSet setWithObjects:[NSString class], [NSMutableData class], [NSMutableArray class], [OSCProfile class], [OnScreenButtonState class], nil];
|
||||
|
||||
NSMutableArray *profilesEncoded = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:profilesArrayEncoded error:nil]; // Decode the encoded array itself, NOT the objects contained in the array
|
||||
|
||||
/* Decode each of the encoded profiles, place them into an array, then return the array */
|
||||
NSMutableArray *profilesDecoded = [[NSMutableArray alloc] init];
|
||||
OSCProfile *profileDecoded;
|
||||
for (NSData *profileEncoded in profilesEncoded) {
|
||||
|
||||
profileDecoded = [NSKeyedUnarchiver unarchivedObjectOfClasses: classes fromData:profileEncoded error: nil];
|
||||
[profilesDecoded addObject: profileDecoded];
|
||||
}
|
||||
return profilesDecoded;
|
||||
}
|
||||
|
||||
- (OSCProfile *) getSelectedProfile {
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
|
||||
for (OSCProfile *profile in profiles) {
|
||||
|
||||
if (profile.isSelected) {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSInteger) getIndexOfSelectedProfile {
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
for (OSCProfile *profile in profiles) {
|
||||
|
||||
if (profile.isSelected == YES) {
|
||||
return [profiles indexOfObject:profile];
|
||||
}
|
||||
}
|
||||
return 0; // if none of the profiles in the array have their 'isSelected' property set to YES (which should not be possible) return the 'Default' profile as the 'selected' profile
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void) setProfileToSelected:(NSString *)name {
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
|
||||
/* Iterate through each profile. If its name equals the value of the 'name' parameter passed into this method then set the profile's 'isSelected' property to YES, otherwise set the value to NO */
|
||||
for (OSCProfile *profile in profiles) {
|
||||
|
||||
if ([profile.name isEqualToString:name]) {
|
||||
profile.isSelected = YES;
|
||||
}
|
||||
else {
|
||||
profile.isSelected = NO;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
|
||||
|
||||
/* Encode the array itself, NOT the objects inside the array, which have already been encoded by this point */
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
|
||||
- (bool) updateSelectedProfile:(NSMutableArray *) oscButtonLayers {
|
||||
NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndButtonsToButtonStates:oscButtonLayers];
|
||||
if([self getIndexOfSelectedProfile]==0) return false;
|
||||
OSCProfile *newProfile = [[OSCProfile alloc] initWithName:[self getSelectedProfile].name
|
||||
buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view
|
||||
|
||||
|
||||
/* set all saved OSCProfiles 'isSelected' property to NO since the new profile you're adding will be set as the selected profile */
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
for (OSCProfile *profile in profiles) {
|
||||
profile.isSelected = NO;
|
||||
}
|
||||
[self replaceProfile:[self getSelectedProfile] withProfile:newProfile];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
- (void) saveProfileWithName:(NSString*)name andButtonLayers:(NSMutableArray *)oscButtonLayers {
|
||||
NSMutableArray* buttonStatesEncoded = [self convertOnScreenControllerAndButtonsToButtonStates:oscButtonLayers];
|
||||
OSCProfile *newProfile = [[OSCProfile alloc] initWithName:name
|
||||
buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view
|
||||
/* set all saved OSCProfiles 'isSelected' property to NO since the new profile you're adding will be set as the selected profile */
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
for (OSCProfile *profile in profiles) {
|
||||
|
||||
profile.isSelected = NO;
|
||||
}
|
||||
|
||||
if ([self profileNameAlreadyExist:name]) { // if a saved profile with the same 'name' already exists in persistent storage then overwrite it and save the change to persistent storage
|
||||
[self replaceProfile:[self OSCProfileWithName:name] withProfile:newProfile];
|
||||
}
|
||||
else { // otherwise encode then add the new profile to the end of the OSCProfiles array
|
||||
NSData *newProfileEncoded = [NSKeyedArchiver archivedDataWithRootObject:newProfile requiringSecureCoding:YES error:nil];
|
||||
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles];
|
||||
[profilesEncoded addObject:newProfileEncoded];
|
||||
|
||||
/* Encode the 'profilesEncoded' array itself, NOT the objects in the 'profilesEncoded' array, all of which are already encoded by this point */
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
// saving test:
|
||||
/*
|
||||
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
|
||||
NSString *path = [documentsPath stringByAppendingPathComponent:@"newDefault.bin"];
|
||||
[newProfileEncoded writeToFile:path atomically:YES];
|
||||
NSLog(@"默认 Profile 数据已保存到: %@", path);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSMutableArray* ) convertOnScreenControllerAndButtonsToButtonStates:(NSMutableArray *) oscButtonLayers {
|
||||
/* iterate through each OSC button the user sees on screen, create an 'OnScreenButtonState' object from each button, encode each object, and then add each object to an array */
|
||||
/*
|
||||
NSSet *validPositionButtonNames = [NSSet setWithObjects:
|
||||
@"l2Button",
|
||||
@"l1Button",
|
||||
@"dPad",
|
||||
@"selectButton",
|
||||
@"leftStickBackground",
|
||||
@"rightStickBackground",
|
||||
@"r2Button",
|
||||
@"r1Button",
|
||||
@"aButton",
|
||||
@"bButton",
|
||||
@"xButton",
|
||||
@"yButton",
|
||||
@"startButton",
|
||||
nil]; */
|
||||
NSMutableArray *buttonStatesEncoded = [[NSMutableArray alloc] init];
|
||||
|
||||
// save on-screen game controller buttons & sticks as buttonstate:
|
||||
for (CALayer *oscButtonLayer in oscButtonLayers) {
|
||||
|
||||
OnScreenButtonState *buttonState = [[OnScreenButtonState alloc] initWithButtonName:oscButtonLayer.name buttonType:LegacyOscButton andPosition:oscButtonLayer.position];
|
||||
// add hidden attr here
|
||||
buttonState.isHidden = oscButtonLayer.isHidden;
|
||||
buttonState.oscLayerSizeFactor = [OnScreenControls getControllerLayerSizeFactor:oscButtonLayer];
|
||||
buttonState.backgroundAlpha = oscButtonLayer.opacity;
|
||||
NSLog(@"oscLayerName: %@, opacity: %f, ", oscButtonLayer.name, buttonState.backgroundAlpha);
|
||||
|
||||
|
||||
// buttonState.oscLayerSizeFactor = oscButtonLayer.bounds;
|
||||
|
||||
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
|
||||
[buttonStatesEncoded addObject: buttonStateEncoded];
|
||||
}
|
||||
|
||||
// save on-screen widget views (keyboard & mouse command) as buttonstate:
|
||||
for(OnScreenWidgetView* widgetView in OnScreenWidgetViews){
|
||||
OnScreenButtonState *buttonState = [[OnScreenButtonState alloc] initWithButtonName:widgetView.keyString buttonType:CustomOnScreenWidget andPosition:widgetView.center];
|
||||
buttonState.alias = widgetView.keyLabel;
|
||||
buttonState.timestamp = widgetView.timestamp;
|
||||
buttonState.widthFactor = widgetView.widthFactor;
|
||||
buttonState.heightFactor = widgetView.heightFactor;
|
||||
buttonState.backgroundAlpha = widgetView.backgroundAlpha;
|
||||
buttonState.borderWidth = widgetView.borderWidth;
|
||||
buttonState.sensitivityFactor = widgetView.sensitivityFactor;
|
||||
buttonState.stickIndicatorOffset = widgetView.stickIndicatorOffset;
|
||||
|
||||
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
|
||||
[buttonStatesEncoded addObject: buttonStateEncoded];
|
||||
|
||||
}
|
||||
return buttonStatesEncoded;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Queries
|
||||
|
||||
- (BOOL) profileNameAlreadyExist:(NSString*)name {
|
||||
NSMutableArray *profiles = [self getAllProfiles];
|
||||
|
||||
/* Iterate through the decoded profiles and return 'YES' if one of the profiles' 'name' properties equals the 'name' passed into this method */
|
||||
for (OSCProfile *profile in profiles) {
|
||||
|
||||
if ([profile.name isEqualToString:name]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,64 +0,0 @@
|
||||
//
|
||||
// OnScreenButtonState.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Long Le on 10/20/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OnScreenButtonState.h"
|
||||
|
||||
@implementation OnScreenButtonState
|
||||
|
||||
|
||||
- (id) initWithButtonName:(NSString*)name buttonType:(uint8_t)buttonType andPosition:(CGPoint)position {
|
||||
if ((self = [self init])) {
|
||||
self.name = name;
|
||||
//self.isHidden = isHidden;
|
||||
self.position = position;
|
||||
self.buttonType = buttonType;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL) supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) encodeWithCoder:(NSCoder*)encoder {
|
||||
[encoder encodeObject:self.name forKey:@"name"];
|
||||
[encoder encodeObject:self.alias forKey:@"alias"];
|
||||
[encoder encodeDouble:self.timestamp forKey:@"timestamp"];
|
||||
[encoder encodeInt:self.buttonType forKey:@"buttonType"];
|
||||
[encoder encodeCGPoint:self.position forKey:@"position"];
|
||||
[encoder encodeBool:self.isHidden forKey:@"isHidden"];
|
||||
[encoder encodeFloat:self.widthFactor forKey:@"widthFactor"];
|
||||
[encoder encodeFloat:self.heightFactor forKey:@"heightFactor"];
|
||||
[encoder encodeFloat:self.sensitivityFactor forKey:@"velocityFactor"];
|
||||
[encoder encodeFloat:self.stickIndicatorOffset forKey:@"stickIndicatorOffset"];
|
||||
[encoder encodeFloat:self.oscLayerSizeFactor forKey:@"oscLayerSizeFactor"];
|
||||
[encoder encodeFloat:self.backgroundAlpha forKey:@"backgroundAlpha"];
|
||||
[encoder encodeFloat:self.borderWidth forKey:@"borderWidth"];
|
||||
}
|
||||
|
||||
- (id) initWithCoder:(NSCoder*)decoder {
|
||||
if (self = [super init]) {
|
||||
self.name = [decoder decodeObjectForKey:@"name"];
|
||||
self.alias = [decoder decodeObjectForKey:@"alias"];
|
||||
self.timestamp = [decoder decodeDoubleForKey:@"timestamp"];
|
||||
self.buttonType = [decoder decodeIntForKey:@"buttonType"];
|
||||
self.position = [decoder decodeCGPointForKey:@"position"];
|
||||
self.isHidden = [decoder decodeBoolForKey:@"isHidden"];
|
||||
self.widthFactor = [decoder decodeFloatForKey:@"widthFactor"];
|
||||
self.heightFactor = [decoder decodeFloatForKey:@"heightFactor"];
|
||||
self.sensitivityFactor = [decoder decodeFloatForKey:@"velocityFactor"];
|
||||
self.stickIndicatorOffset = [decoder decodeFloatForKey:@"stickIndicatorOffset"];
|
||||
self.oscLayerSizeFactor = [decoder decodeFloatForKey:@"oscLayerSizeFactor"];
|
||||
self.backgroundAlpha = [decoder decodeFloatForKey:@"backgroundAlpha"];
|
||||
self.borderWidth = [decoder decodeFloatForKey:@"borderWidth"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,164 +0,0 @@
|
||||
//
|
||||
// NativeTouchHandler.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by ZWM on 2024/6/16.
|
||||
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "NativeTouchHandler.h"
|
||||
#import "NativeTouchPointer.h"
|
||||
#import "OnScreenControls.h"
|
||||
#import "StreamView.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
|
||||
|
||||
|
||||
@implementation NativeTouchHandler {
|
||||
StreamView* streamView;
|
||||
TemporarySettings* currentSettings;
|
||||
bool activateCoordSelector;
|
||||
bool touchPointSpawnedAtUpperScreenEdge;
|
||||
|
||||
// Use a Dictionary to store UITouch object's memory address as key, and pointerId as value,字典存放UITouch对象地址和pointerId映射关系
|
||||
// pointerId will be generated from a pre-defined pool
|
||||
// Use a NSSet store active pointerId,
|
||||
NSMutableDictionary *pointerIdDict; //pointerId Dict for active touches.
|
||||
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
|
||||
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
|
||||
NSMutableSet<NSNumber *> *unassignedPointerIds;
|
||||
|
||||
CGFloat slideGestureVerticalThreshold;
|
||||
CGFloat screenWidthWithThreshold;
|
||||
CGFloat EDGE_TOLERANCE;
|
||||
}
|
||||
|
||||
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
|
||||
self = [super init];
|
||||
self->streamView = view;
|
||||
self->currentSettings = settings;
|
||||
self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
|
||||
// self->notPureNativeTouchMode = !(currentSettings.touchMode.intValue == PURE_NATIVE_TOUCH);
|
||||
|
||||
self->pointerIdDict = [NSMutableDictionary dictionary];
|
||||
self->pointerIdPool = [NSMutableSet set];
|
||||
for (uint8_t i = 0; i <= 10; i++) { //ipadOS supports upto 11 finger touches
|
||||
[self->pointerIdPool addObject:@(i)];
|
||||
}
|
||||
self->activePointerIds = [NSMutableSet set];
|
||||
// self->excludedPointerIds = [[NSMutableSet alloc] init];
|
||||
self->touchPointSpawnedAtUpperScreenEdge = false;
|
||||
|
||||
EDGE_TOLERANCE = 15.0;
|
||||
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
|
||||
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
|
||||
|
||||
[NativeTouchPointer setPointerVelocityDivider:settings.pointerVelocityModeDivider.floatValue];
|
||||
[NativeTouchPointer setPointerVelocityFactor:settings.touchPointerVelocityFactor.floatValue];
|
||||
[NativeTouchPointer initContextWithView:self->streamView];
|
||||
//_touchesCapturedByOnScreenButtons = [[NSMutableSet alloc] init];
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// generate & populate pointerId into NSDict & NSSet, called in touchesBegan
|
||||
- (void)handleTouchDown:(UITouch*)touch{
|
||||
//populate pointerId
|
||||
uintptr_t memAddrValue = (uintptr_t)touch;
|
||||
unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds
|
||||
[unassignedPointerIds minusSet:activePointerIds];
|
||||
uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue];
|
||||
[pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)];
|
||||
[activePointerIds addObject:@(pointerId)];
|
||||
|
||||
//check if touch point is spawned on the left or right upper half screen edges, event to remote PC. this is for better handling in-stream slide gesture
|
||||
CGPoint initialPoint = [touch locationInView:self->streamView];
|
||||
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
|
||||
self->touchPointSpawnedAtUpperScreenEdge = true;
|
||||
// [excludedPointerIds addObject:@(pointerId)];
|
||||
}
|
||||
}
|
||||
|
||||
// remove pointerId in touchesEnded or touchesCancelled
|
||||
- (void)removePointerId:(UITouch*)touch{
|
||||
uintptr_t memAddrValue = (uintptr_t)touch;
|
||||
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)];
|
||||
if(pointerIdObj != nil){
|
||||
[activePointerIds removeObject:pointerIdObj];
|
||||
[pointerIdDict removeObjectForKey:@(memAddrValue)];
|
||||
// if([excludedPointerIds containsObject:pointerIdObj]) [excludedPointerIds removeObject:pointerIdObj]; // remove pointer id from excludedPointerId NSSet
|
||||
}
|
||||
}
|
||||
|
||||
// 从字典中获取UITouch事件对应的pointerId
|
||||
// called in method of sendTouchEvent
|
||||
- (uint8_t) retrievePointerIdFromDict:(UITouch*)touch{
|
||||
return [[pointerIdDict objectForKey:@((uintptr_t)touch)] unsignedIntValue];
|
||||
}
|
||||
|
||||
|
||||
- (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{
|
||||
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash!
|
||||
|
||||
CGPoint targetCoords;
|
||||
// NSLog(@"excluded count: %d", (uint32_t)[excludedPointerIds count]);
|
||||
if(activateCoordSelector && touch.phase == UITouchPhaseMoved) targetCoords = [NativeTouchPointer selectCoordsFor:touch]; // coordinates of touch pointer replaced to relative ones here.
|
||||
else targetCoords = [touch locationInView:streamView];
|
||||
|
||||
CGPoint location = [streamView adjustCoordinatesForVideoArea:targetCoords];
|
||||
CGSize videoSize = [streamView getVideoAreaSize];
|
||||
CGFloat normalizedX = location.x / videoSize.width;
|
||||
CGFloat normalizedY = location.y / videoSize.height;
|
||||
uint8_t pointerId = [self retrievePointerIdFromDict:touch];
|
||||
|
||||
if([NativeTouchPointer getPointerObjFromDict:touch].needResetCoords){ // access whether the current pointer has reached the boundary, and need a coord reset.
|
||||
LiSendTouchEvent(LI_TOUCH_EVENT_UP, pointerId, normalizedX, normalizedY, 0, 0, 0, 0); //event must sent from the lowest level directy by LiSendTouchEvent to simulate continous dragging to another point on screen
|
||||
LiSendTouchEvent(LI_TOUCH_EVENT_DOWN, pointerId, 0.3, 0.4, 0, 0, 0, 0);
|
||||
}else LiSendTouchEvent(touchType, pointerId, normalizedX, normalizedY,(touch.force / touch.maximumPossibleForce) / sin(touch.altitudeAngle),0.0f, 0.0f,[streamView getRotationFromAzimuthAngle:[touch azimuthAngleInView:streamView]]);
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
for (UITouch* touch in touches){
|
||||
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
|
||||
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
|
||||
[self handleTouchDown:touch]; //generate & populate pointerId
|
||||
if(activateCoordSelector) [NativeTouchPointer populatePointerObjIntoDict:touch];
|
||||
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
// NSLog(@"captured by OSB touches: %d", (uint32_t)[OnScreenControls.touchAddrsCapturedByOnScreenControls count]);
|
||||
for (UITouch* touch in touches){
|
||||
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
|
||||
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
|
||||
if(activateCoordSelector) [NativeTouchPointer updatePointerObjInDict:touch];
|
||||
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
|
||||
[[NativeTouchPointer getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
for (UITouch* touch in touches){
|
||||
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
|
||||
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
|
||||
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
|
||||
[self removePointerId:touch]; //then remove pointerId
|
||||
if(activateCoordSelector) [NativeTouchPointer removePointerObjFromDict:touch];
|
||||
}
|
||||
if(touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) touchPointSpawnedAtUpperScreenEdge = false;
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
[self touchesEnded:touches withEvent:event];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// NativeTouchPointer.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by ZWM on 2024/5/14.
|
||||
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "StreamView.h"
|
||||
#import "NativeTouchHandler.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NativeTouchPointer : NSObject
|
||||
@property (nonatomic, assign) bool needResetCoords;
|
||||
|
||||
+ (void)initContextWithView:(StreamView *)view;
|
||||
+ (void)setPointerVelocityDivider:(CGFloat)dividerLocation;
|
||||
+ (void)setPointerVelocityFactor:(CGFloat)velocityFactor;
|
||||
|
||||
+ (NativeTouchPointer* )getPointerObjFromDict:(UITouch*)touch;
|
||||
- (bool)doesNeedResetCoords;
|
||||
|
||||
+ (void)populatePointerObjIntoDict:(UITouch*)touch;
|
||||
+ (void)removePointerObjFromDict:(UITouch*)touch;
|
||||
+ (void)updatePointerObjInDict:(UITouch *)touch;
|
||||
+ (CGPoint)selectCoordsFor:(UITouch *)touch;
|
||||
|
||||
|
||||
- (instancetype)initWithTouch:(UITouch *)touch;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
//
|
||||
// NativeTouchPointer.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by ZWM on 2024/5/14.
|
||||
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "NativeTouchPointer.h"
|
||||
#include <Limelight.h>
|
||||
|
||||
// native touch pointer ojbect that stores & manipulates touch coordinates
|
||||
|
||||
static NSMutableDictionary *pointerObjDict;
|
||||
|
||||
static CGFloat pointerVelocityFactor;
|
||||
static CGFloat pointerVelocityDivider;
|
||||
static CGFloat pointerVelocityDividerLocationByPoints;
|
||||
static CGFloat streamViewHeight;
|
||||
static CGFloat streamViewWidth;
|
||||
static CGFloat fixedResetCoordX;
|
||||
static CGFloat fixedResetCoordY;
|
||||
static StreamView* _streamView;
|
||||
|
||||
@implementation NativeTouchPointer{
|
||||
CGPoint initialPoint;
|
||||
CGPoint latestPoint;
|
||||
CGPoint previousPoint;
|
||||
CGPoint latestRelativePoint;
|
||||
CGPoint previousRelativePoint;
|
||||
bool useRelativeCoords;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTouch:(UITouch *)touch{
|
||||
self = [super init];
|
||||
self->initialPoint = [touch locationInView:_streamView];
|
||||
self->latestPoint = self->initialPoint;
|
||||
self->latestRelativePoint = self->initialPoint;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (bool)doesNeedResetCoords{
|
||||
bool boundaryReached = (latestRelativePoint.x > streamViewWidth || latestRelativePoint.x < 0.0f || latestRelativePoint.y > streamViewHeight || latestRelativePoint.y < 0.0f);
|
||||
bool withinExcludedArea = (initialPoint.x > streamViewWidth * (0.75) && initialPoint.x < streamViewWidth) && (initialPoint.y > streamViewHeight * (0.8) && initialPoint.y < streamViewHeight);
|
||||
_needResetCoords = (pointerVelocityFactor > 1.0f) && self->useRelativeCoords && boundaryReached && !withinExcludedArea;
|
||||
// boundary detection & coordinates reset to the specific point for HK:StarTrail(needs a very high pointer velocity)
|
||||
// must exclude touch pointer that uses native coords instead of relative ones.
|
||||
// also exclude touch pointer created within the bottom right corner
|
||||
|
||||
return _needResetCoords;
|
||||
}
|
||||
|
||||
- (void)updatePointerCoords:(UITouch *)touch{
|
||||
previousPoint = latestPoint;
|
||||
latestPoint = [touch locationInView:_streamView];
|
||||
previousRelativePoint = latestRelativePoint;
|
||||
if(_needResetCoords){// boundary detection & coordinates reset to the central screen point for HK:StarTrail(needs a very high pointer velocity); // boundary detection & coordinates reset to specific point for HK:StarTrail(needs a very high pointer velocity)
|
||||
previousRelativePoint.x = fixedResetCoordX;
|
||||
previousRelativePoint.y = fixedResetCoordY;
|
||||
}
|
||||
latestRelativePoint.x = previousRelativePoint.x + pointerVelocityFactor * (latestPoint.x - previousPoint.x);
|
||||
latestRelativePoint.y = previousRelativePoint.y + pointerVelocityFactor * (latestPoint.y - previousPoint.y);
|
||||
}
|
||||
|
||||
+ (void)initContextWithView:(StreamView *)view {
|
||||
_streamView = view;
|
||||
streamViewWidth = _streamView.frame.size.width;
|
||||
fixedResetCoordX = streamViewWidth * 0.3;
|
||||
streamViewHeight = _streamView.frame.size.height;
|
||||
fixedResetCoordY = streamViewHeight * 0.4;
|
||||
NSLog(@"stream wdith %f, stream height %f", streamViewWidth, streamViewHeight);
|
||||
pointerObjDict = [NSMutableDictionary dictionary];
|
||||
pointerVelocityDividerLocationByPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]) * pointerVelocityDivider;
|
||||
NSLog(@"pointerVelocityDivider: %.2f", pointerVelocityDivider);
|
||||
NSLog(@"pointerVelocityFactor: %.2f", pointerVelocityFactor);
|
||||
NSLog(@"pointerVelocityDividerLocationByPoints: %.2f", pointerVelocityDividerLocationByPoints);
|
||||
}
|
||||
|
||||
+ (void)setPointerVelocityDivider:(CGFloat)dividerLocation{
|
||||
pointerVelocityDivider = dividerLocation;
|
||||
}
|
||||
|
||||
+ (void)setPointerVelocityFactor:(CGFloat)velocityFactor{
|
||||
pointerVelocityFactor = velocityFactor;
|
||||
}
|
||||
|
||||
|
||||
+ (void)populatePointerObjIntoDict:(UITouch*)touch{
|
||||
[pointerObjDict setObject:[[NativeTouchPointer alloc] initWithTouch:touch] forKey:@((uintptr_t)touch)];
|
||||
}
|
||||
|
||||
+ (void)removePointerObjFromDict:(UITouch*)touch{
|
||||
uintptr_t memAddrValue = (uintptr_t)touch;
|
||||
NativeTouchPointer* pointer = [pointerObjDict objectForKey:@(memAddrValue)];
|
||||
if(pointer != nil){
|
||||
[pointerObjDict removeObjectForKey:@(memAddrValue)];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NativeTouchPointer* )getPointerObjFromDict:(UITouch*)touch{
|
||||
return [pointerObjDict objectForKey:@((uintptr_t)touch)];
|
||||
}
|
||||
|
||||
|
||||
+ (void)updatePointerObjInDict:(UITouch *)touch{
|
||||
[[pointerObjDict objectForKey:@((uintptr_t)touch)] updatePointerCoords:touch];
|
||||
}
|
||||
|
||||
|
||||
+ (CGPoint)selectCoordsFor:(UITouch *)touch{
|
||||
NativeTouchPointer *pointer = [pointerObjDict objectForKey:@((uintptr_t)touch)];
|
||||
if(pointer == nil) return CGPointMake(0, 0);
|
||||
if((pointer -> initialPoint).x > pointerVelocityDividerLocationByPoints){ //if first contact coords locates on the right side of divider.
|
||||
pointer -> useRelativeCoords = true;
|
||||
return pointer->latestRelativePoint;
|
||||
}
|
||||
else{
|
||||
pointer -> useRelativeCoords = false;
|
||||
return[touch locationInView:_streamView];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,148 +0,0 @@
|
||||
//
|
||||
// NativeTouchHandler.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by ZWM on 2024/8/29.
|
||||
// Copyright © 2024 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PureNativeTouchHandler.h"
|
||||
#import "NativeTouchPointer.h"
|
||||
#import "StreamView.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
|
||||
@implementation PureNativeTouchHandler {
|
||||
StreamView* streamView;
|
||||
TemporarySettings* currentSettings;
|
||||
bool activateCoordSelector;
|
||||
bool touchPointSpawnedAtUpperScreenEdge;
|
||||
|
||||
// Use a Dictionary to store UITouch object's memory address as key, and pointerId as value,字典存放UITouch对象地址和pointerId映射关系
|
||||
// pointerId will be generated from a pre-defined pool
|
||||
// Use a NSSet store active pointerId,
|
||||
NSMutableDictionary *pointerIdDict; //pointerId Dict for active touches.
|
||||
NSMutableSet<NSNumber *> *activePointerIds; //pointerId Set for active touches.
|
||||
NSMutableSet<NSNumber *> *pointerIdPool; //pre-defined pool of pointerIds.
|
||||
NSMutableSet<NSNumber *> *unassignedPointerIds;
|
||||
|
||||
CGFloat slideGestureVerticalThreshold;
|
||||
CGFloat screenWidthWithThreshold;
|
||||
CGFloat EDGE_TOLERANCE;
|
||||
}
|
||||
|
||||
|
||||
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
|
||||
self = [super init];
|
||||
self->streamView = view;
|
||||
self->currentSettings = settings;
|
||||
self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
|
||||
|
||||
pointerIdDict = [NSMutableDictionary dictionary];
|
||||
pointerIdPool = [NSMutableSet set];
|
||||
for (uint8_t i = 0; i <= 10; i++) { //ipadOS supports upto 11 finger touches
|
||||
[pointerIdPool addObject:@(i)];
|
||||
}
|
||||
activePointerIds = [NSMutableSet set];
|
||||
self->touchPointSpawnedAtUpperScreenEdge = false;
|
||||
|
||||
EDGE_TOLERANCE = 15.0;
|
||||
slideGestureVerticalThreshold = CGRectGetHeight([[UIScreen mainScreen] bounds]) * 0.4;
|
||||
screenWidthWithThreshold = CGRectGetWidth([[UIScreen mainScreen] bounds]) - EDGE_TOLERANCE;
|
||||
|
||||
[NativeTouchPointer setPointerVelocityDivider:settings.pointerVelocityModeDivider.floatValue];
|
||||
[NativeTouchPointer setPointerVelocityFactor:settings.touchPointerVelocityFactor.floatValue];
|
||||
[NativeTouchPointer initContextWithView:self->streamView];
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
// generate & populate pointerId into NSDict & NSSet, called in touchesBegan
|
||||
- (void)handleTouchDown:(UITouch*)touch{
|
||||
uintptr_t memAddrValue = (uintptr_t)touch;
|
||||
unassignedPointerIds = [pointerIdPool mutableCopy]; //reset unassignedPointerIds
|
||||
[unassignedPointerIds minusSet:activePointerIds];
|
||||
uint8_t pointerId = [[unassignedPointerIds anyObject] unsignedIntValue];
|
||||
[pointerIdDict setObject:@(pointerId) forKey:@(memAddrValue)];
|
||||
[activePointerIds addObject:@(pointerId)];
|
||||
|
||||
//check if touch point is spawned on the left or right upper half screen edges,
|
||||
CGPoint initialPoint = [touch locationInView:self->streamView];
|
||||
if(initialPoint.y < slideGestureVerticalThreshold && (initialPoint.x < EDGE_TOLERANCE || initialPoint.x > screenWidthWithThreshold)) {
|
||||
self->touchPointSpawnedAtUpperScreenEdge = true;
|
||||
}
|
||||
}
|
||||
|
||||
// remove pointerId in touchesEnded or touchesCancelled
|
||||
- (void)removePointerId:(UITouch*)touch{
|
||||
uintptr_t memAddrValue = (uintptr_t)touch;
|
||||
NSNumber* pointerIdObj = [pointerIdDict objectForKey:@(memAddrValue)];
|
||||
if(pointerIdObj != nil){
|
||||
[activePointerIds removeObject:pointerIdObj];
|
||||
[pointerIdDict removeObjectForKey:@(memAddrValue)];
|
||||
}
|
||||
}
|
||||
|
||||
// 从字典中获取UITouch事件对应的pointerId
|
||||
// called in method of sendTouchEvent
|
||||
- (uint8_t) retrievePointerIdFromDict:(UITouch*)touch{
|
||||
return [[pointerIdDict objectForKey:@((uintptr_t)touch)] unsignedIntValue];
|
||||
}
|
||||
|
||||
|
||||
- (void)sendTouchEvent:(UITouch*)touch withTouchtype:(uint8_t)touchType{
|
||||
if(touchPointSpawnedAtUpperScreenEdge) return; // we're done here. this touch event will not be sent to the remote PC. and this must be checked after coord selector finishes populating new relative coords, or the app will crash!
|
||||
|
||||
CGPoint targetCoords;
|
||||
if(activateCoordSelector && touch.phase == UITouchPhaseMoved) targetCoords = [NativeTouchPointer selectCoordsFor:touch]; // coordinates of touch pointer replaced to relative ones here.
|
||||
else targetCoords = [touch locationInView:streamView];
|
||||
|
||||
CGPoint location = [streamView adjustCoordinatesForVideoArea:targetCoords];
|
||||
CGSize videoSize = [streamView getVideoAreaSize];
|
||||
CGFloat normalizedX = location.x / videoSize.width;
|
||||
CGFloat normalizedY = location.y / videoSize.height;
|
||||
uint8_t pointerId = [self retrievePointerIdFromDict:touch];
|
||||
|
||||
if([NativeTouchPointer getPointerObjFromDict:touch].needResetCoords){ // access whether the current pointer has reached the boundary, and need a coord reset.
|
||||
LiSendTouchEvent(LI_TOUCH_EVENT_UP, pointerId, normalizedX, normalizedY, 0, 0, 0, 0); //event must sent from the lowest level directy by LiSendTouchEvent to simulate continous dragging to another point on screen
|
||||
LiSendTouchEvent(LI_TOUCH_EVENT_DOWN, pointerId, 0.3, 0.4, 0, 0, 0, 0);
|
||||
}else LiSendTouchEvent(touchType, pointerId, normalizedX, normalizedY,(touch.force / touch.maximumPossibleForce) / sin(touch.altitudeAngle),0.0f, 0.0f,[streamView getRotationFromAzimuthAngle:[touch azimuthAngleInView:streamView]]);
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
for (UITouch* touch in touches){
|
||||
[self handleTouchDown:touch]; //generate & populate pointerId
|
||||
if(activateCoordSelector) [NativeTouchPointer populatePointerObjIntoDict:touch];
|
||||
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
for (UITouch* touch in touches){
|
||||
if(activateCoordSelector) [NativeTouchPointer updatePointerObjInDict:touch];
|
||||
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
|
||||
[[NativeTouchPointer getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
for (UITouch* touch in touches){
|
||||
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
|
||||
[self removePointerId:touch]; //then remove pointerId
|
||||
if(activateCoordSelector) [NativeTouchPointer removePointerObjFromDict:touch];
|
||||
}
|
||||
if(touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count]) touchPointSpawnedAtUpperScreenEdge = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
[self touchesEnded:touches withEvent:event];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C81f" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="Host" representedClassName="Host" syncable="YES">
|
||||
<attribute name="address" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="2" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="148"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="120"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6254" systemVersion="14C81f" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<entity name="Host" representedClassName="Host" syncable="YES">
|
||||
<attribute name="address" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="148"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="120"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="17D102" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES">
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES">
|
||||
<attribute name="address" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="streamingRemotely" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="163"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Automatic">
|
||||
<entity name="App" representedClassName="App" syncable="YES">
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES">
|
||||
<attribute name="address" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
|
||||
<attribute name="uniqueId" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="163"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="135"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17F77" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="165"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="23H420" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="deviceGyroMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableYUV444" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="externalDisplayMode" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="keyboardToggleFingers" optional="YES" attributeType="Integer 16" defaultValueString="3" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="largerStickLR1" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="liftStreamViewForKeyboard" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="localMousePointerMode" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="mousePointerVelocityFactor" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="oscLayoutToolFingers" optional="YES" attributeType="Integer 16" defaultValueString="4" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="oscTapExlusionAreaSize" optional="YES" attributeType="Float" defaultValueString="1.03" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="pointerVelocityModeDivider" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="preferredCodec" attributeType="Integer 32" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="resolutionSelected" optional="YES" attributeType="Integer 16" defaultValueString="5" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="reverseMouseWheelDirection" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="showKeyboardToolbar" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="slideToSettingsDistance" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="slideToSettingsScreenEdge" optional="YES" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayLevel" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="touchMode" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="touchPointerVelocityFactor" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="unlockDisplayOrientation" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14135" systemVersion="17G65" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="165"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18C54" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="195"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14490.99" systemVersion="18F132" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="225"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19E266" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="streamingRemotely" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
|
||||
<element name="Host" positionX="0" positionY="0" width="128" height="210"/>
|
||||
<element name="Settings" positionX="0" positionY="0" width="128" height="238"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useHevc" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hdrSupported" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="hidden" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="id" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="serverCert" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="serverCodecModeSupport" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Settings" representedClassName="Settings" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="absoluteTouchMode" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="audioConfig" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="height" attributeType="Integer 32" defaultValueString="720" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
|
||||
<attribute name="useFramePacing" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="useHevc2" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// VideoDecoderRenderer.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 10/18/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
@import AVFoundation;
|
||||
|
||||
#import "ConnectionCallbacks.h"
|
||||
|
||||
#include "Limelight.h"
|
||||
|
||||
@interface VideoDecoderRenderer : NSObject
|
||||
|
||||
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing;
|
||||
|
||||
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate;
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
- (void)setHdrMode:(BOOL)enabled;
|
||||
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType decodeUnit:(PDECODE_UNIT)du;
|
||||
|
||||
@end
|
||||
@@ -1,689 +0,0 @@
|
||||
//
|
||||
// VideoDecoderRenderer.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 10/18/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "VideoDecoderRenderer.h"
|
||||
#import "StreamView.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/cbs.h>
|
||||
#include <libavcodec/cbs_av1.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
// Private libavformat API for writing the AV1 Codec Configuration Box
|
||||
extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
||||
int write_seq_header);
|
||||
|
||||
@implementation VideoDecoderRenderer {
|
||||
StreamView* _view;
|
||||
id<ConnectionCallbacks> _callbacks;
|
||||
float _streamAspectRatio;
|
||||
|
||||
AVSampleBufferDisplayLayer* displayLayer;
|
||||
int videoFormat;
|
||||
int frameRate;
|
||||
|
||||
NSMutableArray *parameterSetBuffers;
|
||||
NSData *masteringDisplayColorVolume;
|
||||
NSData *contentLightLevelInfo;
|
||||
CMVideoFormatDescriptionRef formatDesc;
|
||||
|
||||
CADisplayLink* _displayLink;
|
||||
BOOL framePacing;
|
||||
}
|
||||
|
||||
- (void)reinitializeDisplayLayer
|
||||
{
|
||||
CALayer *oldLayer = displayLayer;
|
||||
|
||||
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
||||
displayLayer.backgroundColor = [UIColor blackColor].CGColor;
|
||||
|
||||
// Ensure the AVSampleBufferDisplayLayer is sized to preserve the aspect ratio
|
||||
// of the video stream. We used to use AVLayerVideoGravityResizeAspect, but that
|
||||
// respects the PAR encoded in the SPS which causes our computed video-relative
|
||||
// touch location to be wrong in StreamView if the aspect ratio of the host
|
||||
// desktop doesn't match the aspect ratio of the stream.
|
||||
CGSize videoSize;
|
||||
if (_view.bounds.size.width > _view.bounds.size.height * _streamAspectRatio) {
|
||||
videoSize = CGSizeMake(_view.bounds.size.height * _streamAspectRatio, _view.bounds.size.height);
|
||||
} else {
|
||||
videoSize = CGSizeMake(_view.bounds.size.width, _view.bounds.size.width / _streamAspectRatio);
|
||||
}
|
||||
displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
|
||||
displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
|
||||
displayLayer.videoGravity = AVLayerVideoGravityResize;
|
||||
|
||||
// Hide the layer until we get an IDR frame. This ensures we
|
||||
// can see the loading progress label as the stream is starting.
|
||||
displayLayer.hidden = YES;
|
||||
|
||||
if (oldLayer != nil) {
|
||||
// Switch out the old display layer with the new one
|
||||
[_view.layer replaceSublayer:oldLayer with:displayLayer];
|
||||
}
|
||||
else {
|
||||
[_view.layer addSublayer:displayLayer];
|
||||
}
|
||||
|
||||
if (formatDesc != nil) {
|
||||
CFRelease(formatDesc);
|
||||
formatDesc = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithView:(StreamView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_view = view;
|
||||
_callbacks = callbacks;
|
||||
_streamAspectRatio = aspectRatio;
|
||||
framePacing = useFramePacing;
|
||||
|
||||
parameterSetBuffers = [[NSMutableArray alloc] init];
|
||||
|
||||
[self reinitializeDisplayLayer];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reinitializeDisplayLayer)
|
||||
name:@"ScreenChanged"
|
||||
object:nil];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate
|
||||
{
|
||||
self->videoFormat = videoFormat;
|
||||
self->frameRate = frameRate;
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)];
|
||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->frameRate, self->frameRate, self->frameRate);
|
||||
}
|
||||
else {
|
||||
_displayLink.preferredFramesPerSecond = self->frameRate;
|
||||
}
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
}
|
||||
|
||||
// TODO: Refactor this
|
||||
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
||||
|
||||
- (void)displayLinkCallback:(CADisplayLink *)sender
|
||||
{
|
||||
VIDEO_FRAME_HANDLE handle;
|
||||
PDECODE_UNIT du;
|
||||
|
||||
while (LiPollNextVideoFrame(&handle, &du)) {
|
||||
LiCompleteVideoFrame(handle, DrSubmitDecodeUnit(du));
|
||||
|
||||
if (framePacing) {
|
||||
// Calculate the actual display refresh rate
|
||||
double displayRefreshRate = 1 / (_displayLink.targetTimestamp - _displayLink.timestamp);
|
||||
|
||||
// Only pace frames if the display refresh rate is >= 90% of our stream frame rate.
|
||||
// Battery saver, accessibility settings, or device thermals can cause the actual
|
||||
// refresh rate of the display to drop below the physical maximum.
|
||||
if (displayRefreshRate >= frameRate * 0.9f) {
|
||||
// Keep one pending frame to smooth out gaps due to
|
||||
// network jitter at the cost of 1 frame of latency
|
||||
if (LiGetPendingVideoFrames() == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
[_displayLink invalidate];
|
||||
}
|
||||
|
||||
#define NALU_START_PREFIX_SIZE 3
|
||||
#define NAL_LENGTH_PREFIX_SIZE 4
|
||||
|
||||
- (void)updateAnnexBBufferForRange:(CMBlockBufferRef)frameBuffer dataBlock:(CMBlockBufferRef)dataBuffer offset:(int)offset length:(int)nalLength
|
||||
{
|
||||
OSStatus status;
|
||||
size_t oldOffset = CMBlockBufferGetDataLength(frameBuffer);
|
||||
|
||||
// Append a 4 byte buffer to the frame block for the length prefix
|
||||
status = CMBlockBufferAppendMemoryBlock(frameBuffer, NULL,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
kCFAllocatorDefault, NULL, 0,
|
||||
NAL_LENGTH_PREFIX_SIZE, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendMemoryBlock failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the length prefix to the new buffer
|
||||
const int dataLength = nalLength - NALU_START_PREFIX_SIZE;
|
||||
const uint8_t lengthBytes[] = {(uint8_t)(dataLength >> 24), (uint8_t)(dataLength >> 16),
|
||||
(uint8_t)(dataLength >> 8), (uint8_t)dataLength};
|
||||
status = CMBlockBufferReplaceDataBytes(lengthBytes, frameBuffer,
|
||||
oldOffset, NAL_LENGTH_PREFIX_SIZE);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferReplaceDataBytes failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach the data buffer to the frame buffer by reference
|
||||
status = CMBlockBufferAppendBufferReference(frameBuffer, dataBuffer, offset + NALU_START_PREFIX_SIZE, dataLength, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendBufferReference failed: %d", (int)status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData*)getAv1CodecConfigurationBox:(NSData*)frameData {
|
||||
AVIOContext* ioctx = NULL;
|
||||
int err;
|
||||
|
||||
err = avio_open_dyn_buf(&ioctx);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"avio_open_dyn_buf() failed: %d", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Submit the IDR frame to write the av1C blob
|
||||
err = ff_isom_write_av1c(ioctx, (uint8_t*)frameData.bytes, (int)frameData.length, 1);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"ff_isom_write_av1c() failed: %d", err);
|
||||
// Fall-through to close and free buffer
|
||||
}
|
||||
|
||||
// Close the dynbuf and get the underlying buffer back (which we must free)
|
||||
uint8_t* av1cBuf = NULL;
|
||||
int av1cBufLen = avio_close_dyn_buf(ioctx, &av1cBuf);
|
||||
|
||||
Log(LOG_I, @"av1C block is %d bytes", av1cBufLen);
|
||||
|
||||
// Only return data if ff_isom_write_av1c() was successful
|
||||
NSData* data = nil;
|
||||
if (err >= 0 && av1cBufLen > 0) {
|
||||
data = [NSData dataWithBytes:av1cBuf length:av1cBufLen];
|
||||
}
|
||||
else {
|
||||
data = nil;
|
||||
}
|
||||
|
||||
av_free(av1cBuf);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Much of this logic comes from Chrome
|
||||
- (CMVideoFormatDescriptionRef)createAV1FormatDescriptionForIDRFrame:(NSData*)frameData {
|
||||
NSMutableDictionary* extensions = [[NSMutableDictionary alloc] init];
|
||||
|
||||
CodedBitstreamContext* cbsCtx = NULL;
|
||||
int err = ff_cbs_init(&cbsCtx, AV_CODEC_ID_AV1, NULL);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"ff_cbs_init() failed: %d", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
AVPacket avPacket = {};
|
||||
avPacket.data = (uint8_t*)frameData.bytes;
|
||||
avPacket.size = (int)frameData.length;
|
||||
|
||||
// Read the sequence header OBU
|
||||
CodedBitstreamFragment cbsFrag = {};
|
||||
err = ff_cbs_read_packet(cbsCtx, &cbsFrag, &avPacket);
|
||||
if (err < 0) {
|
||||
Log(LOG_E, @"ff_cbs_read_packet() failed: %d", err);
|
||||
ff_cbs_close(&cbsCtx);
|
||||
return nil;
|
||||
}
|
||||
|
||||
#define SET_CFSTR_EXTENSION(key, value) extensions[(__bridge NSString*)key] = (__bridge NSString*)(value)
|
||||
#define SET_EXTENSION(key, value) extensions[(__bridge NSString*)key] = (value)
|
||||
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_FormatName, @"av01");
|
||||
|
||||
// We use the value for YUV without alpha, same as Chrome
|
||||
// https://developer.apple.com/library/archive/qa/qa1183/_index.html
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_Depth, @24);
|
||||
|
||||
CodedBitstreamAV1Context* bitstreamCtx = (CodedBitstreamAV1Context*)cbsCtx->priv_data;
|
||||
AV1RawSequenceHeader* seqHeader = bitstreamCtx->sequence_header;
|
||||
if (seqHeader == NULL) {
|
||||
Log(LOG_E, @"AV1 sequence header not found in IDR frame!");
|
||||
ff_cbs_fragment_free(&cbsFrag);
|
||||
ff_cbs_close(&cbsCtx);
|
||||
return nil;
|
||||
}
|
||||
|
||||
switch (seqHeader->color_config.color_primaries) {
|
||||
case 1: // CP_BT_709
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
|
||||
kCMFormatDescriptionColorPrimaries_ITU_R_709_2);
|
||||
break;
|
||||
|
||||
case 6: // CP_BT_601
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
|
||||
kCMFormatDescriptionColorPrimaries_SMPTE_C);
|
||||
break;
|
||||
|
||||
case 9: // CP_BT_2020
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ColorPrimaries,
|
||||
kCMFormatDescriptionColorPrimaries_ITU_R_2020);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported color_primaries value: %d", seqHeader->color_config.color_primaries);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (seqHeader->color_config.transfer_characteristics) {
|
||||
case 1: // TC_BT_709
|
||||
case 6: // TC_BT_601
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_ITU_R_709_2);
|
||||
break;
|
||||
|
||||
case 7: // TC_SMPTE_240
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_SMPTE_240M_1995);
|
||||
break;
|
||||
|
||||
case 8: // TC_LINEAR
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_Linear);
|
||||
break;
|
||||
|
||||
case 14: // TC_BT_2020_10_BIT
|
||||
case 15: // TC_BT_2020_12_BIT
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_ITU_R_2020);
|
||||
break;
|
||||
|
||||
case 16: // TC_SMPTE_2084
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ);
|
||||
break;
|
||||
|
||||
case 17: // TC_HLG
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_TransferFunction,
|
||||
kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported transfer_characteristics value: %d", seqHeader->color_config.transfer_characteristics);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (seqHeader->color_config.matrix_coefficients) {
|
||||
case 1: // MC_BT_709
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2);
|
||||
break;
|
||||
|
||||
case 6: // MC_BT_601
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4);
|
||||
break;
|
||||
|
||||
case 7: // MC_SMPTE_240
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995);
|
||||
break;
|
||||
|
||||
case 9: // MC_BT_2020_NCL
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_YCbCrMatrix,
|
||||
kCMFormatDescriptionYCbCrMatrix_ITU_R_2020);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported matrix_coefficients value: %d", seqHeader->color_config.matrix_coefficients);
|
||||
break;
|
||||
}
|
||||
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_FullRangeVideo, @(seqHeader->color_config.color_range == 1));
|
||||
|
||||
// Progressive content
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_FieldCount, @(1));
|
||||
|
||||
switch (seqHeader->color_config.chroma_sample_position) {
|
||||
case 1: // CSP_VERTICAL
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ChromaLocationTopField,
|
||||
kCMFormatDescriptionChromaLocation_Left);
|
||||
break;
|
||||
|
||||
case 2: // CSP_COLOCATED
|
||||
SET_CFSTR_EXTENSION(kCMFormatDescriptionExtension_ChromaLocationTopField,
|
||||
kCMFormatDescriptionChromaLocation_TopLeft);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log(LOG_W, @"Unsupported chroma_sample_position value: %d", seqHeader->color_config.chroma_sample_position);
|
||||
break;
|
||||
}
|
||||
|
||||
if (contentLightLevelInfo) {
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_ContentLightLevelInfo, contentLightLevelInfo);
|
||||
}
|
||||
|
||||
if (masteringDisplayColorVolume) {
|
||||
SET_EXTENSION(kCMFormatDescriptionExtension_MasteringDisplayColorVolume, masteringDisplayColorVolume);
|
||||
}
|
||||
|
||||
// Referenced the VP9 code in Chrome that performs a similar function
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/mac/vt_config_util.mm;drc=977dc02c431b4979e34c7792bc3d646f649dacb4;l=155
|
||||
extensions[(__bridge NSString*)kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms] =
|
||||
@{
|
||||
@"av1C" : [self getAv1CodecConfigurationBox:frameData],
|
||||
};
|
||||
extensions[@"BitsPerComponent"] = @(bitstreamCtx->bit_depth);
|
||||
|
||||
#undef SET_EXTENSION
|
||||
#undef SET_CFSTR_EXTENSION
|
||||
|
||||
// AV1 doesn't have a special format description function like H.264 and HEVC have, so we just use the generic one
|
||||
CMVideoFormatDescriptionRef formatDesc = NULL;
|
||||
OSStatus status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_AV1,
|
||||
bitstreamCtx->frame_width, bitstreamCtx->frame_height,
|
||||
(__bridge CFDictionaryRef)extensions,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create AV1 format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
ff_cbs_fragment_free(&cbsFrag);
|
||||
ff_cbs_close(&cbsCtx);
|
||||
return formatDesc;
|
||||
}
|
||||
|
||||
// This function must free data for bufferType == BUFFER_TYPE_PICDATA
|
||||
- (int)submitDecodeBuffer:(unsigned char *)data length:(int)length bufferType:(int)bufferType decodeUnit:(PDECODE_UNIT)du
|
||||
{
|
||||
OSStatus status;
|
||||
|
||||
// Construct a new format description object each time we receive an IDR frame
|
||||
if (du->frameType == FRAME_TYPE_IDR) {
|
||||
if (bufferType != BUFFER_TYPE_PICDATA) {
|
||||
if (bufferType == BUFFER_TYPE_VPS || bufferType == BUFFER_TYPE_SPS || bufferType == BUFFER_TYPE_PPS) {
|
||||
// Add new parameter set into the parameter set array
|
||||
int startLen = data[2] == 0x01 ? 3 : 4;
|
||||
[parameterSetBuffers addObject:[NSData dataWithBytes:&data[startLen] length:length - startLen]];
|
||||
}
|
||||
|
||||
// Data is NOT to be freed here. It's a direct usage of the caller's buffer.
|
||||
|
||||
// No frame data to submit for these NALUs
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
// Create the new format description when we get the first picture data buffer of an IDR frame.
|
||||
// This is the only way we know that there is no more CSD for this frame.
|
||||
//
|
||||
// NB: This logic depends on the fact that we submit all picture data in one buffer!
|
||||
|
||||
// Free the old format description
|
||||
if (formatDesc != NULL) {
|
||||
CFRelease(formatDesc);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
if (videoFormat & VIDEO_FORMAT_MASK_H264) {
|
||||
// Construct parameter set arrays for the format description
|
||||
size_t parameterSetCount = [parameterSetBuffers count];
|
||||
const uint8_t* parameterSetPointers[parameterSetCount];
|
||||
size_t parameterSetSizes[parameterSetCount];
|
||||
for (int i = 0; i < parameterSetCount; i++) {
|
||||
NSData* parameterSet = parameterSetBuffers[i];
|
||||
parameterSetPointers[i] = parameterSet.bytes;
|
||||
parameterSetSizes[i] = parameterSet.length;
|
||||
}
|
||||
|
||||
Log(LOG_I, @"Constructing new H264 format description");
|
||||
status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
|
||||
parameterSetCount,
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
&formatDesc);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create H264 format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
// Free parameter set buffers after submission
|
||||
[parameterSetBuffers removeAllObjects];
|
||||
}
|
||||
else if (videoFormat & VIDEO_FORMAT_MASK_H265) {
|
||||
// Construct parameter set arrays for the format description
|
||||
size_t parameterSetCount = [parameterSetBuffers count];
|
||||
const uint8_t* parameterSetPointers[parameterSetCount];
|
||||
size_t parameterSetSizes[parameterSetCount];
|
||||
for (int i = 0; i < parameterSetCount; i++) {
|
||||
NSData* parameterSet = parameterSetBuffers[i];
|
||||
parameterSetPointers[i] = parameterSet.bytes;
|
||||
parameterSetSizes[i] = parameterSet.length;
|
||||
}
|
||||
|
||||
Log(LOG_I, @"Constructing new HEVC format description");
|
||||
|
||||
NSMutableDictionary* videoFormatParams = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if (contentLightLevelInfo) {
|
||||
[videoFormatParams setObject:contentLightLevelInfo forKey:(__bridge NSString*)kCMFormatDescriptionExtension_ContentLightLevelInfo];
|
||||
}
|
||||
|
||||
if (masteringDisplayColorVolume) {
|
||||
[videoFormatParams setObject:masteringDisplayColorVolume forKey:(__bridge NSString*)kCMFormatDescriptionExtension_MasteringDisplayColorVolume];
|
||||
}
|
||||
|
||||
status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,
|
||||
parameterSetCount,
|
||||
parameterSetPointers,
|
||||
parameterSetSizes,
|
||||
NAL_LENGTH_PREFIX_SIZE,
|
||||
(__bridge CFDictionaryRef)videoFormatParams,
|
||||
&formatDesc);
|
||||
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"Failed to create HEVC format description: %d", (int)status);
|
||||
formatDesc = NULL;
|
||||
}
|
||||
|
||||
// Free parameter set buffers after submission
|
||||
[parameterSetBuffers removeAllObjects];
|
||||
}
|
||||
else if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
|
||||
NSData* fullFrameData = [NSData dataWithBytesNoCopy:data length:length freeWhenDone:NO];
|
||||
|
||||
Log(LOG_I, @"Constructing new AV1 format description");
|
||||
formatDesc = [self createAV1FormatDescriptionForIDRFrame:fullFrameData];
|
||||
}
|
||||
else {
|
||||
// Unsupported codec!
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (formatDesc == NULL) {
|
||||
// Can't decode if we haven't gotten our parameter sets yet
|
||||
free(data);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// Check for previous decoder errors before doing anything
|
||||
if (displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
Log(LOG_E, @"Display layer rendering failed: %@", displayLayer.error);
|
||||
|
||||
// Recreate the display layer. We are already on the main thread,
|
||||
// so this is safe to do right here.
|
||||
[self reinitializeDisplayLayer];
|
||||
|
||||
// Request an IDR frame to initialize the new decoder
|
||||
free(data);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// Now we're decoding actual frame data here
|
||||
CMBlockBufferRef frameBlockBuffer;
|
||||
CMBlockBufferRef dataBlockBuffer;
|
||||
|
||||
status = CMBlockBufferCreateWithMemoryBlock(NULL, data, length, kCFAllocatorDefault, NULL, 0, length, 0, &dataBlockBuffer);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferCreateWithMemoryBlock failed: %d", (int)status);
|
||||
free(data);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// From now on, CMBlockBuffer owns the data pointer and will free it when it's dereferenced
|
||||
|
||||
status = CMBlockBufferCreateEmpty(NULL, 0, 0, &frameBlockBuffer);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferCreateEmpty failed: %d", (int)status);
|
||||
CFRelease(dataBlockBuffer);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// H.264 and HEVC formats require NAL prefix fixups from Annex B to length-delimited
|
||||
if (videoFormat & (VIDEO_FORMAT_MASK_H264 | VIDEO_FORMAT_MASK_H265)) {
|
||||
int lastOffset = -1;
|
||||
for (int i = 0; i < length - NALU_START_PREFIX_SIZE; i++) {
|
||||
// Search for a NALU
|
||||
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1) {
|
||||
// It's the start of a new NALU
|
||||
if (lastOffset != -1) {
|
||||
// We've seen a start before this so enqueue that NALU
|
||||
[self updateAnnexBBufferForRange:frameBlockBuffer dataBlock:dataBlockBuffer offset:lastOffset length:i - lastOffset];
|
||||
}
|
||||
|
||||
lastOffset = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOffset != -1) {
|
||||
// Enqueue the remaining data
|
||||
[self updateAnnexBBufferForRange:frameBlockBuffer dataBlock:dataBlockBuffer offset:lastOffset length:length - lastOffset];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For formats that require no length-changing fixups, just append a reference to the raw data block
|
||||
status = CMBlockBufferAppendBufferReference(frameBlockBuffer, dataBlockBuffer, 0, length, 0);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMBlockBufferAppendBufferReference failed: %d", (int)status);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
}
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
|
||||
CMSampleTimingInfo sampleTiming = {kCMTimeInvalid, CMTimeMake(du->presentationTimeMs, 1000), kCMTimeInvalid};
|
||||
|
||||
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
|
||||
frameBlockBuffer,
|
||||
formatDesc, 1, 1,
|
||||
&sampleTiming, 0, NULL,
|
||||
&sampleBuffer);
|
||||
if (status != noErr) {
|
||||
Log(LOG_E, @"CMSampleBufferCreate failed: %d", (int)status);
|
||||
CFRelease(dataBlockBuffer);
|
||||
CFRelease(frameBlockBuffer);
|
||||
return DR_NEED_IDR;
|
||||
}
|
||||
|
||||
// Enqueue the next frame
|
||||
[self->displayLayer enqueueSampleBuffer:sampleBuffer];
|
||||
|
||||
if (du->frameType == FRAME_TYPE_IDR) {
|
||||
// Ensure the layer is visible now
|
||||
self->displayLayer.hidden = NO;
|
||||
|
||||
// Tell our parent VC to hide the progress indicator
|
||||
[self->_callbacks videoContentShown];
|
||||
}
|
||||
|
||||
// Dereference the buffers
|
||||
CFRelease(dataBlockBuffer);
|
||||
CFRelease(frameBlockBuffer);
|
||||
CFRelease(sampleBuffer);
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
- (void)setHdrMode:(BOOL)enabled {
|
||||
SS_HDR_METADATA hdrMetadata;
|
||||
|
||||
BOOL hasMetadata = enabled && LiGetHdrMetadata(&hdrMetadata);
|
||||
BOOL metadataChanged = NO;
|
||||
|
||||
if (hasMetadata && hdrMetadata.displayPrimaries[0].x != 0 && hdrMetadata.maxDisplayLuminance != 0) {
|
||||
// This data is all in big-endian
|
||||
struct {
|
||||
vector_ushort2 primaries[3];
|
||||
vector_ushort2 white_point;
|
||||
uint32_t luminance_max;
|
||||
uint32_t luminance_min;
|
||||
} __attribute__((packed, aligned(4))) mdcv;
|
||||
|
||||
// mdcv is in GBR order while SS_HDR_METADATA is in RGB order
|
||||
mdcv.primaries[0].x = __builtin_bswap16(hdrMetadata.displayPrimaries[1].x);
|
||||
mdcv.primaries[0].y = __builtin_bswap16(hdrMetadata.displayPrimaries[1].y);
|
||||
mdcv.primaries[1].x = __builtin_bswap16(hdrMetadata.displayPrimaries[2].x);
|
||||
mdcv.primaries[1].y = __builtin_bswap16(hdrMetadata.displayPrimaries[2].y);
|
||||
mdcv.primaries[2].x = __builtin_bswap16(hdrMetadata.displayPrimaries[0].x);
|
||||
mdcv.primaries[2].y = __builtin_bswap16(hdrMetadata.displayPrimaries[0].y);
|
||||
|
||||
mdcv.white_point.x = __builtin_bswap16(hdrMetadata.whitePoint.x);
|
||||
mdcv.white_point.y = __builtin_bswap16(hdrMetadata.whitePoint.y);
|
||||
|
||||
// These luminance values are in 10000ths of a nit
|
||||
mdcv.luminance_max = __builtin_bswap32((uint32_t)hdrMetadata.maxDisplayLuminance * 10000);
|
||||
mdcv.luminance_min = __builtin_bswap32(hdrMetadata.minDisplayLuminance);
|
||||
|
||||
NSData* newMdcv = [NSData dataWithBytes:&mdcv length:sizeof(mdcv)];
|
||||
if (masteringDisplayColorVolume == nil || ![newMdcv isEqualToData:masteringDisplayColorVolume]) {
|
||||
masteringDisplayColorVolume = newMdcv;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
}
|
||||
else if (masteringDisplayColorVolume != nil) {
|
||||
masteringDisplayColorVolume = nil;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
|
||||
if (hasMetadata && hdrMetadata.maxContentLightLevel != 0 && hdrMetadata.maxFrameAverageLightLevel != 0) {
|
||||
// This data is all in big-endian
|
||||
struct {
|
||||
uint16_t max_content_light_level;
|
||||
uint16_t max_frame_average_light_level;
|
||||
} __attribute__((packed, aligned(2))) cll;
|
||||
|
||||
cll.max_content_light_level = __builtin_bswap16(hdrMetadata.maxContentLightLevel);
|
||||
cll.max_frame_average_light_level = __builtin_bswap16(hdrMetadata.maxFrameAverageLightLevel);
|
||||
|
||||
NSData* newCll = [NSData dataWithBytes:&cll length:sizeof(cll)];
|
||||
if (contentLightLevelInfo == nil || ![newCll isEqualToData:contentLightLevelInfo]) {
|
||||
contentLightLevelInfo = newCll;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
}
|
||||
else if (contentLightLevelInfo != nil) {
|
||||
contentLightLevelInfo = nil;
|
||||
metadataChanged = YES;
|
||||
}
|
||||
|
||||
// If the metadata changed, request an IDR frame to re-create the CMVideoFormatDescription
|
||||
if (metadataChanged) {
|
||||
LiRequestIdrFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// UIComputerView.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/22/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TemporaryHost.h"
|
||||
|
||||
@protocol HostCallback <NSObject>
|
||||
|
||||
- (void) hostClicked:(TemporaryHost*)host view:(UIView*)view;
|
||||
- (void) hostLongClicked:(TemporaryHost*)host view:(UIView*)view;
|
||||
- (void) addHostClicked;
|
||||
|
||||
@end
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@interface UIComputerView : UIButton <UIContextMenuInteractionDelegate>
|
||||
#else
|
||||
@interface UIComputerView : UIButton
|
||||
#endif
|
||||
|
||||
- (id) initWithComputer:(TemporaryHost*)host andCallback:(id<HostCallback>)callback;
|
||||
- (id) initForAddWithCallback:(id<HostCallback>)callback;
|
||||
|
||||
@end
|
||||
@@ -1,240 +0,0 @@
|
||||
//
|
||||
// UIComputerView.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/22/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIComputerView.h"
|
||||
#import "LocalizationHelper.h"
|
||||
|
||||
@implementation UIComputerView {
|
||||
TemporaryHost* _host;
|
||||
UIImageView* _hostIcon;
|
||||
UILabel* _hostLabel;
|
||||
UIImageView* _hostOverlay;
|
||||
UIActivityIndicatorView* _hostSpinner;
|
||||
id<HostCallback> _callback;
|
||||
CGSize _labelSize;
|
||||
}
|
||||
static const float REFRESH_CYCLE = 2.0f;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
static const int ITEM_PADDING = 50;
|
||||
static const int LABEL_DY = 40;
|
||||
#else
|
||||
static const int ITEM_PADDING = 0;
|
||||
static const int LABEL_DY = 20;
|
||||
#endif
|
||||
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
|
||||
#if TARGET_OS_TV
|
||||
self.frame = CGRectMake(0, 0, 400, 400);
|
||||
#else
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
self.frame = CGRectMake(0, 0, 200, 200);
|
||||
} else {
|
||||
self.frame = CGRectMake(0, 0, 100, 100);
|
||||
}
|
||||
#endif
|
||||
|
||||
_hostIcon = [[UIImageView alloc] initWithFrame:self.frame];
|
||||
[_hostIcon setImage:[UIImage imageNamed:@"Computer"]];
|
||||
|
||||
self.layer.shadowColor = [[UIColor blackColor] CGColor];
|
||||
self.layer.shadowOffset = CGSizeMake(5,8);
|
||||
self.layer.shadowOpacity = 0.3;
|
||||
|
||||
[self addTarget:self action:@selector(hostButtonSelected:) forControlEvents:UIControlEventTouchDown];
|
||||
[self addTarget:self action:@selector(hostButtonDeselected:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit];
|
||||
|
||||
_hostLabel = [[UILabel alloc] init];
|
||||
_hostLabel.textColor = [UIColor whiteColor];
|
||||
|
||||
_hostOverlay = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.size.width / 3, _hostIcon.frame.size.height / 4, _hostIcon.frame.size.width / 3, self.frame.size.height / 3)];
|
||||
_hostSpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
|
||||
[_hostSpinner setFrame:_hostOverlay.frame];
|
||||
_hostSpinner.userInteractionEnabled = NO;
|
||||
_hostSpinner.hidesWhenStopped = YES;
|
||||
|
||||
[self addSubview:_hostLabel];
|
||||
[self addSubview:_hostIcon];
|
||||
|
||||
#if TARGET_OS_TV
|
||||
_hostIcon.clipsToBounds = NO;
|
||||
_hostIcon.adjustsImageWhenAncestorFocused = YES;
|
||||
_hostIcon.masksFocusEffectToContents = YES;
|
||||
|
||||
self.adjustsImageWhenHighlighted = NO;
|
||||
|
||||
_hostOverlay.masksFocusEffectToContents = YES;
|
||||
_hostOverlay.adjustsImageWhenAncestorFocused = NO;
|
||||
|
||||
[_hostIcon.overlayContentView addSubview:_hostOverlay];
|
||||
[_hostIcon.overlayContentView addSubview:_hostSpinner];
|
||||
#else
|
||||
[self addSubview:_hostOverlay];
|
||||
[self addSubview:_hostSpinner];
|
||||
|
||||
if (@available(iOS 13.4.1, *)) {
|
||||
// Allow the button style to change when moused over
|
||||
self.pointerInteractionEnabled = YES;
|
||||
}
|
||||
#endif
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) hostButtonSelected:(id)sender {
|
||||
_hostIcon.layer.opacity = 0.5f;
|
||||
_hostSpinner.layer.opacity = 0.5f;
|
||||
_hostOverlay.layer.opacity = 0.5f;
|
||||
}
|
||||
- (void) hostButtonDeselected:(id)sender {
|
||||
_hostIcon.layer.opacity = 1.0f;
|
||||
_hostSpinner.layer.opacity = 1.0f;
|
||||
_hostOverlay.layer.opacity = 1.0f;
|
||||
}
|
||||
|
||||
- (id) initForAddWithCallback:(id<HostCallback>)callback {
|
||||
self = [self init];
|
||||
_callback = callback;
|
||||
|
||||
[self addTarget:self action:@selector(addClicked) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
|
||||
[_hostLabel setText:[LocalizationHelper localizedStringForKey:@"Add Host Manually"]];
|
||||
[_hostLabel sizeToFit];
|
||||
|
||||
[_hostOverlay setImage:[UIImage imageNamed:@"AddOverlayIcon"]];
|
||||
|
||||
[self updateBounds];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithComputer:(TemporaryHost*)host andCallback:(id<HostCallback>)callback {
|
||||
self = [self init];
|
||||
_host = host;
|
||||
_callback = callback;
|
||||
|
||||
// Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer
|
||||
// for tvOS devices and iOS prior to 13.0.
|
||||
#if !TARGET_OS_TV
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIContextMenuInteraction* rightClickInteraction = [[UIContextMenuInteraction alloc] initWithDelegate:self];
|
||||
[self addInteraction:rightClickInteraction];
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(hostLongClicked:)];
|
||||
[self addGestureRecognizer:longPressRecognizer];
|
||||
}
|
||||
|
||||
[self addTarget:self action:@selector(hostClicked) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
|
||||
[self updateContentsForHost:host];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didMoveToSuperview {
|
||||
// Start our update loop when we are added to our cell
|
||||
if (self.superview != nil && _host != nil) {
|
||||
[self updateLoop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateBounds {
|
||||
float x = FLT_MAX;
|
||||
float y = FLT_MAX;
|
||||
float width = 0;
|
||||
float height;
|
||||
|
||||
float iconX = _hostIcon.frame.origin.x + _hostIcon.frame.size.width / 2;
|
||||
_hostLabel.center = CGPointMake(iconX, _hostIcon.frame.origin.y + _hostIcon.frame.size.height + LABEL_DY);
|
||||
|
||||
x = MIN(x, _hostIcon.frame.origin.x);
|
||||
x = MIN(x, _hostLabel.frame.origin.x);
|
||||
|
||||
y = MIN(y, _hostIcon.frame.origin.y);
|
||||
y = MIN(y, _hostLabel.frame.origin.y);
|
||||
|
||||
width = MAX(width, _hostIcon.frame.size.width);
|
||||
width = MAX(width, _hostLabel.frame.size.width);
|
||||
|
||||
height = _hostIcon.frame.size.height +
|
||||
_hostLabel.frame.size.height +
|
||||
LABEL_DY / 2;
|
||||
|
||||
self.bounds = CGRectMake(x - ITEM_PADDING, y - ITEM_PADDING, width + 2 * ITEM_PADDING, height + 2 * ITEM_PADDING);
|
||||
}
|
||||
|
||||
- (void) updateContentsForHost:(TemporaryHost*)host {
|
||||
_hostLabel.text = _host.name;
|
||||
[_hostLabel sizeToFit];
|
||||
|
||||
if (host.state == StateOnline) {
|
||||
[_hostSpinner stopAnimating];
|
||||
|
||||
if (host.pairState == PairStateUnpaired) {
|
||||
[_hostOverlay setImage:[UIImage imageNamed:@"LockedOverlayIcon"]];
|
||||
}
|
||||
else {
|
||||
[_hostOverlay setImage:nil];
|
||||
}
|
||||
}
|
||||
else if (host.state == StateOffline) {
|
||||
[_hostSpinner stopAnimating];
|
||||
[_hostOverlay setImage:[UIImage imageNamed:@"ErrorOverlayIcon"]];
|
||||
}
|
||||
else {
|
||||
[_hostSpinner startAnimating];
|
||||
}
|
||||
|
||||
[self updateBounds];
|
||||
}
|
||||
|
||||
- (void) updateLoop {
|
||||
// Stop immediately if the view has been detached
|
||||
if (self.superview == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateContentsForHost:_host];
|
||||
|
||||
// Queue the next refresh cycle
|
||||
[self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
|
||||
}
|
||||
|
||||
- (void) hostLongClicked:(UILongPressGestureRecognizer*)gesture {
|
||||
if (gesture.state == UIGestureRecognizerStateBegan) {
|
||||
[_callback hostLongClicked:_host view:self];
|
||||
}
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
|
||||
configurationForMenuAtLocation:(CGPoint)location {
|
||||
// We don't want to trigger the primary action at this point, so cancel
|
||||
// tracking touch on this view now. This will also have the (intended)
|
||||
// effect of removing the touch highlight on this view.
|
||||
[self cancelTrackingWithEvent:nil];
|
||||
|
||||
[_callback hostLongClicked:_host view:self];
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void) hostClicked {
|
||||
[_callback hostClicked:_host view:self];
|
||||
}
|
||||
|
||||
- (void) addClicked {
|
||||
[_callback addHostClicked];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// Logger.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 2/10/15.
|
||||
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Limelight_Logger_h
|
||||
#define Limelight_Logger_h
|
||||
|
||||
#import <stdarg.h>
|
||||
|
||||
typedef enum {
|
||||
LOG_D,
|
||||
LOG_I,
|
||||
LOG_W,
|
||||
LOG_E
|
||||
} LogLevel;
|
||||
|
||||
#define PRFX_DEBUG @"<DEBUG>"
|
||||
#define PRFX_INFO @"<INFO>"
|
||||
#define PRFX_WARN @"<WARN>"
|
||||
#define PRFX_ERROR @"<ERROR>"
|
||||
|
||||
void Log(LogLevel level, NSString* fmt, ...);
|
||||
void LogTag(LogLevel level, NSString* tag, NSString* fmt, ...);
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
//
|
||||
// LayoutOnScreenControlsViewController.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Long Le on 9/27/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "LayoutOnScreenControls.h"
|
||||
#import "ToolBarContainerView.h"
|
||||
#import "OSCProfilesManager.h"
|
||||
#import "OSCProfilesTableViewController.h"
|
||||
#import "Moonlight-Swift.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
This view controller provides the user interface which allows the user to position on screen controller buttons anywhere they'd like on the screen. It also provides the user with the abilities to undo a change, save the on screen controller layout for later retrieval, and load previously saved controller layouts
|
||||
*/
|
||||
@interface LayoutOnScreenControlsViewController : UIViewController
|
||||
- (void)profileRefresh;
|
||||
- (void)reloadOnScreenWidgetViews;
|
||||
|
||||
@property LayoutOnScreenControls *layoutOSC; // object that contains a view which contains the on screen controller buttons that allows the user to drag and positions each button on the screen using touch
|
||||
@property (nonatomic) NSMutableSet* OnScreenWidgetViews;
|
||||
|
||||
@property int OSCSegmentSelected;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIButton *trashCanButton;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *undoButton;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet ToolBarContainerView *toolbarRootView;
|
||||
@property (weak, nonatomic) IBOutlet UIView *chevronView;
|
||||
@property (weak, nonatomic) IBOutlet UIImageView *chevronImageView;
|
||||
@property (weak, nonatomic) IBOutlet UIStackView *toolbarStackView;
|
||||
@property (strong, nonatomic) OSCProfilesTableViewController *oscProfilesTableViewController;
|
||||
@property (nonatomic, assign) NSString *currentProfileName;
|
||||
@property (strong, nonatomic) IBOutlet UILabel *currentProfileLabel;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *widgetSizeSlider;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *widgetHeightSlider;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *widgetAlphaSlider;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *sensitivityFactorSlider;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *stickIndicatorOffsetSlider;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *widgetBorderWidthSlider;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,878 +0,0 @@
|
||||
//
|
||||
// LayoutOnScreenControlsViewController.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Long Le on 9/27/22.
|
||||
// Copyright © 2022 Moonlight Game Streaming Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LayoutOnScreenControlsViewController.h"
|
||||
#import "OSCProfilesTableViewController.h"
|
||||
#import "OnScreenButtonState.h"
|
||||
//#import "OnScreenControls.h"
|
||||
#import "OSCProfilesManager.h"
|
||||
#import "LocalizationHelper.h"
|
||||
#import "Moonlight-Swift.h"
|
||||
|
||||
@interface LayoutOnScreenControlsViewController ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation LayoutOnScreenControlsViewController {
|
||||
BOOL isToolbarHidden;
|
||||
OSCProfilesManager* profilesManager;
|
||||
OnScreenWidgetView* selectedWidgetView;
|
||||
CALayer* selectedControllerLayer;
|
||||
CGRect controllerLoadedBounds;
|
||||
bool widgetViewSelected;
|
||||
bool controllerLayerSelected;
|
||||
__weak IBOutlet NSLayoutConstraint *toolbarTopConstraintiPhone;
|
||||
__weak IBOutlet NSLayoutConstraint *toolbarTopConstraintiPad;
|
||||
UILabel *widgetSizeSliderLabel;
|
||||
UILabel *widgetHeightSliderLabel;
|
||||
UILabel *widgetAlphaSliderLabel;
|
||||
UILabel *widgetBorderWidthSliderLabel;
|
||||
UILabel *sensitivitySliderLabel;
|
||||
UILabel *stickIndicatorOffsetSliderLabel;
|
||||
UILabel *stickIndicatorOffsetExplain;
|
||||
}
|
||||
|
||||
@synthesize trashCanButton;
|
||||
@synthesize undoButton;
|
||||
@synthesize OSCSegmentSelected;
|
||||
@synthesize toolbarRootView;
|
||||
@synthesize chevronView;
|
||||
@synthesize chevronImageView;
|
||||
|
||||
- (UIInterfaceOrientationMask)getCurrentOrientation{
|
||||
CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
|
||||
CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]);
|
||||
//lock the orientation accordingly after streaming is started
|
||||
if(screenWidthInPoints > screenHeightInPoints) return UIInterfaceOrientationMaskLandscape;
|
||||
else return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
||||
// Return the supported interface orientations acoordingly
|
||||
return [self getCurrentOrientation]; // 90 Degree rotation not allowed in streaming or app view
|
||||
}
|
||||
|
||||
- (void) viewWillDisappear:(BOOL)animated{
|
||||
OnScreenWidgetView.editMode = false;
|
||||
for (OnScreenWidgetView* widgetView in self.OnScreenWidgetViews){
|
||||
[widgetView.stickBallLayer removeFromSuperlayer];
|
||||
[widgetView.crossMarkLayer removeFromSuperlayer];
|
||||
}
|
||||
[super viewWillDisappear:animated];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"OscLayoutCloseNotification" object:self];
|
||||
}
|
||||
|
||||
- (void) reloadOnScreenWidgetViews {
|
||||
for (UIView *subview in self.view.subviews) {
|
||||
// 检查子视图是否是特定类型的实例
|
||||
if ([subview isKindOfClass:[OnScreenWidgetView class]]) {
|
||||
// 如果是,就添加到将要被移除的数组中
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
[self.OnScreenWidgetViews removeAllObjects];
|
||||
|
||||
NSLog(@"reload os Key here");
|
||||
|
||||
// _activeCustomOscButtonPositionDict will be updated every time when the osc profile is reloaded
|
||||
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
|
||||
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
|
||||
OnScreenButtonState* buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
|
||||
if(buttonState.buttonType == CustomOnScreenWidget){
|
||||
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithKeyString:buttonState.name keyLabel:buttonState.alias]; //reconstruct widgetView
|
||||
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
|
||||
widgetView.timestamp = buttonState.timestamp; // will be set as key in in the dict.
|
||||
widgetView.widthFactor = buttonState.widthFactor;
|
||||
widgetView.heightFactor = buttonState.heightFactor;
|
||||
widgetView.borderWidth = buttonState.borderWidth;
|
||||
widgetView.sensitivityFactor = buttonState.sensitivityFactor;
|
||||
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
|
||||
// widgetView.backgroundAlpha = buttonState.backgroundAlpha;
|
||||
// Add the widgetView to the view controller's view
|
||||
[self.view addSubview:widgetView];
|
||||
[widgetView setLocationWithXOffset:buttonState.position.x yOffset:buttonState.position.y];
|
||||
[widgetView resizeWidgetView]; // resize must be called after relocation
|
||||
[widgetView adjustTransparencyWithAlpha:buttonState.backgroundAlpha];
|
||||
[widgetView adjustBorderWithWidth:buttonState.borderWidth];
|
||||
[self.OnScreenWidgetViews addObject:widgetView];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void) viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
profilesManager = [OSCProfilesManager sharedManager];
|
||||
self.OnScreenWidgetViews = [[NSMutableSet alloc] init]; // will be revised to read persisted data , somewhere else
|
||||
[OSCProfilesManager setOnScreenWidgetViewsSet:self.OnScreenWidgetViews]; // pass the keyboard button dict to profiles manager
|
||||
|
||||
isToolbarHidden = NO; // keeps track if the toolbar is hidden up above the screen so that we know whether to hide or show it when the user taps the toolbar's hide/show button
|
||||
|
||||
widgetSizeSliderLabel = [[UILabel alloc] init];
|
||||
widgetHeightSliderLabel = [[UILabel alloc] init];
|
||||
widgetAlphaSliderLabel = [[UILabel alloc] init];
|
||||
widgetBorderWidthSliderLabel = [[UILabel alloc] init];
|
||||
sensitivitySliderLabel = [[UILabel alloc] init];
|
||||
stickIndicatorOffsetSliderLabel = [[UILabel alloc] init];
|
||||
stickIndicatorOffsetExplain = [[UILabel alloc] init];
|
||||
|
||||
/* add curve to bottom of chevron tab view */
|
||||
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.chevronView.bounds byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(10.0, 10.0)];
|
||||
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
|
||||
maskLayer.frame = self.view.bounds;
|
||||
maskLayer.path = maskPath.CGPath;
|
||||
self.chevronView.layer.mask = maskLayer;
|
||||
|
||||
/* Add swipe gesture to toolbar to allow user to swipe it up and off screen */
|
||||
UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(moveToolbar:)];
|
||||
swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
|
||||
[self.toolbarRootView addGestureRecognizer:swipeUp];
|
||||
|
||||
/* Add tap gesture to toolbar's chevron to allow user to tap it in order to move the toolbar on and off screen */
|
||||
UITapGestureRecognizer *singleFingerTap =
|
||||
[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(moveToolbar:)];
|
||||
[self.chevronView addGestureRecognizer:singleFingerTap];
|
||||
|
||||
self.layoutOSC = [[LayoutOnScreenControls alloc] initWithView:self.view controllerSup:nil streamConfig:nil oscLevel:OSCSegmentSelected];
|
||||
self.layoutOSC._level = OnScreenControlsLevelCustom;
|
||||
self.layoutOSC.layoutToolVC = self;
|
||||
[self.layoutOSC show]; // draw on screen controls
|
||||
|
||||
[self addInnerAnalogSticksToOuterAnalogLayers]; // allows inner and analog sticks to be dragged together around the screen together as one unit which is the expected behavior
|
||||
|
||||
self.undoButton.alpha = 0.3; // no changes to undo yet, so fade out the undo button a bit
|
||||
|
||||
if ([[profilesManager getAllProfiles] count] == 0) { // if no saved OSC profiles exist yet then create one called 'Default' and associate it with Moonlight's legacy 'Full' OSC layout that's already been laid out on the screen at this point
|
||||
[profilesManager saveProfileWithName:@"Default" andButtonLayers:self.layoutOSC.OSCButtonLayers];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(clearSelectedWidgetView)
|
||||
name:@"LegacyOscCALayerSelectedNotification"
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(profileRefresh)
|
||||
name:@"OscLayoutTableViewCloseNotification"
|
||||
object:nil];
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reloadOnScreenWidgetViews)
|
||||
name:@"OscLayoutProfileSelctedInTableView" // This is a special notification for reloading the on screen keyboard buttons. which can't be executed by _oscProfilesTableViewController.needToUpdateOscLayoutTVC code block, and has to be triggered by a notification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(widgetViewTapped:)
|
||||
name:@"OnScreenWidgetViewSelected"
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(setControllerCALayerSliderValues:)
|
||||
name:@"ControllerCALayerSelected"
|
||||
object:nil];
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OSCLayoutChanged) name:@"OSCLayoutChanged" object:nil]; // used to notifiy this view controller that the user made a change to the OSC layout so that the VC can either fade in or out its 'Undo button' which will signify to the user whether there are any OSC layout changes to undo
|
||||
|
||||
/* This will animate the toolbar with a subtle up and down motion intended to telegraph to the user that they can hide the toolbar if they wish*/
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
||||
[UIView animateWithDuration:0.3
|
||||
delay:0.25
|
||||
usingSpringWithDamping:0.8
|
||||
initialSpringVelocity:0.5
|
||||
options:UIViewAnimationOptionCurveEaseInOut animations:^{ // Animate toolbar up a a very small distance. Note the 0.35 time delay is necessary to avoid a bug that keeps animations from playing if the animation is presented immediately on a modally presented VC
|
||||
self.toolbarRootView.frame = CGRectMake(self.toolbarRootView.frame.origin.x, self.toolbarRootView.frame.origin.y - 25, self.toolbarRootView.frame.size.width, self.toolbarRootView.frame.size.height);
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:0.3
|
||||
delay:0
|
||||
usingSpringWithDamping:0.7
|
||||
initialSpringVelocity:1.0
|
||||
options:UIViewAnimationOptionCurveEaseIn animations:^{ // Animate the toolbar back down that same distance
|
||||
self.toolbarRootView.frame = CGRectMake(self.toolbarRootView.frame.origin.x, self.toolbarRootView.frame.origin.y + 25, self.toolbarRootView.frame.size.width, self.toolbarRootView.frame.size.height);
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
NSLog (@"done");
|
||||
}];
|
||||
}];
|
||||
});
|
||||
[self profileRefresh];
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void) viewDidAppear:(BOOL)animated {
|
||||
OnScreenWidgetView.editMode = true;
|
||||
[super viewWillAppear:animated];
|
||||
[self profileRefresh];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Class Helper Functions
|
||||
|
||||
/* fades the 'Undo Button' in or out depending on whether the user has any OSC layout changes to undo */
|
||||
- (void) OSCLayoutChanged {
|
||||
if ([self.layoutOSC.layoutChanges count] > 0) {
|
||||
self.undoButton.alpha = 1.0;
|
||||
}
|
||||
else {
|
||||
self.undoButton.alpha = 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
/* animates the toolbar up and off the screen or back down onto the screen */
|
||||
- (void) moveToolbar:(UISwipeGestureRecognizer *)sender {
|
||||
BOOL isPad = [[UIDevice currentDevice].model hasPrefix:@"iPad"];
|
||||
NSLayoutConstraint *toolbarTopConstraint = isPad ? self->toolbarTopConstraintiPad : self->toolbarTopConstraintiPhone;
|
||||
if (isToolbarHidden == NO) {
|
||||
[UIView animateWithDuration:0.2 animations:^{ // animates toolbar up and off screen
|
||||
toolbarTopConstraint.constant -= self.toolbarRootView.frame.size.height;
|
||||
[self.view layoutIfNeeded];
|
||||
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
self->isToolbarHidden = YES;
|
||||
self.chevronImageView.image = [UIImage imageNamed:@"ChevronCompactDown"];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else {
|
||||
[UIView animateWithDuration:0.2 animations:^{ // animates the toolbar back down into the screen
|
||||
toolbarTopConstraint.constant += self.toolbarRootView.frame.size.height;
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
self->isToolbarHidden = NO;
|
||||
self.chevronImageView.image = [UIImage imageNamed:@"ChevronCompactUp"];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the inner analog stick layers a child layer of its corresponding outer analog stick layers so that both the inner and its corresponding outer layers move together when the user drags them around the screen as is the expected behavior when laying out OSC. Note that this is NOT expected behavior on the game stream view where the inner analog sticks move to follow toward the user's touch and their corresponding outer analog stick layers do not move
|
||||
*/
|
||||
- (void)addInnerAnalogSticksToOuterAnalogLayers {
|
||||
// right stick
|
||||
[self.layoutOSC._rightStickBackground addSublayer: self.layoutOSC._rightStick];
|
||||
self.layoutOSC._rightStick.position = CGPointMake(self.layoutOSC._rightStickBackground.frame.size.width / 2, self.layoutOSC._rightStickBackground.frame.size.height / 2);
|
||||
|
||||
// left stick
|
||||
[self.layoutOSC._leftStickBackground addSublayer: self.layoutOSC._leftStick];
|
||||
self.layoutOSC._leftStick.position = CGPointMake(self.layoutOSC._leftStickBackground.frame.size.width / 2, self.layoutOSC._leftStickBackground.frame.size.height / 2);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIButton Actions
|
||||
|
||||
- (IBAction) closeTapped:(id)sender {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction) trashCanTapped:(id)sender {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Delete Buttons Here"] message:[LocalizationHelper localizedStringForKey:@"Drag and drop buttons onto this trash can to remove them from the interface"] preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction *ok = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil];
|
||||
[alert addAction:ok];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (IBAction) undoTapped:(id)sender {
|
||||
if ([self.layoutOSC.layoutChanges count] > 0) { // check if there are layout changes to roll back to
|
||||
OnScreenButtonState *buttonState = [self.layoutOSC.layoutChanges lastObject]; // Get the 'OnScreenButtonState' object that contains the name, position, and visiblity state of the button the user last moved
|
||||
|
||||
CALayer *buttonLayer = [self.layoutOSC controllerLayerFromName:buttonState.name]; // get the on screen button layer that corresponds with the 'OnScreenButtonState' object that we retrieved above
|
||||
|
||||
/* Set the button's position and visiblity to what it was before the user last moved it */
|
||||
buttonLayer.position = buttonState.position;
|
||||
buttonLayer.hidden = buttonState.isHidden;
|
||||
|
||||
/* if user is showing or hiding dPad, then show or hide all four dPad button child layers as well since setting the 'hidden' property on the parent CALayer is not automatically setting the individual dPad child CALayers */
|
||||
if ([buttonLayer.name isEqualToString:@"dPad"]) {
|
||||
self.layoutOSC._upButton.hidden = buttonState.isHidden;
|
||||
self.layoutOSC._rightButton.hidden = buttonState.isHidden;
|
||||
self.layoutOSC._downButton.hidden = buttonState.isHidden;
|
||||
self.layoutOSC._leftButton.hidden = buttonState.isHidden;
|
||||
}
|
||||
|
||||
/* if user is showing or hiding the left or right analog sticks, then show or hide their corresponding inner analog stick child layers as well since setting the 'hidden' property on the parent analog stick doesn't automatically hide its child inner analog stick CALayer */
|
||||
if ([buttonLayer.name isEqualToString:@"leftStickBackground"]) {
|
||||
self.layoutOSC._leftStick.hidden = buttonState.isHidden;
|
||||
}
|
||||
if ([buttonLayer.name isEqualToString:@"rightStickBackground"]) {
|
||||
self.layoutOSC._rightStick.hidden = buttonState.isHidden;
|
||||
}
|
||||
|
||||
[self.layoutOSC.layoutChanges removeLastObject];
|
||||
|
||||
[self OSCLayoutChanged]; // will fade the undo button in or out depending on whether there are any further changes to undo
|
||||
}
|
||||
else { // there are no changes to undo. let user know there are no changes to undo
|
||||
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [LocalizationHelper localizedStringForKey:@"Nothing to Undo"] message: [LocalizationHelper localizedStringForKey: @"There are no changes to undo"] preferredStyle:UIAlertControllerStyleAlert];
|
||||
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[savedAlertController dismissViewControllerAnimated:NO completion:nil];
|
||||
}]];
|
||||
[self presentViewController:savedAlertController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction) addTapped:(id)sender{
|
||||
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"New On-Screen Widget"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"Enter the command & alias label"]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.placeholder = [LocalizationHelper localizedStringForKey:@"Command"];
|
||||
textField.keyboardType = UIKeyboardTypeASCIICapable;
|
||||
textField.autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
textField.spellCheckingType = UITextSpellCheckingTypeNo;
|
||||
}];
|
||||
|
||||
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.placeholder = [LocalizationHelper localizedStringForKey:@"Alias label (optional)"];
|
||||
textField.keyboardType = UIKeyboardTypeASCIICapable;
|
||||
textField.autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
textField.spellCheckingType = UITextSpellCheckingTypeNo;
|
||||
}];
|
||||
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:nil];
|
||||
|
||||
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction *action) {
|
||||
/*
|
||||
alertController.textFields[0].keyboardType = UIKeyboardTypeASCIICapable;
|
||||
alertController.textFields[0].autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
alertController.textFields[0].spellCheckingType = UITextSpellCheckingTypeNo;
|
||||
alertController.textFields[1].keyboardType = UIKeyboardTypeASCIICapable;
|
||||
alertController.textFields[1].autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
alertController.textFields[1].spellCheckingType = UITextSpellCheckingTypeNo;*/
|
||||
|
||||
NSString *cmdString = [alertController.textFields[0].text uppercaseString]; // convert to uppercase
|
||||
NSString *keyLabel = alertController.textFields[1].text;
|
||||
if([keyLabel isEqualToString:@""]) keyLabel = [[cmdString lowercaseString] capitalizedString];
|
||||
bool noValidKeyboardString = [CommandManager.shared extractKeyStringsFromComboCommandFrom:cmdString] == nil; // this is a invalid string.
|
||||
bool noValidSuperComboButtonString = [CommandManager.shared extractKeyStringsFromComboKeysFrom:cmdString] == nil; // this is a invalid string.
|
||||
bool noValidMouseButtonString = ![CommandManager.mouseButtonMappings.allKeys containsObject:cmdString];
|
||||
bool noValidTouchPadString = ![CommandManager.touchPadCmds containsObject:cmdString];
|
||||
bool noValidOscButtonString = ![CommandManager.oscButtonMappings.allKeys containsObject:cmdString];
|
||||
bool noValidSpecialButtonString = ![CommandManager.specialOverlayButtonCmds containsObject:cmdString];
|
||||
|
||||
if(noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString && noValidSuperComboButtonString) return;
|
||||
|
||||
//saving & present the keyboard button:
|
||||
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithKeyString:cmdString keyLabel:keyLabel];
|
||||
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
|
||||
widgetView.timestamp = CACurrentMediaTime(); // will be set as key in in the dict.
|
||||
[self.OnScreenWidgetViews addObject:widgetView];
|
||||
// Add the widgetView to the view controller's view
|
||||
[self.view addSubview:widgetView];
|
||||
[widgetView setLocationWithXOffset:50 yOffset:50];
|
||||
[widgetView resizeWidgetView];
|
||||
}];
|
||||
[alertController addAction:cancelAction];
|
||||
[alertController addAction:okAction];
|
||||
[self presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* show pop up notification that lets users choose to save the current OSC layout configuration as a profile they can load when they want. User can also choose to cancel out of this pop up */
|
||||
- (IBAction) saveTapped:(id)sender {
|
||||
|
||||
if([self->profilesManager updateSelectedProfile:self.layoutOSC.OSCButtonLayers]){
|
||||
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Current profile updated successfully"] preferredStyle:UIAlertControllerStyleAlert];
|
||||
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
}]];
|
||||
[self presentViewController:savedAlertController animated:YES completion:nil];
|
||||
}
|
||||
else{
|
||||
UIAlertController * savedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Profile Default can not be overwritten"] preferredStyle:UIAlertControllerStyleAlert];
|
||||
[savedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self.oscProfilesTableViewController profileViewRefresh]; // execute this will reset layout in OSC tool!
|
||||
}]];
|
||||
[self presentViewController:savedAlertController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearSelectedWidgetView{
|
||||
selectedWidgetView = nil;
|
||||
}
|
||||
|
||||
- (void)widgetViewTapped: (NSNotification *)notification{
|
||||
// receive the selected widgetView obj passed from the notification
|
||||
OnScreenWidgetView* widgetView = (OnScreenWidgetView* )notification.object;
|
||||
self->widgetViewSelected = true;
|
||||
self->controllerLayerSelected = false;
|
||||
self->selectedWidgetView = widgetView;
|
||||
// setup slider values
|
||||
[self.widgetSizeSlider setValue: self->selectedWidgetView.widthFactor];
|
||||
[self.widgetHeightSlider setValue: self->selectedWidgetView.heightFactor];
|
||||
[self.widgetAlphaSlider setValue: self->selectedWidgetView.backgroundAlpha];
|
||||
[self.widgetBorderWidthSlider setValue:self->selectedWidgetView.borderWidth];
|
||||
|
||||
NSSet *stickAndMouseTouchpads = [NSSet setWithObjects:@"LSPAD", @"RSPAD", @"LSVPAD", @"RSVPAD", @"MOUSEPAD", nil];
|
||||
NSSet *nonVectorStickPads = [NSSet setWithObjects:@"LSPAD", @"RSPAD", nil];
|
||||
|
||||
|
||||
bool showSensitivityFactorSlider = [stickAndMouseTouchpads containsObject:self->selectedWidgetView.keyString];
|
||||
bool showStickIndicatorOffsetSlider = [nonVectorStickPads containsObject:self->selectedWidgetView.keyString];
|
||||
self.sensitivityFactorSlider.hidden = self->sensitivitySliderLabel.hidden = !showSensitivityFactorSlider;
|
||||
self.stickIndicatorOffsetSlider.hidden = self->stickIndicatorOffsetExplain.hidden = self->stickIndicatorOffsetSliderLabel.hidden = !showStickIndicatorOffsetSlider;
|
||||
if(showSensitivityFactorSlider){
|
||||
[self.sensitivityFactorSlider setValue:self->selectedWidgetView.sensitivityFactor];
|
||||
[sensitivitySliderLabel setText:[LocalizationHelper localizedStringForKey:@"Sensitivity: %.2f", self->selectedWidgetView.sensitivityFactor]];
|
||||
}
|
||||
if(showStickIndicatorOffsetSlider){
|
||||
// illustrating the indicator offset,
|
||||
[selectedWidgetView.stickBallLayer removeFromSuperlayer];
|
||||
[selectedWidgetView.crossMarkLayer removeFromSuperlayer];
|
||||
selectedWidgetView.touchBeganLocation = CGPointMake(CGRectGetWidth(selectedWidgetView.frame)/2, CGRectGetHeight(selectedWidgetView.frame)/4);
|
||||
[selectedWidgetView showStickIndicator];// this will create the indicator CAShapeLayers
|
||||
[self.stickIndicatorOffsetSlider setValue:self->selectedWidgetView.stickIndicatorOffset];
|
||||
[stickIndicatorOffsetSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Indicator Offset: %.2f", self->selectedWidgetView.stickIndicatorOffset]];
|
||||
[self->selectedWidgetView updateStickIndicator];
|
||||
}
|
||||
[widgetSizeSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Size: %.2f", self->selectedWidgetView.widthFactor]];
|
||||
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", self->selectedWidgetView.heightFactor]];
|
||||
[widgetAlphaSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Alpha: %.2f", self->selectedWidgetView.backgroundAlpha]];
|
||||
[widgetBorderWidthSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", self->selectedWidgetView.borderWidth]];
|
||||
}
|
||||
|
||||
- (void)setControllerCALayerSliderValues: (NSNotification *)notification{
|
||||
// receive the selected widgetView obj passed from the notification
|
||||
CALayer* controllerLayer = (CALayer* )notification.object;
|
||||
self->widgetViewSelected = false;
|
||||
self->stickIndicatorOffsetExplain.hidden = true;
|
||||
self->stickIndicatorOffsetSliderLabel.hidden = true;
|
||||
self.stickIndicatorOffsetSlider.hidden = true;
|
||||
self->sensitivitySliderLabel.hidden = true;
|
||||
self.sensitivityFactorSlider.hidden = true;
|
||||
|
||||
self->controllerLayerSelected = true;
|
||||
self->selectedControllerLayer = controllerLayer;
|
||||
self->controllerLoadedBounds = controllerLayer.bounds;
|
||||
|
||||
// setup slider values
|
||||
CGFloat sizeFactor = [OnScreenControls getControllerLayerSizeFactor:controllerLayer]; // calculated sizeFactor from loaded layer bounds.
|
||||
[self.widgetSizeSlider setValue:sizeFactor];
|
||||
[self.widgetHeightSlider setValue:sizeFactor];
|
||||
CGFloat alpha = [self.layoutOSC getControllerLayerOpacity:controllerLayer];
|
||||
[self.widgetAlphaSlider setValue:alpha];
|
||||
|
||||
[widgetSizeSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Size: %.2f", sizeFactor]];
|
||||
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", sizeFactor]];
|
||||
[widgetAlphaSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Alpha: %.2f", alpha]];
|
||||
}
|
||||
|
||||
- (void)widgetSizeSliderMoved{
|
||||
[widgetSizeSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Size: %.2f", self.widgetSizeSlider.value]];
|
||||
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", self.widgetSizeSlider.value]]; // resizing the whole button
|
||||
[self.widgetHeightSlider setValue: self.widgetSizeSlider.value];
|
||||
|
||||
if(self->selectedWidgetView != nil && self->widgetViewSelected){
|
||||
self->selectedWidgetView.translatesAutoresizingMaskIntoConstraints = true; // this is mandatory to prevent unexpected key view location change
|
||||
// when adjusting width, the widgetView height will be syncronized
|
||||
self->selectedWidgetView.widthFactor = self->selectedWidgetView.heightFactor = self.widgetSizeSlider.value;
|
||||
[self->selectedWidgetView resizeWidgetView];
|
||||
}
|
||||
if(self->selectedControllerLayer != nil && self->controllerLayerSelected){
|
||||
[self.layoutOSC resizeControllerLayerWith:self->selectedControllerLayer and:self.widgetSizeSlider.value];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)widgetHeightSliderMoved{
|
||||
[widgetHeightSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Height: %.2f", self.widgetHeightSlider.value]];
|
||||
if(self->selectedWidgetView != nil && self->widgetViewSelected){
|
||||
self->selectedWidgetView.translatesAutoresizingMaskIntoConstraints = true; // this is mandatory to prevent unexpected key view location change
|
||||
if([CommandManager.oscButtonMappings.allKeys containsObject:selectedWidgetView.keyString] && ![CommandManager.oscRectangleButtonCmds containsObject:selectedWidgetView.keyString]) return; // don't change custom osc button height, except for dPad buttons which are in rectangle shape
|
||||
self->selectedWidgetView.heightFactor = self.widgetHeightSlider.value;
|
||||
[self->selectedWidgetView resizeWidgetView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)widgetAlphaSliderMoved{
|
||||
[widgetAlphaSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Widget Alpha: %.2f", self.widgetAlphaSlider.value]];
|
||||
if(self->selectedWidgetView != nil && self->widgetViewSelected){
|
||||
[self->selectedWidgetView adjustTransparencyWithAlpha:self.widgetAlphaSlider.value];
|
||||
}
|
||||
|
||||
if(self->selectedControllerLayer != nil && self->controllerLayerSelected){
|
||||
[self.layoutOSC adjustControllerLayerOpacityWith:self->selectedControllerLayer and:self.widgetAlphaSlider.value];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)widgetBorderWidthSliderMoved{
|
||||
[widgetBorderWidthSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Border Width: %.2f", self.widgetBorderWidthSlider.value]];
|
||||
if(self->selectedWidgetView != nil && self->widgetViewSelected){
|
||||
[self->selectedWidgetView adjustBorderWithWidth:self.widgetBorderWidthSlider.value];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)sensitivitySliderMoved{
|
||||
[sensitivitySliderLabel setText:[LocalizationHelper localizedStringForKey:@"Sensitivity: %.2f", self.sensitivityFactorSlider.value]];
|
||||
if(self->selectedWidgetView != nil && self->widgetViewSelected) self->selectedWidgetView.sensitivityFactor = self.sensitivityFactorSlider.value;
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)stickIndicatorOffsetSliderMoved{
|
||||
[stickIndicatorOffsetSliderLabel setText:[LocalizationHelper localizedStringForKey:@"Indicator Offset: %.2f", self.stickIndicatorOffsetSlider.value]];
|
||||
if(self->selectedWidgetView != nil && self->widgetViewSelected){
|
||||
self->selectedWidgetView.stickIndicatorOffset = self.stickIndicatorOffsetSlider.value;
|
||||
[self->selectedWidgetView updateStickIndicator];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)showIndicatorOffset{
|
||||
selectedWidgetView.touchBeganLocation = CGPointMake(CGRectGetWidth(selectedWidgetView.frame)/2, CGRectGetHeight(selectedWidgetView.frame)/4);
|
||||
[selectedWidgetView showStickIndicator];
|
||||
}
|
||||
|
||||
- (void)setupProfileLableAndSliders{
|
||||
// self.currentProfileLabel.frame = CGRectMake(0, 0, 180, 35);
|
||||
// CGFloat profileLabelXPosition = self.view.bounds.size.width - self.currentProfileLabel.frame.size.width - 20;
|
||||
CGFloat sliderXPosition = (self.view.bounds.size.width - self.widgetSizeSlider.frame.size.width)/2 ;
|
||||
CGFloat profileLabelXPosition = self.view.bounds.size.width/2 + (self.widgetSizeSlider.frame.size.width)/2 + 8;
|
||||
|
||||
// Set the label's frame with the calculated x-position
|
||||
self.currentProfileLabel.frame = CGRectMake(profileLabelXPosition, self.currentProfileLabel.frame.origin.y, self.currentProfileLabel.frame.size.width, self.currentProfileLabel.frame.size.height);
|
||||
self.currentProfileLabel.hidden = NO; // Show Current Profile display
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.currentProfileLabel.textAlignment = NSTextAlignmentNatural;
|
||||
self.currentProfileLabel.numberOfLines = 2;
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
[self.currentProfileLabel setText:[LocalizationHelper localizedStringForKey:@"Profile: %@",[profilesManager getSelectedProfile].name]]; // display current profile name when profile is being refreshed.
|
||||
|
||||
// button size sliders
|
||||
self.widgetSizeSlider.hidden = NO;
|
||||
self.widgetSizeSlider.frame = CGRectMake(sliderXPosition, self.widgetSizeSlider.frame.origin.y, self.widgetSizeSlider.frame.size.width, self.widgetSizeSlider.frame.size.height);
|
||||
[self.widgetSizeSlider addTarget:self action:@selector(widgetSizeSliderMoved) forControlEvents:(UIControlEventValueChanged)];
|
||||
widgetSizeSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Widget Size"];
|
||||
widgetSizeSliderLabel.font = [UIFont systemFontOfSize:18];
|
||||
widgetSizeSliderLabel.textColor = [UIColor whiteColor];
|
||||
widgetSizeSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
widgetSizeSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
// Add slider and label to the view
|
||||
[self.view addSubview:widgetSizeSliderLabel];
|
||||
// Use Auto Layout to position the label relative to the slider
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Position the label to the left of the slider
|
||||
[widgetSizeSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetSizeSlider.leadingAnchor constant:-10],
|
||||
// Align vertically with the slider
|
||||
[widgetSizeSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetSizeSlider.centerYAnchor]
|
||||
]];
|
||||
widgetSizeSliderLabel.hidden = NO;
|
||||
|
||||
// button height sliders
|
||||
self.widgetHeightSlider.hidden = NO;
|
||||
self.widgetHeightSlider.frame = CGRectMake(sliderXPosition, self.widgetHeightSlider.frame.origin.y, self.widgetHeightSlider.frame.size.width, self.widgetHeightSlider.frame.size.height);
|
||||
[self.widgetHeightSlider addTarget:self action:@selector(widgetHeightSliderMoved) forControlEvents:(UIControlEventValueChanged)];
|
||||
// button height label
|
||||
widgetHeightSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Widget Height"];
|
||||
widgetHeightSliderLabel.font = [UIFont systemFontOfSize:18];
|
||||
widgetHeightSliderLabel.textColor = [UIColor whiteColor];
|
||||
widgetHeightSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
widgetHeightSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
// Add slider and label to the view
|
||||
[self.view addSubview:widgetHeightSliderLabel];
|
||||
// Use Auto Layout to position the label relative to the slider
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Position the label to the left of the slider
|
||||
[widgetHeightSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetHeightSlider.leadingAnchor constant:-10],
|
||||
// Align vertically with the slider
|
||||
[widgetHeightSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetHeightSlider.centerYAnchor]
|
||||
]];
|
||||
widgetHeightSliderLabel.hidden = NO;
|
||||
|
||||
// button alpha label
|
||||
self.widgetAlphaSlider.hidden = NO;
|
||||
self.widgetAlphaSlider.frame = CGRectMake(sliderXPosition, self.widgetAlphaSlider.frame.origin.y, self.widgetAlphaSlider.frame.size.width, self.widgetAlphaSlider.frame.size.height);
|
||||
[self.widgetAlphaSlider addTarget:self action:@selector(widgetAlphaSliderMoved) forControlEvents:(UIControlEventValueChanged)];
|
||||
widgetAlphaSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Widget Alpha"];
|
||||
widgetAlphaSliderLabel.font = [UIFont systemFontOfSize:18];
|
||||
widgetAlphaSliderLabel.textColor = [UIColor whiteColor];
|
||||
widgetAlphaSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
widgetAlphaSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
// Add slider and label to the view
|
||||
[self.view addSubview:widgetAlphaSliderLabel];
|
||||
// Use Auto Layout to position the label relative to the slider
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Position the label to the left of the slider
|
||||
[widgetAlphaSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetAlphaSlider.leadingAnchor constant:-10],
|
||||
// Align vertically with the slider
|
||||
[widgetAlphaSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetAlphaSlider.centerYAnchor]
|
||||
]];
|
||||
widgetAlphaSliderLabel.hidden = NO;
|
||||
|
||||
|
||||
// border Width slider
|
||||
self.widgetBorderWidthSlider.hidden = NO;
|
||||
self.widgetBorderWidthSlider.frame = CGRectMake(CGRectGetMaxX(self.currentProfileLabel.frame)-self.widgetBorderWidthSlider.frame.size.width, self.widgetBorderWidthSlider.frame.origin.y, self.widgetBorderWidthSlider.frame.size.width, self.widgetBorderWidthSlider.frame.size.height);
|
||||
|
||||
[self.widgetBorderWidthSlider addTarget:self action:@selector(widgetBorderWidthSliderMoved) forControlEvents:(UIControlEventValueChanged)];
|
||||
widgetBorderWidthSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Border Width"];
|
||||
widgetBorderWidthSliderLabel.font = [UIFont systemFontOfSize:18];
|
||||
widgetBorderWidthSliderLabel.textColor = [UIColor whiteColor];
|
||||
widgetBorderWidthSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
widgetBorderWidthSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
[self.view addSubview:widgetBorderWidthSliderLabel];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Position the label to the left of the slider
|
||||
[widgetBorderWidthSliderLabel.trailingAnchor constraintEqualToAnchor:self.widgetBorderWidthSlider.leadingAnchor constant:-10],
|
||||
// Align vertically with the slider
|
||||
[widgetBorderWidthSliderLabel.centerYAnchor constraintEqualToAnchor:self.widgetBorderWidthSlider.centerYAnchor]
|
||||
]];
|
||||
widgetBorderWidthSliderLabel.hidden = NO;
|
||||
|
||||
|
||||
|
||||
// sensitivity slider
|
||||
self.sensitivityFactorSlider.hidden = YES;
|
||||
self.sensitivityFactorSlider.frame = CGRectMake(sliderXPosition, self.sensitivityFactorSlider.frame.origin.y, self.sensitivityFactorSlider.frame.size.width, self.sensitivityFactorSlider.frame.size.height);
|
||||
[self.sensitivityFactorSlider addTarget:self action:@selector(sensitivitySliderMoved) forControlEvents:(UIControlEventValueChanged)];
|
||||
sensitivitySliderLabel.text = [LocalizationHelper localizedStringForKey:@"Sensitivity"];
|
||||
sensitivitySliderLabel.font = [UIFont systemFontOfSize:18];
|
||||
sensitivitySliderLabel.textColor = [UIColor whiteColor];
|
||||
sensitivitySliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
sensitivitySliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
// Add slider and label to the view
|
||||
[self.view addSubview:sensitivitySliderLabel];
|
||||
// Use Auto Layout to position the label relative to the slider
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Position the label to the left of the slider
|
||||
[sensitivitySliderLabel.trailingAnchor constraintEqualToAnchor:self.sensitivityFactorSlider.leadingAnchor constant:-10],
|
||||
// Align vertically with the slider
|
||||
[sensitivitySliderLabel.centerYAnchor constraintEqualToAnchor:self.sensitivityFactorSlider.centerYAnchor]
|
||||
]];
|
||||
sensitivitySliderLabel.hidden = self.sensitivityFactorSlider.hidden;
|
||||
|
||||
|
||||
// stick indicator offset slider
|
||||
self.stickIndicatorOffsetSlider.hidden = YES;
|
||||
self.stickIndicatorOffsetSlider.frame = CGRectMake(sliderXPosition, self.stickIndicatorOffsetSlider.frame.origin.y, self.stickIndicatorOffsetSlider.frame.size.width, self.stickIndicatorOffsetSlider.frame.size.height); // make this label bigger to show some tips
|
||||
[self.stickIndicatorOffsetSlider addTarget:self action:@selector(stickIndicatorOffsetSliderMoved) forControlEvents:(UIControlEventValueChanged)];
|
||||
stickIndicatorOffsetSliderLabel.text = [LocalizationHelper localizedStringForKey:@"Indicator Offset"];
|
||||
stickIndicatorOffsetSliderLabel.font = [UIFont systemFontOfSize:18];
|
||||
stickIndicatorOffsetSliderLabel.textColor = [UIColor whiteColor];
|
||||
stickIndicatorOffsetSliderLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
stickIndicatorOffsetSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
// tips for stick indicator
|
||||
stickIndicatorOffsetExplain.text = [LocalizationHelper localizedStringForKey:@"Ball: assumed touch point. Cross: indicator location"];
|
||||
//stickIndicatorOffsetExplain.frame = CGRectMake(sliderXPosition, self.stickIndicatorOffsetSlider.frame.origin.y + self.stickIndicatorOffsetSlider.frame.size.height, self.stickIndicatorOffsetSlider.frame.size.width, self.stickIndicatorOffsetSlider.frame.size.height); // label bigger show some tips
|
||||
stickIndicatorOffsetExplain.font = [UIFont systemFontOfSize:18];
|
||||
stickIndicatorOffsetExplain.textColor = [UIColor whiteColor];
|
||||
stickIndicatorOffsetExplain.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
stickIndicatorOffsetExplain.translatesAutoresizingMaskIntoConstraints = NO; // Disable autoresizing mask for Auto Layout
|
||||
// Add slider and label to the view
|
||||
[self.view addSubview:stickIndicatorOffsetSliderLabel];
|
||||
[self.view addSubview:stickIndicatorOffsetExplain];
|
||||
// Use Auto Layout to position the label relative to the slider
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Position the label to the left of the slider
|
||||
[stickIndicatorOffsetSliderLabel.trailingAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.leadingAnchor constant:-10],
|
||||
// Align vertically with the slider
|
||||
[stickIndicatorOffsetSliderLabel.centerYAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.centerYAnchor],
|
||||
// Position the explain label
|
||||
[stickIndicatorOffsetExplain.centerXAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.leadingAnchor constant:-10],
|
||||
[stickIndicatorOffsetExplain.topAnchor constraintEqualToAnchor:self.stickIndicatorOffsetSlider.bottomAnchor]
|
||||
]];
|
||||
stickIndicatorOffsetSliderLabel.hidden = stickIndicatorOffsetExplain.hidden = self.stickIndicatorOffsetSlider.hidden;
|
||||
}
|
||||
|
||||
|
||||
/* Basically the same method as loadTapped, without parameter*/
|
||||
// Make sure whenever self view controller load the selected profile and layout its buttons.
|
||||
- (void)profileRefresh{
|
||||
UIStoryboard *storyboard;
|
||||
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
|
||||
if (isIPhone) {
|
||||
storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
|
||||
}
|
||||
else {
|
||||
storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
|
||||
}
|
||||
|
||||
// setup: current profile lable, button width slider, button height slider & button alpha slider
|
||||
[self setupProfileLableAndSliders];
|
||||
|
||||
//initialiaze _oscProfilesTableViewController
|
||||
self->_oscProfilesTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"OSCProfilesTableViewController"];
|
||||
|
||||
//this part is just for registration, will not be immediately executed.
|
||||
self->_oscProfilesTableViewController.needToUpdateOscLayoutTVC = ^() { // a block that will be called when the modally presented 'OSCProfilesTableViewController' VC is dismissed. By the time the 'OSCProfilesTableViewController' VC is dismissed the user would have potentially selected a different OSC profile with a different layout and they want to see this layout on this 'LayoutOnScreenControlsViewController.' This block of code will load the profile and then hide/show and move each OSC button to their appropriate position
|
||||
[self.layoutOSC updateControls]; // creates and saves a 'Default' OSC profile or loads the one the user selected on the previous screen
|
||||
[self addInnerAnalogSticksToOuterAnalogLayers];
|
||||
[self.layoutOSC.layoutChanges removeAllObjects]; // since a new OSC profile is being loaded, this will remove all previous layout changes made from the array
|
||||
[self OSCLayoutChanged]; // fades the 'Undo Button' out
|
||||
self->_oscProfilesTableViewController.currentOSCButtonLayers = self.layoutOSC.OSCButtonLayers; //pass updated OSCLayout to OSCProfileTableView again
|
||||
//[self reloadOnScreenKeyboardButtons];
|
||||
};
|
||||
|
||||
[self.oscProfilesTableViewController profileViewRefresh]; // execute this will make sure OSCLayout is updated from persisted profile, not any cache.
|
||||
[self reloadOnScreenWidgetViews];
|
||||
|
||||
// [self presentViewController:vc animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
/* Presents the view controller that lists all OSC profiles the user can choose from */
|
||||
- (IBAction) loadTapped:(id)sender {
|
||||
UIStoryboard *storyboard;
|
||||
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
|
||||
if (isIPhone) {
|
||||
storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
|
||||
}
|
||||
else {
|
||||
storyboard = [UIStoryboard storyboardWithName:@"iPad" bundle:nil];
|
||||
}
|
||||
|
||||
_oscProfilesTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"OSCProfilesTableViewController"] ;
|
||||
|
||||
_oscProfilesTableViewController.needToUpdateOscLayoutTVC = ^() { // a block that will be called when the modally presented 'OSCProfilesTableViewController' VC is dismissed. By the time the 'OSCProfilesTableViewController' VC is dismissed the user would have potentially selected a different OSC ofile with a different layout and they want to see this layout on this 'LayoutOnScreenControlsViewController.' This block of code will load the profile and then hide/show and move each OSC button to their appropriate position
|
||||
[self.layoutOSC updateControls]; // creates and saves a 'Default' OSC profile or loads the one the user selected on the previous screen
|
||||
|
||||
[self addInnerAnalogSticksToOuterAnalogLayers];
|
||||
|
||||
[self.layoutOSC.layoutChanges removeAllObjects]; // since a new OSC profile is being loaded, this will remove all previous layout changes made from the array
|
||||
|
||||
[self OSCLayoutChanged]; // fades the 'Undo Button' out
|
||||
};
|
||||
self.currentProfileLabel.hidden = YES; // Hide Current Profile display before entering the profile table view
|
||||
self.widgetSizeSlider.hidden = YES;
|
||||
self.widgetHeightSlider.hidden = YES;
|
||||
self.widgetAlphaSlider.hidden = YES;
|
||||
widgetSizeSliderLabel.hidden = YES;
|
||||
widgetHeightSliderLabel.hidden = YES;
|
||||
widgetAlphaSliderLabel.hidden = YES;
|
||||
_oscProfilesTableViewController.currentOSCButtonLayers = self.layoutOSC.OSCButtonLayers;
|
||||
[self presentViewController:_oscProfilesTableViewController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Touch
|
||||
|
||||
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
for (UITouch* touch in touches) {
|
||||
|
||||
CGPoint touchLocation = [touch locationInView:self.view];
|
||||
touchLocation = [[touch view] convertPoint:touchLocation toView:nil];
|
||||
CALayer *layer = [self.view.layer hitTest:touchLocation];
|
||||
|
||||
if (layer == self.toolbarRootView.layer ||
|
||||
layer == self.chevronView.layer ||
|
||||
layer == self.chevronImageView.layer ||
|
||||
layer == self.toolbarStackView.layer ||
|
||||
layer == self.view.layer) { // don't let user move toolbar or toolbar UI buttons, toolbar's chevron 'pull tab', or the layer associated with this VC's view
|
||||
return;
|
||||
}
|
||||
}
|
||||
[self.layoutOSC touchesBegan:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
|
||||
// for OnScreenWidgets:
|
||||
|
||||
|
||||
// -------- for OSC buttons
|
||||
[self.layoutOSC touchesMoved:touches withEvent:event];
|
||||
if ([self.layoutOSC isLayer:self.layoutOSC.layerBeingDragged
|
||||
hoveringOverButton:trashCanButton]) { // check if user is dragging around a button and hovering it over the trash can button
|
||||
trashCanButton.tintColor = [UIColor redColor];
|
||||
}
|
||||
else {
|
||||
trashCanButton.tintColor = [UIColor colorWithRed:171.0/255.0 green:157.0/255.0 blue:255.0/255.0 alpha:1];
|
||||
}
|
||||
|
||||
// -------- for keyboard Buttons
|
||||
UITouch *touch = [touches anyObject]; // Get the first touch in the set
|
||||
if([self touchWithinTashcanButton:touch]){
|
||||
trashCanButton.tintColor = [UIColor redColor];
|
||||
}
|
||||
else trashCanButton.tintColor = [UIColor colorWithRed:171.0/255.0 green:157.0/255.0 blue:255.0/255.0 alpha:1];
|
||||
}
|
||||
|
||||
- (bool)touchWithinTashcanButton:(UITouch* )touch {
|
||||
CGPoint locationInView = [touch locationInView:self.view];
|
||||
|
||||
// Convert the location to the button's coordinate system
|
||||
CGPoint locationInButton = [self.view convertPoint:locationInView toView:trashCanButton];
|
||||
bool ret = CGRectContainsPoint(trashCanButton.bounds, locationInButton);
|
||||
// NSLog(@"within button: %d", ret);
|
||||
// Check if the location is within the button's bounds
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
|
||||
// removing keyboard buttons objs
|
||||
UITouch *touch = [touches anyObject]; // Get the first touch in the set
|
||||
if([self touchWithinTashcanButton:touch]){
|
||||
if(self->selectedWidgetView != nil){
|
||||
[self->selectedWidgetView removeFromSuperview];
|
||||
[self.OnScreenWidgetViews removeObject:self->selectedWidgetView];
|
||||
[selectedWidgetView.stickBallLayer removeFromSuperlayer];
|
||||
[selectedWidgetView.crossMarkLayer removeFromSuperlayer];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//removing OSC buttons
|
||||
if (self.layoutOSC.layerBeingDragged != nil &&
|
||||
[self.layoutOSC isLayer:self.layoutOSC.layerBeingDragged hoveringOverButton:trashCanButton]) { // check if user wants to throw OSC button into the trash can
|
||||
// here we're going to delete something
|
||||
|
||||
self.layoutOSC.layerBeingDragged.hidden = YES;
|
||||
|
||||
if ([self.layoutOSC.layerBeingDragged.name isEqualToString:@"dPad"]) { // if user is hiding dPad, then hide all four dPad button child layers as well since setting the 'hidden' property on the parent dPad CALayer doesn't automatically hide the four child CALayer dPad buttons
|
||||
self.layoutOSC._upButton.hidden = YES;
|
||||
self.layoutOSC._rightButton.hidden = YES;
|
||||
self.layoutOSC._downButton.hidden = YES;
|
||||
self.layoutOSC._leftButton.hidden = YES;
|
||||
}
|
||||
|
||||
/* if user is hiding left or right analog sticks, then hide their corresponding inner analog stick child layers as well since setting the 'hidden' property on the parent analog stick doesn't automatically hide its child inner analog stick CALayer */
|
||||
if ([self.layoutOSC.layerBeingDragged.name isEqualToString:@"leftStickBackground"]) {
|
||||
self.layoutOSC._leftStick.hidden = YES;
|
||||
}
|
||||
if ([self.layoutOSC.layerBeingDragged.name isEqualToString:@"rightStickBackground"]) {
|
||||
self.layoutOSC._rightStick.hidden = YES;
|
||||
}
|
||||
}
|
||||
[self.layoutOSC touchesEnded:touches withEvent:event];
|
||||
|
||||
// in case of default profile OSC change, popup msgbox & remind user it's not allowed.
|
||||
if([profilesManager getIndexOfSelectedProfile] == 0 && [self.layoutOSC.layoutChanges count] > 0){
|
||||
UIAlertController * movedAlertController = [UIAlertController alertControllerWithTitle: [NSString stringWithFormat:@""] message: [LocalizationHelper localizedStringForKey:@"Layout of the Default profile can not be changed"] preferredStyle:UIAlertControllerStyleAlert];
|
||||
[movedAlertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self.oscProfilesTableViewController profileViewRefresh];
|
||||
}]];
|
||||
[self presentViewController:movedAlertController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
trashCanButton.tintColor = [UIColor colorWithRed:171.0/255.0 green:157.0/255.0 blue:255.0/255.0 alpha:1];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// MainFrameViewController.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "DiscoveryManager.h"
|
||||
#import "PairManager.h"
|
||||
#import "StreamConfiguration.h"
|
||||
#import "UIComputerView.h"
|
||||
#import "UIAppView.h"
|
||||
#import "AppAssetManager.h"
|
||||
#import "SWRevealViewController.h"
|
||||
|
||||
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, HostCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate>
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIBarButtonItem *settingsButton;
|
||||
#if !TARGET_OS_TV
|
||||
@property (weak, nonatomic) IBOutlet UIBarButtonItem *upButton;
|
||||
@property (nonatomic, assign) bool settingsExpandedInStreamView;
|
||||
|
||||
-(void)simulateSettingsButtonPress;
|
||||
-(void)reloadStreamConfig;
|
||||
-(bool)isIPhonePortrait;
|
||||
#endif
|
||||
|
||||
@end
|
||||
@@ -22,6 +22,7 @@
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *touchModeSelector;
|
||||
@property (strong, nonatomic) IBOutlet UILabel *onscreenControllerLabel;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *onscreenControlSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *motionModeSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *largerStickLR1Selector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *optimizeSettingsSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *multiControllerSelector;
|
||||
@@ -29,6 +30,7 @@
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *pipSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
|
||||
|
||||
@@ -228,7 +228,7 @@ BOOL isCustomResolution(CGSize res) {
|
||||
}
|
||||
|
||||
- (void)updateResolutionTable{
|
||||
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
|
||||
UIWindow *window = self.view.window;
|
||||
CGFloat screenScale = window.screen.scale;
|
||||
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
||||
CGFloat appWindowWidth = window.frame.size.width * screenScale;
|
||||
@@ -465,6 +465,7 @@ BOOL isCustomResolution(CGSize res) {
|
||||
}
|
||||
|
||||
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
|
||||
[self.pipSelector setSelectedSegmentIndex:currentSettings.enablePIP ? 1 : 0];
|
||||
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
|
||||
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
|
||||
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
|
||||
@@ -477,6 +478,8 @@ BOOL isCustomResolution(CGSize res) {
|
||||
[self.resolutionSelector addTarget:self action:@selector(newResolutionChosen) forControlEvents:UIControlEventValueChanged];
|
||||
[self.framerateSelector setSelectedSegmentIndex:framerate];
|
||||
[self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged];
|
||||
NSInteger motionMode = [currentSettings.motionMode integerValue];
|
||||
[self.motionModeSelector setSelectedSegmentIndex:motionMode];
|
||||
[self.bitrateSlider setMinimumValue:0];
|
||||
[self.bitrateSlider setMaximumValue:(sizeof(bitrateTable) / sizeof(*bitrateTable)) - 1];
|
||||
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
|
||||
@@ -1054,6 +1057,7 @@ BOOL isCustomResolution(CGSize res) {
|
||||
NSInteger height = [self getChosenStreamHeight];
|
||||
NSInteger width = [self getChosenStreamWidth];
|
||||
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
|
||||
NSInteger motionMode = [self.motionModeSelector selectedSegmentIndex];
|
||||
NSInteger keyboardToggleFingers = (uint16_t)self.keyboardToggleFingerNumSlider.value;
|
||||
NSInteger oscLayoutToolFingers = (uint16_t)self->oscLayoutFingers;
|
||||
// NSLog(@"saveSettings keyboardToggleFingers %d", (uint16_t)keyboardToggleFingers);
|
||||
@@ -1076,6 +1080,7 @@ BOOL isCustomResolution(CGSize res) {
|
||||
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
|
||||
uint32_t preferredCodec = [self getChosenCodecPreference];
|
||||
BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1;
|
||||
BOOL enablePIP = [self.pipSelector selectedSegmentIndex] == 1;
|
||||
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
|
||||
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
|
||||
// BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
|
||||
@@ -1087,12 +1092,14 @@ BOOL isCustomResolution(CGSize res) {
|
||||
NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex];
|
||||
NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex];
|
||||
NSInteger localMousePointerMode = [self.localMousePointerModeSelector selectedSegmentIndex];
|
||||
NSLog(@"saving motionMode: %ld", motionMode);
|
||||
[dataMan saveSettingsWithBitrate:_bitrate
|
||||
framerate:framerate
|
||||
height:height
|
||||
width:width
|
||||
audioConfig:2 // Stereo
|
||||
onscreenControls:onscreenControls
|
||||
motionMode:motionMode
|
||||
keyboardToggleFingers:keyboardToggleFingers
|
||||
oscLayoutToolFingers:oscLayoutToolFingers
|
||||
slideToSettingsScreenEdge:slideToSettingsScreenEdge
|
||||
@@ -1111,6 +1118,7 @@ BOOL isCustomResolution(CGSize res) {
|
||||
audioOnPC:audioOnPC
|
||||
preferredCodec:preferredCodec
|
||||
enableYUV444:enableYUV444
|
||||
enablePIP:enablePIP
|
||||
useFramePacing:useFramePacing
|
||||
enableHdr:enableHdr
|
||||
btMouseSupport:btMouseSupport
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// StreamFrameViewController.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/18/14.
|
||||
// Copyright (c) 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Connection.h"
|
||||
#import "StreamConfiguration.h"
|
||||
#import "StreamView.h"
|
||||
#import "LayoutOnScreenControlsViewController.h"
|
||||
#import "MainFrameViewController.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#if TARGET_OS_TV
|
||||
@import GameController;
|
||||
|
||||
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
||||
#else
|
||||
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
||||
|
||||
#endif
|
||||
@property (nonatomic) StreamConfiguration* streamConfig;
|
||||
@property (nonatomic, assign) MainFrameViewController *mainFrameViewcontroller;
|
||||
|
||||
|
||||
-(void)updatePreferredDisplayMode:(BOOL)streamActive;
|
||||
|
||||
@end
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Moonlight-ZWM</string>
|
||||
<string>VoidLink</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>054A62F3-1197-45FB-A607-EA7922396E24</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>Limelight</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
|
||||
<string>github.com:limelight-stream/limelight-ios.git</string>
|
||||
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
|
||||
<string>github.com:limelight-stream/limelight-common-c.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>Limelight.xcodeproj</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
|
||||
<string>../..</string>
|
||||
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
|
||||
<string>../../limelight-common-c</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:limelight-stream/limelight-ios.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>EB99D631D5149240E91F8D168F0F298D7A5BAA89</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>limelight-common-c</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>limelight-ios</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>AD7C6582-F39A-441A-A7FD-9EBF143920BA</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>Moonlight</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
|
||||
<string>github.com:limelight-stream/limelight-ios.git</string>
|
||||
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
|
||||
<string>github.com:limelight-stream/limelight-common-c.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>Moonlight.xcodeproj</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
|
||||
<string>../..</string>
|
||||
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
|
||||
<string>../../limelight-common-c</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:limelight-stream/limelight-ios.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>EB99D631D5149240E91F8D168F0F298D7A5BAA89</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>limelight-common-c</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>moonlight-ios</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,53 +0,0 @@
|
||||
<?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>IDESourceControlProjectFavoriteDictionaryKey</key>
|
||||
<false/>
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>6524503E-ED62-4E8E-8A67-B6DFB5A532FD</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>project</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
|
||||
<string>github.com:limelight-stream/limelight-ios.git</string>
|
||||
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
|
||||
<string>github.com:limelight-stream/limelight-common-c.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>Moonlight.xcodeproj/project.xcworkspace</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</key>
|
||||
<string>../..</string>
|
||||
<key>EB99D631D5149240E91F8D168F0F298D7A5BAA89</key>
|
||||
<string>../..limelight-common-c/</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:limelight-stream/limelight-ios.git</string>
|
||||
<key>IDESourceControlProjectVersion</key>
|
||||
<integer>111</integer>
|
||||
<key>IDESourceControlProjectWCCIdentifier</key>
|
||||
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
|
||||
<key>IDESourceControlProjectWCConfigurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>EB99D631D5149240E91F8D168F0F298D7A5BAA89</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>limelight-common-c</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
|
||||
<string>public.vcs.git</string>
|
||||
<key>IDESourceControlWCCIdentifierKey</key>
|
||||
<string>A4B0849259F9566B0CBA84A6D3D4A0F895C03123</string>
|
||||
<key>IDESourceControlWCCName</key>
|
||||
<string>limelight-ios</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,24 +1,42 @@
|
||||
# Moonlight iOS/tvOS
|
||||
# VoidLink 已上架App Store.
|
||||
# VoidLink is available on App Store
|
||||
### [https://apps.apple.com/app/voidlink/id6747717070](https://apps.apple.com/cn/app/voidlink/id6747717070)
|
||||
|
||||
[](https://ci.appveyor.com/project/cgutman/moonlight-ios/branch/master)
|
||||
<br>
|
||||
|
||||
[Moonlight for iOS/tvOS](https://moonlight-stream.org) is an open source client for [Sunshine](https://github.com/LizardByte/Sunshine) and NVIDIA GameStream. Moonlight for iOS/tvOS allows you to stream your full collection of games and apps from your powerful desktop computer to your iOS device or Apple TV.
|
||||
# 代码 Coding
|
||||
- 代码提交在`Integration`分支。
|
||||
- For latest coding commits, go to branch `Integration`.
|
||||
|
||||
Moonlight also has a [PC client](https://github.com/moonlight-stream/moonlight-qt) and [Android client](https://github.com/moonlight-stream/moonlight-android).
|
||||
<br>
|
||||
|
||||
Check out [the Moonlight wiki](https://github.com/moonlight-stream/moonlight-docs/wiki) for more detailed project information, setup guide, or troubleshooting steps.
|
||||
# 关于VoidLink. About VoidLink
|
||||
- 本项目最初基于开源项目 [moonlight-iOS] fork 而来。在此基础上,True砖家(True Zhuanjia)@ Bilibili 及其他社区开发者对项目进行了大量重构、重新设计与功能扩展,包括全新的用户界面和显著增强的功能特性。<br>我们对 moonlight-iOS 开发者的开创性工作表示衷心感谢。 <br><br>
|
||||
- VoidLink was originally forked from the open-source project [moonlight-iOS], but has since been extensively reworked, redesigned, and expanded by True砖家 (True Zhuanjia) @ Bilibili and other community developers. These contributions include a completely new user interface and significant enhancements to the application's functionality.<br>We gratefully acknowledge the foundational work of the moonlight-iOS developers.
|
||||
|
||||
[](https://apps.apple.com/us/app/moonlight-game-streaming/id1000551566)
|
||||
<br>
|
||||
|
||||
## Building
|
||||
* Install Xcode from the [App Store page](https://apps.apple.com/us/app/xcode/id497799835)
|
||||
* Run `git clone --recursive https://github.com/moonlight-stream/moonlight-ios.git`
|
||||
* If you've already clone the repo without `--recursive`, run `git submodule update --init --recursive`
|
||||
* Open Moonlight.xcodeproj in Xcode
|
||||
* To run on a real device, you will need to locally modify the signing options:
|
||||
* Click on "Moonlight" at the top of the left sidebar
|
||||
* Click on the "Signing & Capabilities" tab
|
||||
* Under "Targets", select "Moonlight" (for iOS/iPadOS) or "Moonlight TV" (for tvOS)
|
||||
* In the "Team" dropdown, select your name. If your name doesn't appear, you may need to sign into Xcode with your Apple account.
|
||||
* Change the "Bundle Identifier" to something different. You can add your name or some random letters to make it unique.
|
||||
* Now you can select your Apple device in the top bar as a target and click the Play button to run.
|
||||
# App Store 分发一次性收费声明.
|
||||
# Statement on One-Time App Store Distribution Fee.
|
||||
- VoidLink 的 App Store 安装费用用于覆盖通过 Apple 框架进行安全可靠应用分发的成本。同时也支持项目维护者进行持续的开发、维护,以及公众用户访问并下载官方签名版本。感谢您对项目的支持,帮助我们持续改进和优化。<br><br>
|
||||
- The App Store fee for VoidLink covers the cost of secure and trusted distribution using Apple's infrastructure. It supports ongoing development, maintenance, and access to the official signed build. Thank you for supporting the project and helping sustain ongoing improvements.
|
||||
|
||||
<br>
|
||||
|
||||
# 开发者B站号. Developer on Bilibili
|
||||
|
||||
如果你在用Bilibili, 请关注`True砖家`,了解该fork的最新消息。 <br>
|
||||
If you are on Bilibili, subscribe `True砖家` to get the latest news of this fork: <br>
|
||||
https://b23.tv/A0F9v7n
|
||||
|
||||
<br>
|
||||
|
||||
# 贡献者 Contributors
|
||||
[@TrueZhuangJia](https://github.com/TrueZhuangJia) <br>
|
||||
[@All contributors from moonlight-iOS](https://github.com/moonlight-stream/moonlight-ios/graphs/contributors) <br>
|
||||
[@stefanilijev97](https://github.com/stefanilijev97/stefanilijev97) <br>
|
||||
[@Acaki](https://github.com/Acaki) <br>
|
||||
[@seastwood](https://github.com/seastwood) <br>
|
||||
[@Danos0100](https://github.com/Danos0100) <br>
|
||||
[@xzzpig](https://github.com/xzzpig) <br>
|
||||
[@King0fSpace](https://github.com/King0fSpace) <br>
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:/Users/admin/moonlight-ios/Moonlight-ZWM.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"originHash" : "d1fc50a28d75017e32553df2b233f8a7e2cf1542da32728ce6722b200f745608",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoalumberjack",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git",
|
||||
"state" : {
|
||||
"revision" : "a9ed4b6f9bdedce7d77046f43adfb8ce1fd54114",
|
||||
"version" : "3.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "openssl-package",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/OpenSSL-Package.git",
|
||||
"state" : {
|
||||
"revision" : "b9eb055fdf73e595cb4b9f665d13dc85975bf80a",
|
||||
"version" : "3.3.2000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "svgkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SVGKit/SVGKit.git",
|
||||
"state" : {
|
||||
"revision" : "58152b9f7c85eab239160b36ffdfd364aa43d666",
|
||||
"version" : "3.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log",
|
||||
"state" : {
|
||||
"revision" : "bc386b95f2a16ccd0150a8235e7c69eab2b866ca",
|
||||
"version" : "1.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// AboutView.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 5/18/25.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
|
||||
public struct AboutView: View {
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
public var aboutVC:UIViewController
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: 7) {
|
||||
// App 图标
|
||||
Image(uiImage: UIImage(named: "AppIconMedium") ?? UIImage())
|
||||
// .resizable()
|
||||
.resizable(capInsets:EdgeInsets(top: 3.5, leading: 3.5, bottom: 3.5, trailing: 3.5))
|
||||
.frame(width: 90, height: 90)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28))
|
||||
|
||||
// App 名称
|
||||
Text(Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "App Name")
|
||||
.font(.title)
|
||||
.bold()
|
||||
|
||||
// 版本号
|
||||
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
|
||||
Text("Version \(version)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
if Locale.preferredLanguages.first!.hasPrefix("zh-Hans") {
|
||||
if #available(iOS 14.0, *) {
|
||||
Text("闽ICP备17012590号-3A 主办单位:福州创图信息技术有限公司").font(.caption2)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(nil)
|
||||
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
|
||||
//.padding()
|
||||
} else {
|
||||
Text("闽ICP备17012590号-3A 主办单位:福州创图信息技术有限公司").font(.caption)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(nil)
|
||||
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
|
||||
//.padding()
|
||||
}
|
||||
}
|
||||
|
||||
// 说明文字
|
||||
Text(SwiftLocalizationHelper.localizedString(forKey: "From the player community, to the player community."))
|
||||
.multilineTextAlignment(.center)
|
||||
.font(Font.caption.italic())
|
||||
.lineLimit(nil)
|
||||
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
|
||||
.padding()
|
||||
|
||||
|
||||
Text(SwiftLocalizationHelper.localizedString(forKey: "VoidLink delivers better performance now!"))
|
||||
.multilineTextAlignment(.center)
|
||||
.font(Font.callout.bold())
|
||||
.lineLimit(nil)
|
||||
.frame(maxWidth: 570) // ✅ 避免 Text 被拉得太宽无法换行
|
||||
|
||||
// 链接按钮
|
||||
if #available(iOS 14.0, *) {
|
||||
Link(SwiftLocalizationHelper.localizedString(forKey: "Learn more & join us"), destination: URL(string: SwiftLocalizationHelper.localizedString(forKey: "supportLink"))!)
|
||||
.padding(.top, 10)
|
||||
Spacer()
|
||||
// OK 按钮
|
||||
Button(SwiftLocalizationHelper.localizedString(forKey: "OK")) {
|
||||
aboutVC.dismiss(animated:true)
|
||||
}
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.frame(height: 46)
|
||||
.cornerRadius(12)
|
||||
} else {
|
||||
HStack(spacing: 20) {
|
||||
Button(SwiftLocalizationHelper.localizedString(forKey: "Join us")) {
|
||||
// 打开链接
|
||||
if let url = URL(string: SwiftLocalizationHelper.localizedString(forKey: "supportLink")) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
.frame(height: 33)
|
||||
.frame(minWidth: 100)
|
||||
/*
|
||||
Button(SwiftLocalizationHelper.localizedString(forKey: "OK")) {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
.frame(height: 33)
|
||||
.frame(minWidth: 100)*/
|
||||
Button(SwiftLocalizationHelper.localizedString(forKey: "OK")) {
|
||||
aboutVC.dismiss(animated:true)
|
||||
}
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
.frame(height: 33)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// AboutViewController.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by Weimin on 2025/7/14.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@objc class AboutViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let hostingVC = UIHostingController(rootView: AboutView( aboutVC: self))
|
||||
|
||||
addChild(hostingVC)
|
||||
hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(hostingVC.view)
|
||||
hostingVC.didMove(toParent: self)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
hostingVC.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
hostingVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
hostingVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
hostingVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
])
|
||||
|
||||
// 🎯 设置弹窗大小
|
||||
preferredContentSize = CGSize(width: 530, height: 430)
|
||||
|
||||
// ⚠️ 一定要设置以下样式才会生效 preferredContentSize
|
||||
modalPresentationStyle = .formSheet
|
||||
}
|
||||
}
|
||||
|
||||
func isIPhone() -> Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// AlertControllerUtil.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by Weimin on 2025/10/6.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
import UIKit
|
||||
|
||||
@objc class AlertControllerUtil: NSObject {
|
||||
|
||||
/// 类方法,直接在任何 UIViewController 上显示倒计时弹窗
|
||||
/// - Parameters:
|
||||
/// - viewController: 要显示弹窗的控制器
|
||||
/// - title: 弹窗标题
|
||||
/// - message: 弹窗内容
|
||||
/// - buttonTitle: 按钮初始文字(倒计时结束后显示)
|
||||
/// - countdown: 倒计时秒数
|
||||
/// - completion: 点击确认或倒计时结束的回调
|
||||
|
||||
@objc public static var actionCancelled:Bool = false
|
||||
@objc public static var alertController:UIAlertController = UIAlertController()
|
||||
@objc public static var cancelButtonString:String = "Cancel"
|
||||
@objc public static var autoCompletion:Bool = false
|
||||
|
||||
@objc class func showAlert(
|
||||
in viewController: UIViewController,
|
||||
title: String?,
|
||||
message: String?,
|
||||
withCancel: Bool,
|
||||
buttonTitle: String,
|
||||
countdown: Int,
|
||||
action: (() -> Void)? = nil,
|
||||
completion: (() -> Void)? = nil
|
||||
) {
|
||||
var remainingSeconds = countdown
|
||||
|
||||
alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
|
||||
let confirmAction = UIAlertAction(title: "\(remainingSeconds)", style: .default) { _ in
|
||||
actionCancelled = false
|
||||
completion?()
|
||||
cancelButtonString = "Cancel"
|
||||
}
|
||||
|
||||
let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: cancelButtonString), style: .cancel) { _ in
|
||||
actionCancelled = true
|
||||
completion?()
|
||||
cancelButtonString = "Cancel"
|
||||
}
|
||||
|
||||
confirmAction.isEnabled = false
|
||||
|
||||
if withCancel {alertController.addAction(cancelAction)}
|
||||
if buttonTitle != "" && !autoCompletion {alertController.addAction(confirmAction)}
|
||||
|
||||
if(countdown == 0) {
|
||||
confirmAction.setValue(buttonTitle, forKey: "title")
|
||||
confirmAction.isEnabled = buttonTitle != ""
|
||||
}
|
||||
|
||||
viewController.present(alertController, animated: true, completion: action)
|
||||
|
||||
if(countdown == 0) {return}
|
||||
|
||||
// 倒计时
|
||||
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
|
||||
timer.schedule(deadline: .now(), repeating: 1.0, leeway: .milliseconds(50))
|
||||
timer.setEventHandler {
|
||||
remainingSeconds -= 1
|
||||
|
||||
if remainingSeconds <= 0 {
|
||||
timer.cancel()
|
||||
confirmAction.isEnabled = true
|
||||
confirmAction.setValue(buttonTitle, forKey: "title")
|
||||
if autoCompletion {
|
||||
autoCompletion = false
|
||||
completion?()
|
||||
alertController.dismiss(animated: false)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
confirmAction.setValue("\(remainingSeconds)", forKey: "title")
|
||||
}
|
||||
}
|
||||
timer.resume()
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,6 @@
|
||||
- (void)saveContext;
|
||||
- (NSURL *)applicationDocumentsDirectory;
|
||||
- (NSURL*) getStoreURL;
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0));
|
||||
|
||||
@end
|
||||
@@ -5,10 +5,13 @@
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.7.12
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "Moonlight-Swift.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
@@ -24,6 +27,15 @@ static NSString* DB_NAME = @"Moonlight_tvOS.bin";
|
||||
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||
#endif
|
||||
|
||||
#pragma mark - UISceneSession lifecycle
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){
|
||||
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)){
|
||||
}
|
||||
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
|
||||
@@ -48,6 +60,27 @@ static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// Override point for command tool customization after application launch (works only when user default is nil)
|
||||
[CommandManager presetDefaultCommands];
|
||||
|
||||
// For iOS 12 and below, we need to manually create the window
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// Scene delegate will handle window creation
|
||||
} else {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
|
||||
NSString *storyboardName;
|
||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
storyboardName = @"iPad";
|
||||
} else {
|
||||
storyboardName = @"iPhone";
|
||||
}
|
||||
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
|
||||
UIViewController *initialViewController = [storyboard instantiateInitialViewController];
|
||||
|
||||
self.window.rootViewController = initialViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ static NSData* p12 = nil;
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
i2d_X509_bio(bio, x509);
|
||||
X509_free(x509);
|
||||
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
@@ -222,6 +223,7 @@ static NSData* p12 = nil;
|
||||
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
||||
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (!x509) {
|
||||
Log(LOG_E, @"Unable to parse certificate in memory!");
|
||||
@@ -4,10 +4,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <OpenSSL/provider.h>
|
||||
#include <OpenSSL/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <OpenSSL/rand.h>
|
||||
|
||||
static const int NUM_BITS = 2048;
|
||||
static const int SERIAL = 0;
|
||||
@@ -61,22 +63,30 @@ void mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
||||
}
|
||||
|
||||
struct CertKeyPair generateCertKeyPair(void) {
|
||||
BIO *bio_err;
|
||||
X509 *x509 = NULL;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
PKCS12 *p12 = NULL;
|
||||
|
||||
bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
||||
|
||||
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
|
||||
|
||||
p12 = PKCS12_create("limelight", "GameStream", pkey, x509, NULL, 0, 0, 0, 0, 0);
|
||||
|
||||
char* pass = "limelight";
|
||||
p12 = PKCS12_create(pass,
|
||||
"GameStream",
|
||||
pkey,
|
||||
x509,
|
||||
NULL,
|
||||
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
|
||||
-1, // disable certificate encryption
|
||||
2048,
|
||||
-1, // disable the automatic MAC
|
||||
0);
|
||||
// MAC it ourselves with SHA1 since iOS refuses to load anything else.
|
||||
PKCS12_set_mac(p12, pass, -1, NULL, 0, 1, EVP_sha1());
|
||||
|
||||
if (p12 == NULL) {
|
||||
printf("Error generating a valid PKCS12 certificate.\n");
|
||||
}
|
||||
|
||||
BIO_free(bio_err);
|
||||
|
||||
return (CertKeyPair){x509, pkey, p12};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// DataManager.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/28/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.6.1
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "TemporaryHost.h"
|
||||
#import "TemporaryApp.h"
|
||||
#import "TemporarySettings.h"
|
||||
|
||||
@interface DataManager : NSObject
|
||||
|
||||
typedef NS_ENUM(NSInteger, ControllerGyroSwitchMode) {
|
||||
ControllerGyroSwitchDisabled,
|
||||
ControllerGyroSwitchPressToToggle,
|
||||
ControllerGyroSwitchHoldDown
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TouchMode) {
|
||||
RelativeTouch,
|
||||
NativeTouch,
|
||||
AbsoluteTouch,
|
||||
TouchDisabled,
|
||||
NativeTouchOnly
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TouchEventPriorityEnum) {
|
||||
AsyncNativeTouchOff,
|
||||
EqualPriority,
|
||||
TouchDownPriority,// deprecated by GUI
|
||||
TouchMovePriority// deprecated by GUI
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, GyroMode) {
|
||||
GyroModeOff,
|
||||
GyroModeAuto,
|
||||
AlwaysDevice,
|
||||
AlwaysController
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, HapticEnginePreference) {
|
||||
HapticEngineAuto,
|
||||
RumbleDevice,
|
||||
LeftRightSwapped,
|
||||
RumbleOff
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, ControllerMouseStick) {
|
||||
LeftStickToMouse,
|
||||
RightStickToMouse,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, FramePacingMode) {
|
||||
FramePacingModeOff,
|
||||
FramePacingModeLegacy,
|
||||
FramePacingModeQueue
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SettingsMenuMode) {
|
||||
AllSettings,
|
||||
FavoriteSettings,
|
||||
RemoveSettingItem,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, WidgetSizeTransition) {
|
||||
keepWidgetSize,
|
||||
transitionWithOrientation,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, PencilTickMode) {
|
||||
PencilTickDisabled,
|
||||
ManualTick
|
||||
};
|
||||
|
||||
- (void) saveSettings:(Settings*)settings
|
||||
withBitrate:(NSInteger)bitrate
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
gyroMode:(NSInteger)gyroMode
|
||||
emulatedControllerType:(NSInteger)emulatedControllerType
|
||||
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
|
||||
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
|
||||
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
|
||||
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
|
||||
gyroSensitivity:(CGFloat)gyroSensitivity
|
||||
localVolume:(CGFloat)localVolume
|
||||
micVolume:(CGFloat)micVolume
|
||||
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
|
||||
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
|
||||
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
|
||||
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
buttonVisualFeedback:(BOOL)buttonVisualFeedback
|
||||
touchPointTracking:(BOOL)touchPointTracking
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
redirectMic:(BOOL)redirectMic
|
||||
useBuiltinMic:(BOOL)useBuiltinMic
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
enableYUV444:(BOOL)enableYUV444
|
||||
enablePIP:(BOOL)enablePIP
|
||||
fullColorRange:(BOOL)fullColorRange
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
// absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
touchMode:(NSInteger)touchMode
|
||||
statsOverlayLevel:(NSInteger)statsOverlayLevel
|
||||
statsOverlayEnabled:(BOOL)statsOverlayEnabled
|
||||
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
|
||||
resolutionSelected:(NSInteger)resolutionSelected
|
||||
externalDisplayMode:(NSInteger)externalDisplayMode
|
||||
localMousePointerMode:(NSInteger)localMousePointerMode
|
||||
frameQueueSize:(NSInteger)frameQueueSize
|
||||
enableGraphs:(BOOL)enableGraphs
|
||||
graphOpacity:(NSInteger)graphOpacity
|
||||
renderingBackend:(NSInteger)renderingBackend
|
||||
framePacingMode:(NSInteger)framePacingMode
|
||||
sendDummyEvent:(BOOL)sendDummyEvent
|
||||
rememberFoldState:(BOOL)rememberFoldState
|
||||
singleTapSensitivy:(CGFloat)singleTapSensitivy
|
||||
hapticEngine:(NSInteger)hapticEngine
|
||||
edgeSlidingSensitivity:(CGFloat)edgeSlidingSensitivity
|
||||
audioEngine:(NSInteger)audioEngine
|
||||
delayLeftClick:(BOOL)delayLeftClick
|
||||
duckOtherApps:(BOOL)duckOtherApps
|
||||
muteInBackground:(BOOL)muteInBackground
|
||||
relativeTouchSlideThreshold:(CGFloat)relativeTouchSlideThreshold
|
||||
enablePinch:(BOOL)enablePinch
|
||||
scrollSensitivity:(CGFloat)scrollSensitivity
|
||||
pinchSensitivity:(CGFloat)pinchSensitivity
|
||||
ctrlDownForPinch:(BOOL)ctrlDownForPinch
|
||||
leftClickDelayMs:(CGFloat)leftClickDelayMs
|
||||
settingsMenuOffset:(CGFloat)settingsMenuOffset
|
||||
passthroughGestures:(BOOL)passthroughGestures
|
||||
mapControllerToMouse:(BOOL)mapControllerToMouse
|
||||
controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity
|
||||
controllerMouseExpo:(CGFloat)controllerMouseExpo
|
||||
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
|
||||
enableFrameTimebase:(BOOL)enableFrameTimebase
|
||||
asyncFrameDequeue:(BOOL)asyncFrameDequeue
|
||||
sdrPerformanceWorkaround:(BOOL)sdrPerformanceWorkaround
|
||||
backgroundSessionTimer:(NSInteger)backgroundSessionTimer;
|
||||
|
||||
- (NSArray*) getHosts;
|
||||
- (void) updateHost:(TemporaryHost*)host;
|
||||
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
|
||||
- (void) removeHost:(TemporaryHost*)host;
|
||||
- (void) removeApp:(TemporaryApp*)app;
|
||||
- (Settings*) retrieveSettings;
|
||||
- (void) saveData;
|
||||
- (TemporarySettings*) getSettings;
|
||||
|
||||
- (void) updateUniqueId:(NSString*)uniqueId;
|
||||
- (NSString*) getUniqueId;
|
||||
|
||||
@end
|
||||
@@ -5,6 +5,9 @@
|
||||
// Created by Diego Waxemberg on 10/28/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.6.1
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DataManager.h"
|
||||
#import "TemporaryApp.h"
|
||||
@@ -52,50 +55,94 @@
|
||||
return uid;
|
||||
}
|
||||
|
||||
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||
- (void) saveSettings:(Settings*)settings
|
||||
withBitrate:(NSInteger)bitrate
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
gyroMode:(NSInteger)gyroMode
|
||||
emulatedControllerType:(NSInteger)emulatedControllerType
|
||||
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
|
||||
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
|
||||
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
|
||||
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
|
||||
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
|
||||
oscTapExlusionAreaSize:(CGFloat)oscTapExlusionAreaSize
|
||||
gyroSensitivity:(CGFloat)gyroSensitivity
|
||||
localVolume:(CGFloat)localVolume
|
||||
micVolume:(CGFloat)micVolume
|
||||
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
|
||||
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
|
||||
largerStickLR1:(BOOL)largerStickLR1
|
||||
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
|
||||
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
buttonVisualFeedback:(BOOL)buttonVisualFeedback
|
||||
touchPointTracking:(BOOL)touchPointTracking
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
redirectMic:(BOOL)redirectMic
|
||||
useBuiltinMic:(BOOL)useBuiltinMic
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
enableYUV444:(BOOL)enableYUV444
|
||||
useFramePacing:(BOOL)useFramePacing
|
||||
enableYUV444:(BOOL)enableYUV444
|
||||
enablePIP:(BOOL)enablePIP
|
||||
fullColorRange:(BOOL)fullColorRange
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
// absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
touchMode:(NSInteger)touchMode
|
||||
statsOverlayLevel:(NSInteger)statsOverlayLevel
|
||||
statsOverlayEnabled:(BOOL)statsOverlayEnabled
|
||||
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
|
||||
statsOverlayEnabled:(BOOL)statsOverlayEnabled
|
||||
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
|
||||
resolutionSelected:(NSInteger)resolutionSelected
|
||||
externalDisplayMode:(NSInteger)externalDisplayMode
|
||||
localMousePointerMode:(NSInteger)localMousePointerMode {
|
||||
localMousePointerMode:(NSInteger)localMousePointerMode
|
||||
frameQueueSize:(NSInteger)frameQueueSize
|
||||
enableGraphs:(BOOL)enableGraphs
|
||||
graphOpacity:(NSInteger)graphOpacity
|
||||
renderingBackend:(NSInteger)renderingBackend
|
||||
framePacingMode:(NSInteger)framePacingMode
|
||||
sendDummyEvent:(BOOL)sendDummyEvent
|
||||
rememberFoldState:(BOOL)rememberFoldState
|
||||
singleTapSensitivy:(CGFloat)singleTapSensitivy
|
||||
hapticEngine:(NSInteger)hapticEngine
|
||||
edgeSlidingSensitivity:(CGFloat)edgeSlidingSensitivity
|
||||
audioEngine:(NSInteger)audioEngine
|
||||
delayLeftClick:(BOOL)delayLeftClick
|
||||
duckOtherApps:(BOOL)duckOtherApps
|
||||
muteInBackground:(BOOL)muteInBackground
|
||||
relativeTouchSlideThreshold:(CGFloat)relativeTouchSlideThreshold
|
||||
enablePinch:(BOOL)enablePinch
|
||||
scrollSensitivity:(CGFloat)scrollSensitivity
|
||||
pinchSensitivity:(CGFloat)pinchSensitivity
|
||||
ctrlDownForPinch:(BOOL)ctrlDownForPinch
|
||||
leftClickDelayMs:(CGFloat)leftClickDelayMs
|
||||
settingsMenuOffset:(CGFloat)settingsMenuOffset
|
||||
passthroughGestures:(BOOL)passthroughGestures
|
||||
mapControllerToMouse:(BOOL)mapControllerToMouse
|
||||
controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity
|
||||
controllerMouseExpo:(CGFloat)controllerMouseExpo
|
||||
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
|
||||
enableFrameTimebase:(BOOL)enableFrameTimebase
|
||||
asyncFrameDequeue:(BOOL)asyncFrameDequeue
|
||||
sdrPerformanceWorkaround:(BOOL)sdrPerformanceWorkaround
|
||||
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
|
||||
|
||||
__block Settings* settingsToSave = settings;
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
Settings* settingsToSave = [self retrieveSettings];
|
||||
if(!settingsToSave) settingsToSave = [self retrieveSettings];
|
||||
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
|
||||
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
|
||||
settingsToSave.height = [NSNumber numberWithInteger:height];
|
||||
settingsToSave.width = [NSNumber numberWithInteger:width];
|
||||
settingsToSave.audioConfig = [NSNumber numberWithInteger:audioConfig];
|
||||
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
|
||||
settingsToSave.gyroMode = [NSNumber numberWithInteger:gyroMode];
|
||||
settingsToSave.emulatedControllerType = [NSNumber numberWithInteger:emulatedControllerType];
|
||||
settingsToSave.keyboardToggleFingers = [NSNumber numberWithInteger:(uint16_t)keyboardToggleFingers];
|
||||
settingsToSave.oscLayoutToolFingers = [NSNumber numberWithInteger:(uint16_t)oscLayoutToolFingers];
|
||||
settingsToSave.slideToSettingsScreenEdge = [NSNumber numberWithInteger:(uint32_t)slideToSettingsScreenEdge];
|
||||
@@ -103,18 +150,27 @@
|
||||
settingsToSave.pointerVelocityModeDivider = [NSNumber numberWithFloat:pointerVelocityModeDivider];
|
||||
settingsToSave.touchPointerVelocityFactor = [NSNumber numberWithFloat:touchPointerVelocityFactor];
|
||||
settingsToSave.mousePointerVelocityFactor = [NSNumber numberWithFloat:mousePointerVelocityFactor];
|
||||
settingsToSave.oscTapExlusionAreaSize = [NSNumber numberWithFloat:oscTapExlusionAreaSize];
|
||||
settingsToSave.gyroSensitivity = [NSNumber numberWithFloat:gyroSensitivity];
|
||||
settingsToSave.localVolume = [NSNumber numberWithFloat:localVolume];
|
||||
settingsToSave.micVolume = [NSNumber numberWithFloat:micVolume];
|
||||
settingsToSave.touchMoveEventInterval = [NSNumber numberWithInteger:touchMoveEventInterval];
|
||||
settingsToSave.reverseMouseWheelDirection = reverseMouseWheelDirection;
|
||||
settingsToSave.largerStickLR1 = largerStickLR1;
|
||||
settingsToSave.asyncNativeTouchPriority = [NSNumber numberWithInteger:asyncNativeTouchPriority];
|
||||
settingsToSave.liftStreamViewForKeyboard = liftStreamViewForKeyboard;
|
||||
settingsToSave.showKeyboardToolbar = showKeyboardToolbar;
|
||||
settingsToSave.optimizeGames = optimizeGames;
|
||||
settingsToSave.multiController = multiController;
|
||||
settingsToSave.buttonVisualFeedback = buttonVisualFeedback;
|
||||
settingsToSave.touchPointTracking = touchPointTracking;
|
||||
settingsToSave.swapABXYButtons = swapABXYButtons;
|
||||
settingsToSave.playAudioOnPC = audioOnPC;
|
||||
settingsToSave.redirectMic = redirectMic;
|
||||
settingsToSave.useBuiltinMic = useBuiltinMic;
|
||||
settingsToSave.preferredCodec = preferredCodec;
|
||||
settingsToSave.enableYUV444 = enableYUV444;
|
||||
settingsToSave.useFramePacing = useFramePacing;
|
||||
settingsToSave.sdrPerformanceWorkaround = sdrPerformanceWorkaround;
|
||||
settingsToSave.enablePIP = enablePIP;
|
||||
settingsToSave.fullColorRange = fullColorRange;
|
||||
settingsToSave.enableHdr = enableHdr;
|
||||
settingsToSave.btMouseSupport = btMouseSupport;
|
||||
// settingsToSave.absoluteTouchMode = absoluteTouchMode;
|
||||
@@ -125,6 +181,35 @@
|
||||
settingsToSave.resolutionSelected = [NSNumber numberWithInteger:resolutionSelected];
|
||||
settingsToSave.externalDisplayMode = [NSNumber numberWithInteger:externalDisplayMode];
|
||||
settingsToSave.localMousePointerMode = [NSNumber numberWithInteger:localMousePointerMode];
|
||||
settingsToSave.backroundSessionTimer = [NSNumber numberWithInteger:backgroundSessionTimer];
|
||||
|
||||
settingsToSave.frameQueueSize = [NSNumber numberWithInteger:frameQueueSize];
|
||||
settingsToSave.enableFrameTimebase = enableFrameTimebase;
|
||||
settingsToSave.asyncFrameDequeue = asyncFrameDequeue;
|
||||
settingsToSave.enableGraphs = enableGraphs;
|
||||
settingsToSave.graphOpacity = [NSNumber numberWithInteger:graphOpacity];
|
||||
settingsToSave.renderingBackend = [NSNumber numberWithInteger:renderingBackend];
|
||||
settingsToSave.framePacingMode = [NSNumber numberWithInteger:framePacingMode];
|
||||
settingsToSave.sendDummyEvent = sendDummyEvent;
|
||||
settingsToSave.singleTapSensitivity = [NSNumber numberWithDouble:singleTapSensitivy];
|
||||
settingsToSave.hapticEngine = [NSNumber numberWithInteger:hapticEngine];
|
||||
settingsToSave.edgeSlidingSensitivity = [NSNumber numberWithFloat:edgeSlidingSensitivity];
|
||||
settingsToSave.audioEngine = [NSNumber numberWithInteger:audioEngine];
|
||||
settingsToSave.delayLeftClick = delayLeftClick;
|
||||
settingsToSave.duckOtherApps = duckOtherApps;
|
||||
settingsToSave.muteInBackground = muteInBackground;
|
||||
settingsToSave.relativeTouchSlideThreshold = [NSNumber numberWithFloat:relativeTouchSlideThreshold];
|
||||
settingsToSave.enablePinch = enablePinch;
|
||||
settingsToSave.scrollSensitivity = [NSNumber numberWithFloat:scrollSensitivity];
|
||||
settingsToSave.pinchSensitivity = [NSNumber numberWithFloat:pinchSensitivity];
|
||||
settingsToSave.ctrlDownForPinch = ctrlDownForPinch;
|
||||
settingsToSave.leftClickDelayMs = [NSNumber numberWithFloat:leftClickDelayMs];
|
||||
settingsToSave.settingsMenuOffset = [NSNumber numberWithFloat:settingsMenuOffset];
|
||||
settingsToSave.passthroughGestures = passthroughGestures;
|
||||
settingsToSave.mapControllerToMouse = mapControllerToMouse;
|
||||
settingsToSave.controllerMousePointerVelocity = [NSNumber numberWithFloat:controllerMousePointerVelocity];
|
||||
settingsToSave.controllerMouseExpo = [NSNumber numberWithFloat:controllerMouseExpo];
|
||||
settingsToSave.rememberFoldState = rememberFoldState;
|
||||
[self saveData];
|
||||
}];
|
||||
}
|
||||